mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 02:12:53 +00:00
perf: 语音消息转文字
This commit is contained in:
parent
bf46a00937
commit
426fa63288
@ -11,6 +11,7 @@ use Carbon\Carbon;
|
||||
use App\Models\File;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\TimeRange;
|
||||
use App\Models\FileContent;
|
||||
use App\Models\AbstractModel;
|
||||
@ -1476,6 +1477,50 @@ class DialogController extends AbstractController
|
||||
return Base::retSuccess("success");
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/voice2text 29. 语音消息转文字
|
||||
*
|
||||
* @apiDescription 将语音消息转文字,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName msg__voice2text
|
||||
*
|
||||
* @apiParam {Number} msg_id 消息ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function msg__voice2text()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$msg_id = intval(Request::input("msg_id"));
|
||||
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
|
||||
if (empty($msg)) {
|
||||
return Base::retError("消息不存在或已被删除");
|
||||
}
|
||||
if ($msg->type !== 'record') {
|
||||
return Base::retError("仅支持语音消息");
|
||||
}
|
||||
if ($msg['msg']['text']) {
|
||||
return Base::retSuccess("success", $msg);
|
||||
}
|
||||
WebSocketDialog::checkDialog($msg->dialog_id);
|
||||
//
|
||||
$msgData = Base::json2array($msg->getRawOriginal('msg'));
|
||||
$res = Extranet::openAItranscriptions(public_path($msgData['path']));
|
||||
if (Base::isError($res)) {
|
||||
return $res;
|
||||
}
|
||||
//
|
||||
$msg->updateInstance([
|
||||
'msg' => array_merge($msgData, ['text' => $res['data']]),
|
||||
]);
|
||||
$msg->save();
|
||||
return Base::retSuccess("success", $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/msg/mark 30. 消息标记操作
|
||||
*
|
||||
|
||||
@ -40,7 +40,7 @@ class SystemController extends AbstractController
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - all: 获取所有(需要管理员权限)
|
||||
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'temp_account_alias', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'e2e_message', 'auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'all_group_mute', 'all_group_autoin', 'user_private_chat_mute', 'user_group_chat_mute', 'image_compress', 'image_save_local', 'start_home'])
|
||||
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'temp_account_alias', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'voice2text', 'e2e_message', 'auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'all_group_mute', 'all_group_autoin', 'user_private_chat_mute', 'user_group_chat_mute', 'image_compress', 'image_save_local', 'start_home'])
|
||||
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@ -66,6 +66,7 @@ class SystemController extends AbstractController
|
||||
'project_invite',
|
||||
'chat_information',
|
||||
'anon_message',
|
||||
'voice2text',
|
||||
'e2e_message',
|
||||
'auto_archived',
|
||||
'archived_day',
|
||||
@ -93,6 +94,9 @@ class SystemController extends AbstractController
|
||||
return Base::retError('自动归档时间不可大于100天!');
|
||||
}
|
||||
}
|
||||
if ($all['voice2text'] == 'open' && empty(Base::settingFind('aibotSetting', 'openai_key'))) {
|
||||
return Base::retError('开启语音转文字功能需要在应用中开启 ChatGPT AI 机器人。');
|
||||
}
|
||||
$setting = Base::setting('system', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('system');
|
||||
@ -113,6 +117,7 @@ class SystemController extends AbstractController
|
||||
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
|
||||
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
|
||||
$setting['anon_message'] = $setting['anon_message'] ?: 'open';
|
||||
$setting['voice2text'] = $setting['voice2text'] ?: 'close';
|
||||
$setting['e2e_message'] = $setting['e2e_message'] ?: 'close';
|
||||
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
|
||||
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
|
||||
|
||||
@ -12,6 +12,49 @@ use Illuminate\Support\Facades\Config;
|
||||
*/
|
||||
class Extranet
|
||||
{
|
||||
/**
|
||||
* 通过 openAI 语音转文字
|
||||
* @param string $filePath
|
||||
* @return array
|
||||
*/
|
||||
public static function openAItranscriptions($filePath)
|
||||
{
|
||||
if (!file_exists($filePath)) {
|
||||
return Base::retError("语音文件不存在");
|
||||
}
|
||||
$systemSetting = Base::setting('system');
|
||||
$aibotSetting = Base::setting('aibotSetting');
|
||||
if ($systemSetting['voice2text'] !== 'open' || empty($aibotSetting['openai_key'])) {
|
||||
return Base::retError("语音转文字功能未开启");
|
||||
}
|
||||
//
|
||||
$extra = [
|
||||
'Content-Type' => 'multipart/form-data',
|
||||
'Authorization' => 'Bearer ' . $aibotSetting['openai_key'],
|
||||
];
|
||||
if ($aibotSetting['openai_agency']) {
|
||||
$extra['CURLOPT_PROXY'] = $aibotSetting['openai_agency'];
|
||||
if (str_contains($aibotSetting['openai_agency'], 'socks')) {
|
||||
$extra['CURLOPT_PROXYTYPE'] = CURLPROXY_SOCKS5;
|
||||
} else {
|
||||
$extra['CURLOPT_PROXYTYPE'] = CURLPROXY_HTTP;
|
||||
}
|
||||
}
|
||||
$res = Ihttp::ihttp_request('https://api.openai.com/v1/audio/transcriptions', [
|
||||
'file' => new \CURLFile($filePath),
|
||||
'model' => 'whisper-1'
|
||||
], $extra, 15);
|
||||
if (Base::isError($res)) {
|
||||
return Base::retError("语音转文字失败", $res);
|
||||
}
|
||||
$resData = Base::json2array($res['data']);
|
||||
if (empty($resData['text'])) {
|
||||
return Base::retError("语音转文字失败", $resData);
|
||||
}
|
||||
//
|
||||
return Base::retSuccess("success", $resData['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取IP地址经纬度
|
||||
* @param string $ip
|
||||
|
||||
@ -35,8 +35,12 @@ class Ihttp
|
||||
if($post) {
|
||||
if (is_array($post)) {
|
||||
$filepost = false;
|
||||
foreach ($post as $name => $value) {
|
||||
if (is_string($value) && substr($value, 0, 1) == '@') {
|
||||
foreach ($post as $value) {
|
||||
if (is_string($value) && str_starts_with($value, '@')) {
|
||||
$filepost = true;
|
||||
break;
|
||||
}
|
||||
if ($value instanceof \CURLFile) {
|
||||
$filepost = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -497,3 +497,10 @@ Api接口文档
|
||||
|
||||
请选择举报类型
|
||||
请填写举报原因
|
||||
|
||||
|
||||
开启语音转文字功能需要在应用中开启 ChatGPT AI 机器人。
|
||||
语音转文字功能未开启
|
||||
语音文件不存在
|
||||
语音转文字失败
|
||||
仅支持语音消息
|
||||
|
||||
@ -1594,3 +1594,9 @@ License Key
|
||||
举报原因
|
||||
举报图
|
||||
举报投诉
|
||||
|
||||
转文字
|
||||
语音转文字
|
||||
长按语音消息可转换成文字。
|
||||
需要在应用中开启 ChatGPT AI 机器人
|
||||
关闭语音转文字功能。
|
||||
|
||||
@ -52,6 +52,9 @@
|
||||
<div class="record-time">{{recordDuration(msgData.msg.duration)}}</div>
|
||||
<div class="record-icon taskfont"></div>
|
||||
</div>
|
||||
<div v-if="msgData.msg.text" class="dialog-record-text">
|
||||
{{msgData.msg.text}}
|
||||
</div>
|
||||
</div>
|
||||
<!--会议-->
|
||||
<div v-else-if="msgData.type === 'meeting'" class="content-meeting no-dark-content">
|
||||
|
||||
@ -297,6 +297,10 @@
|
||||
<i class="taskfont"></i>
|
||||
<span>{{ $L('编辑') }}</span>
|
||||
</li>
|
||||
<li v-if="actionPermission(operateItem, 'voice2text')" @click="onOperate('voice2text')">
|
||||
<i class="taskfont"></i>
|
||||
<span>{{ $L('转文字') }}</span>
|
||||
</li>
|
||||
<li v-for="item in operateCopys" @click="onOperate('copy', item)">
|
||||
<i class="taskfont" v-html="item.icon"></i>
|
||||
<span>{{ $L(item.label) }}</span>
|
||||
@ -2891,6 +2895,10 @@ export default {
|
||||
this.onUpdate()
|
||||
break;
|
||||
|
||||
case "voice2text":
|
||||
this.onVoice2text()
|
||||
break;
|
||||
|
||||
case "copy":
|
||||
this.onCopy(value)
|
||||
break;
|
||||
@ -3005,6 +3013,26 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onVoice2text() {
|
||||
if (!this.actionPermission(this.operateItem, 'voice2text')) {
|
||||
return;
|
||||
}
|
||||
const {id: msg_id} = this.operateItem
|
||||
this.$store.dispatch("setLoad", `msg-${msg_id}`)
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/msg/voice2text',
|
||||
data: {
|
||||
msg_id
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.$store.dispatch("saveDialogMsg", data);
|
||||
}).catch(({msg}) => {
|
||||
$A.messageError(msg);
|
||||
}).finally(_ => {
|
||||
this.$store.dispatch("cancelLoad", `msg-${msg_id}`)
|
||||
});
|
||||
},
|
||||
|
||||
onCopy(data) {
|
||||
if (!$A.isJson(data)) {
|
||||
return
|
||||
@ -3552,6 +3580,16 @@ export default {
|
||||
return typeof item.msg.approve_type === 'undefined' // 审批消息不支持新建任务
|
||||
}
|
||||
return false
|
||||
} else if (permission === 'voice2text') {
|
||||
if (item.type !== 'record') {
|
||||
return false;
|
||||
}
|
||||
if (item.msg.text) {
|
||||
return false;
|
||||
}
|
||||
if (this.isLoad(`msg-${item.id}`)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true // 返回 true 允许操作
|
||||
},
|
||||
|
||||
@ -124,14 +124,6 @@
|
||||
<div class="block-setting-box">
|
||||
<h3>{{ $L('消息相关') }}</h3>
|
||||
<div class="form-box">
|
||||
<FormItem :label="$L('全员群组禁言')" prop="allGroupMute">
|
||||
<RadioGroup v-model="formDatum.all_group_mute">
|
||||
<Radio label="open">{{$L('开放')}}</Radio>
|
||||
<Radio label="close">{{$L('禁言')}}</Radio>
|
||||
</RadioGroup>
|
||||
<div v-if="formDatum.all_group_mute == 'open'" class="form-tip">{{$L('开放:所有人都可以在全员群组发言。')}}</div>
|
||||
<div v-else-if="formDatum.all_group_mute == 'close'" class="form-tip">{{$L('禁言:除管理员外所有人都禁止在全员群组发言。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('自动进入全员群')" prop="allGroupAutoin">
|
||||
<RadioGroup v-model="formDatum.all_group_autoin">
|
||||
<Radio label="yes">{{$L('自动')}}</Radio>
|
||||
@ -140,6 +132,14 @@
|
||||
<div v-if="formDatum.all_group_autoin == 'yes'" class="form-tip">{{$L('自动:注册成功后自动进入全员群。')}}</div>
|
||||
<div v-else-if="formDatum.all_group_autoin == 'no'" class="form-tip">{{$L('关闭:其他成员通过@邀请进入。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('全员群组禁言')" prop="allGroupMute">
|
||||
<RadioGroup v-model="formDatum.all_group_mute">
|
||||
<Radio label="open">{{$L('开放')}}</Radio>
|
||||
<Radio label="close">{{$L('禁言')}}</Radio>
|
||||
</RadioGroup>
|
||||
<div v-if="formDatum.all_group_mute == 'open'" class="form-tip">{{$L('开放:所有人都可以在全员群组发言。')}}</div>
|
||||
<div v-else-if="formDatum.all_group_mute == 'close'" class="form-tip">{{$L('禁言:除管理员外所有人都禁止在全员群组发言。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('私聊禁言')" prop="userPrivateChatMute">
|
||||
<RadioGroup v-model="formDatum.user_private_chat_mute">
|
||||
<Radio label="open">{{$L('开放')}}</Radio>
|
||||
@ -177,6 +177,14 @@
|
||||
<div v-if="formDatum.anon_message == 'open'" class="form-tip">{{$L('允许匿名发送消息给其他成员。')}}</div>
|
||||
<div v-else class="form-tip">{{$L('禁止匿名发送消息。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('语音转文字')" prop="voice2text">
|
||||
<RadioGroup v-model="formDatum.voice2text">
|
||||
<Radio label="open">{{$L('开启')}}</Radio>
|
||||
<Radio label="close">{{$L('关闭')}}</Radio>
|
||||
</RadioGroup>
|
||||
<div v-if="formDatum.voice2text == 'open'" class="form-tip">{{$L('长按语音消息可转换成文字。')}} ({{$L('需要在应用中开启 ChatGPT AI 机器人')}})</div>
|
||||
<div v-else class="form-tip">{{$L('关闭语音转文字功能。')}}</div>
|
||||
</FormItem>
|
||||
<FormItem :label="$L('端到端加密')" prop="e2eMessage">
|
||||
<RadioGroup v-model="formDatum.e2e_message">
|
||||
<Radio label="open">{{$L('开启')}}</Radio>
|
||||
|
||||
@ -654,7 +654,8 @@
|
||||
margin: 0 0 0 8px;
|
||||
position: relative;
|
||||
|
||||
&.text {
|
||||
&.text,
|
||||
&.record {
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
@ -1067,7 +1068,10 @@
|
||||
|
||||
.content-record {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
color: $primary-title-color;
|
||||
max-width: 100%;
|
||||
|
||||
.dialog-record {
|
||||
display: flex;
|
||||
@ -1075,6 +1079,7 @@
|
||||
justify-content: flex-end;
|
||||
align-content: center;
|
||||
line-height: 24px;
|
||||
max-width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
.record-time {
|
||||
@ -1112,6 +1117,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-record-text {
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-meeting {
|
||||
@ -1607,6 +1628,7 @@
|
||||
}
|
||||
|
||||
.content-record {
|
||||
align-items: flex-end;
|
||||
color: #ffffff;
|
||||
|
||||
.dialog-record {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user