mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-16 14:12:51 +00:00
perf: 新增录音转文字
This commit is contained in:
parent
4fa54381a6
commit
7b1d352c95
@ -1348,6 +1348,55 @@ class DialogController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/dialog/msg/convertrecord 25. 录音转文字
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup dialog
|
||||||
|
* @apiName msg__convertrecord
|
||||||
|
*
|
||||||
|
* @apiParam {Number} dialog_id 对话ID
|
||||||
|
* @apiParam {String} base64 语音base64
|
||||||
|
* @apiParam {Number} duration 语音时长(毫秒)
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function msg__convertrecord()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
$user->checkChatInformation();
|
||||||
|
//
|
||||||
|
$dialog_id = intval(Request::input('dialog_id'));
|
||||||
|
//
|
||||||
|
WebSocketDialog::checkDialog($dialog_id);
|
||||||
|
//
|
||||||
|
$path = "uploads/tmp/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||||
|
$base64 = Request::input('base64');
|
||||||
|
$duration = intval(Request::input('duration'));
|
||||||
|
if ($duration < 600) {
|
||||||
|
return Base::retError('说话时间太短');
|
||||||
|
}
|
||||||
|
$data = Base::record64save([
|
||||||
|
"base64" => $base64,
|
||||||
|
"path" => $path,
|
||||||
|
]);
|
||||||
|
if (Base::isError($data)) {
|
||||||
|
return Base::retError($data['msg']);
|
||||||
|
}
|
||||||
|
$recordData = $data['data'];
|
||||||
|
$res = Extranet::openAItranscriptions($recordData['file']);
|
||||||
|
if (Base::isError($res)) {
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
if (strlen($res['data']) < 1) {
|
||||||
|
return Base::retError('转文字失败');
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} api/dialog/msg/sendfile 26. 文件上传
|
* @api {post} api/dialog/msg/sendfile 26. 文件上传
|
||||||
*
|
*
|
||||||
|
|||||||
@ -188,15 +188,53 @@
|
|||||||
v-transfer-dom
|
v-transfer-dom
|
||||||
:data-transfer="true"
|
:data-transfer="true"
|
||||||
class="chat-input-record-transfer"
|
class="chat-input-record-transfer"
|
||||||
:class="{cancel: touchLimitY}"
|
:class="recordClassName"
|
||||||
:style="recordTransferStyle"
|
:style="recordTransferStyle"
|
||||||
@click="stopRecord">
|
@click="stopRecord">
|
||||||
<div v-if="recordDuration > 0" class="record-duration">{{recordFormatDuration}}</div>
|
<div v-if="recordDuration > 0" class="record-duration">{{recordFormatDuration}}</div>
|
||||||
<div v-else class="record-loading"><Loading type="pure"/></div>
|
<div v-else class="record-loading"><Loading type="pure"/></div>
|
||||||
<div class="record-cancel" @click.stop="stopRecord(true)">{{$L(touchLimitY ? '松开取消' : '向上滑动取消')}}</div>
|
<div class="record-cancel" @click.stop="stopRecord(true)">{{$L(recordFormatTip)}}</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
<!-- 录音转文字 -->
|
||||||
|
<transition name="fade">
|
||||||
|
<div
|
||||||
|
v-if="recordConvertIng"
|
||||||
|
v-transfer-dom
|
||||||
|
:data-transfer="true"
|
||||||
|
class="chat-input-convert-transfer"
|
||||||
|
:style="recordTransferStyle">
|
||||||
|
<div class="convert-box">
|
||||||
|
<div class="convert-content">
|
||||||
|
<Input
|
||||||
|
type="textarea"
|
||||||
|
class="convert-result"
|
||||||
|
v-model="recordConvertResult"
|
||||||
|
:rows="1"
|
||||||
|
:autosize="{minRows: 1, maxRows: 5}"
|
||||||
|
:placeholder="recordConvertStatus === 0 ? '...' : ''"/>
|
||||||
|
</div>
|
||||||
|
<ul class="convert-footer">
|
||||||
|
<li @click="recordConvertIng=false">
|
||||||
|
<i class="taskfont"></i>
|
||||||
|
<span>{{$L('取消')}}</span>
|
||||||
|
</li>
|
||||||
|
<li @click="convertSend('voice')">
|
||||||
|
<i class="taskfont voice"></i>
|
||||||
|
<span>{{$L('发送原语音')}}</span>
|
||||||
|
</li>
|
||||||
|
<li @click="convertSend('result')">
|
||||||
|
<i v-if="recordConvertStatus === 0" class="send"><Loading/></i>
|
||||||
|
<i v-else-if="recordConvertStatus === 2" class="taskfont error"></i>
|
||||||
|
<i v-else class="taskfont send"></i>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
v-model="fullInput"
|
v-model="fullInput"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
@ -335,6 +373,9 @@ export default {
|
|||||||
recordInter: null,
|
recordInter: null,
|
||||||
recordState: "stop",
|
recordState: "stop",
|
||||||
recordDuration: 0,
|
recordDuration: 0,
|
||||||
|
recordConvertIng: false,
|
||||||
|
recordConvertStatus: 0, // 0: 转换中 1: 转换成功 2: 转换失败
|
||||||
|
recordConvertResult: '',
|
||||||
|
|
||||||
touchStart: {},
|
touchStart: {},
|
||||||
touchFocus: false,
|
touchFocus: false,
|
||||||
@ -453,6 +494,9 @@ export default {
|
|||||||
if (this.recordRec) {
|
if (this.recordRec) {
|
||||||
this.recordRec = null
|
this.recordRec = null
|
||||||
}
|
}
|
||||||
|
if (this.recordConvertIng) {
|
||||||
|
this.recordConvertIng = false
|
||||||
|
}
|
||||||
if (this.recordInter) {
|
if (this.recordInter) {
|
||||||
clearInterval(this.recordInter)
|
clearInterval(this.recordInter)
|
||||||
}
|
}
|
||||||
@ -563,6 +607,24 @@ export default {
|
|||||||
return `${minute}:${seconds}″${millisecond}`
|
return `${minute}:${seconds}″${millisecond}`
|
||||||
},
|
},
|
||||||
|
|
||||||
|
recordClassName({touchLimitX, touchLimitY}) {
|
||||||
|
if (touchLimitY) {
|
||||||
|
return 'cancel'
|
||||||
|
} else if (touchLimitX) {
|
||||||
|
return 'convert'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
recordFormatTip({touchLimitX, touchLimitY}) {
|
||||||
|
if (touchLimitY) {
|
||||||
|
return '松开取消'
|
||||||
|
} else if (touchLimitX) {
|
||||||
|
return '转文字'
|
||||||
|
}
|
||||||
|
return '向上滑动取消'
|
||||||
|
},
|
||||||
|
|
||||||
dialogData() {
|
dialogData() {
|
||||||
return this.dialogId > 0 ? (this.cacheDialogs.find(({id}) => id == this.dialogId) || {}) : {};
|
return this.dialogId > 0 ? (this.cacheDialogs.find(({id}) => id == this.dialogId) || {}) : {};
|
||||||
},
|
},
|
||||||
@ -1140,7 +1202,7 @@ export default {
|
|||||||
if (this.showMenu) {
|
if (this.showMenu) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.stopRecord(this.touchLimitY)) {
|
if (this.stopRecord(this.touchLimitY, this.touchLimitX)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.touchLimitY || this.touchLimitX) {
|
if (this.touchLimitY || this.touchLimitX) {
|
||||||
@ -1222,7 +1284,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stopRecord(isCancel) {
|
stopRecord(isCancel, isConvert = false) {
|
||||||
switch (this.recordState) {
|
switch (this.recordState) {
|
||||||
case "ing":
|
case "ing":
|
||||||
this.recordState = "stop";
|
this.recordState = "stop";
|
||||||
@ -1235,7 +1297,12 @@ export default {
|
|||||||
$A.messageWarning("说话时间太短") // 小于 600ms 不发送
|
$A.messageWarning("说话时间太短") // 小于 600ms 不发送
|
||||||
} else {
|
} else {
|
||||||
this.recordBlob = blob;
|
this.recordBlob = blob;
|
||||||
this.uploadRecord(duration);
|
this.recordDuration = duration;
|
||||||
|
if (isConvert === true) {
|
||||||
|
this.convertRecord();
|
||||||
|
} else {
|
||||||
|
this.uploadRecord();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, (msg) => {
|
}, (msg) => {
|
||||||
this.recordRec.close();
|
this.recordRec.close();
|
||||||
@ -1270,7 +1337,54 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadRecord(duration) {
|
convertRecord() {
|
||||||
|
if (this.recordBlob === null) {
|
||||||
|
this.recordConvertIng = false
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.recordConvertResult = ''
|
||||||
|
this.recordConvertStatus = 0
|
||||||
|
this.recordConvertIng = true
|
||||||
|
//
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'dialog/msg/convertrecord',
|
||||||
|
data: {
|
||||||
|
dialog_id: this.dialogId,
|
||||||
|
base64: reader.result,
|
||||||
|
duration: this.recordDuration,
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.recordConvertStatus = data ? 1 : 2
|
||||||
|
this.recordConvertResult = data || this.$L('转文字失败')
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
this.recordConvertStatus = 2
|
||||||
|
this.recordConvertResult = msg
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(this.recordBlob);
|
||||||
|
},
|
||||||
|
|
||||||
|
convertSend(type) {
|
||||||
|
if (!this.recordConvertIng) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === 'voice') {
|
||||||
|
this.uploadRecord();
|
||||||
|
this.recordConvertIng = false
|
||||||
|
} else {
|
||||||
|
if (this.recordConvertStatus === 1) {
|
||||||
|
this.$emit('on-send', this.recordConvertResult)
|
||||||
|
this.recordConvertIng = false
|
||||||
|
} else if (this.recordConvertStatus === 2) {
|
||||||
|
this.convertRecord()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadRecord() {
|
||||||
if (this.recordBlob === null) {
|
if (this.recordBlob === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1279,7 +1393,7 @@ export default {
|
|||||||
this.$emit('on-record', {
|
this.$emit('on-record', {
|
||||||
type: this.recordBlob.type,
|
type: this.recordBlob.type,
|
||||||
base64: reader.result,
|
base64: reader.result,
|
||||||
duration,
|
duration: this.recordDuration,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(this.recordBlob);
|
reader.readAsDataURL(this.recordBlob);
|
||||||
|
|||||||
1
resources/assets/sass/dark.scss
vendored
1
resources/assets/sass/dark.scss
vendored
@ -571,6 +571,7 @@ body.dark-mode-reverse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-record-transfer {
|
.chat-input-record-transfer {
|
||||||
|
&.convert,
|
||||||
&.cancel {
|
&.cancel {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -723,12 +723,122 @@
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
&.convert {
|
||||||
|
background-color: #2db7f5;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
&.cancel {
|
&.cancel {
|
||||||
background-color: #ff6565;
|
background-color: #ff6565;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-input-convert-transfer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
.convert-box {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
height: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.convert-content {
|
||||||
|
position: relative;
|
||||||
|
background-color: $primary-color;
|
||||||
|
color: #000000;
|
||||||
|
width: 88%;
|
||||||
|
padding: 18px;
|
||||||
|
border-radius: 14px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15px;
|
||||||
|
right: 12%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border-width: 8px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: $primary-color transparent transparent transparent;
|
||||||
|
}
|
||||||
|
.convert-result {
|
||||||
|
.ivu-input {
|
||||||
|
font-size: 18px;
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #000000;
|
||||||
|
border-radius: 0;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.convert-footer {
|
||||||
|
width: 88%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 64px;
|
||||||
|
> li {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
> i {
|
||||||
|
font-size: 18px;
|
||||||
|
&.voice {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
&.send,
|
||||||
|
&.error {
|
||||||
|
font-size: 22px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #0a7600;
|
||||||
|
background: #000000;
|
||||||
|
font-weight: 600;
|
||||||
|
.common-loading {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
color: #ff0000;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> span {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-input-full-input {
|
.chat-input-full-input {
|
||||||
.ivu-modal {
|
.ivu-modal {
|
||||||
.ivu-modal-content {
|
.ivu-modal-content {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user