package echomeet import ( "yunyan/comm" "yunyan/pb" "encoding/json" "time" ) // @Summary 启动会议转写任务 // @Description 扣除积分并启动指定会议记录的转写/翻译任务,根据源语言自动路由到字节或阿里云 // @Tags Echomeet // @Accept json // @Produce json // @Security BearerAuth // @Param user body pb.EchomeetStartTaskReq true "请求数据" // @Success 200 {object} comm.HttpResult{data=pb.EchomeetStartTaskResp} "响应数据" // @Router /api/home/echomeet_starttask [post] func (this *apiComp) StartTask(session comm.IUserSession, req *pb.EchomeetStartTaskReq) (resp *pb.EchomeetStartTaskResp, errdata *pb.ErrorData) { var ( user *pb.DBUser model *pb.DBEchoMeetRecord err error ) user, err = this.module.model.getuser(session.GetUserId()) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } model, err = this.module.model.getrecord(req.Id) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if model.State > pb.DBEchoMeetRecordState_Unknow && model.State < pb.DBEchoMeetRecordState_Completed { errdata = &pb.ErrorData{ Code: pb.ErrorCode_TaskStateError, Message: "任务状态错误", } return } if user.Meetintegral < int64(model.Seconds) { errdata = &pb.ErrorData{ Code: pb.ErrorCode_MeetIntegralNotEnough, Message: "积分不足", } return } user.Meetintegral -= int64(model.Seconds) err = this.module.model.updateuser(user) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } // 会议时长消耗埋点 if this.module.analyze != nil { this.module.analyze.Report(&comm.StatEvent{ Type: comm.StatEventMeeting, ProductId: user.Lastbindproductid, Uid: user.Uid, Second: int64(model.Seconds), }) } // 同步更新累计统计(排行榜依赖此表) if statistics, sErr := this.module.model.getStatistics(session.GetUserId()); sErr == nil { statistics.Meetnum += 1 statistics.Meettime += int64(model.Seconds) if uErr := this.module.model.updateStatistics(statistics); uErr != nil { this.module.Warnf("StartTask id:%d update userstatistics failed: %v", model.Id, uErr) } } // 写消费日志(供历史重建扫描) _ = this.module.model.addUseLog(&pb.DBUserUseLog{ Uid: session.GetUserId(), Logtype: pb.UserLogType_UserConsume, Ts: time.Now().Unix(), Addmeetsecond: -1 * int64(model.Seconds), }) tmpl, err := this.module.cache.ResolveTemplate(req.Tid, req.Templateid, req.Tolanguage) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_TemplateNotFound, Message: "模板不存在或不支持该语言", } this.module.Errorf("StartTask id:%d req:%+v 解析模板失败 err:%v", model.Id, req, err) return } model.State = pb.DBEchoMeetRecordState_AwaitTranscribing model.Starttime = time.Now().Unix() model.Processduration = 0 model.Templateid = tmpl.Id model.Isdistinguishspeaker = req.Isdistinguishspeaker model.Formlanguage = req.Formlanguage model.Tolanguage = req.Tolanguage // 三段选路(后台编排,fail-closed:未配置直接报错提示去后台配置): // - 识别/翻译:按后台优先级 + 语言支持过滤,客户端不可指定; // - 总结:后台默认模型;开关允许时客户端可用 summary_svc_id 指定。 llmSvcId, _, err := this.module.providers.PickLLM(req.SummarySvcId) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: err.Error()} return } model.LlmSvcId = llmSvcId // 翻译:源=目标语言时无需翻译,选路失败不阻断(后续改语言重译时再补选)。 if mtSvcId, _, merr := this.module.providers.PickMT(model.Formlanguage); merr == nil { model.MtSvcId = mtSvcId } else if model.Formlanguage != model.Tolanguage { errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: merr.Error()} return } if model.Rtype == "VOICETRANSLAT" { // 语音翻译:已有转写文本,只走翻译+总结,无需识别选路。 this.module.model.saverecord(model) this.module.Infof("StartTask id:%d → 语音翻译 mt:%s llm:%s", model.Id, model.MtSvcId, model.LlmSvcId) var contexts []*pb.ContextStruct if err = json.Unmarshal([]byte(model.Original), &contexts); err != nil { this.module.Errorf("Summary id:%d 解析 Original 失败 err:%v", model.Id, err) errdata = &pb.ErrorData{ Code: pb.ErrorCode_SystemError, Message: "json.Unmarshal Original 失败", } return } else { this.module.tasks.TranslateProcess(model, contexts) _ = this.module.tasks.SubmitAITask(model) } return } if model.Audiourl == "" { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AudioUrlEmpty, Message: "音频URL为空", } return } asrSvcId, _, _, err := this.module.providers.PickASR(model.Formlanguage) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: err.Error()} return } model.AsrSvcId = asrSvcId this.module.model.saverecord(model) this.module.Infof("StartTask id:%d uid:%s formlang:%s tolang:%s seconds:%d size:%d distinguishSpeaker:%v asr:%s mt:%s llm:%s", model.Id, model.Uid, model.Formlanguage, model.Tolanguage, model.Seconds, model.Size, model.Isdistinguishspeaker, model.AsrSvcId, model.MtSvcId, model.LlmSvcId) // 字节短音频走专用限流队列(flash 同步通道);其余统一走通用异步提交。 if this.module.providers.ASRProvider(asrSvcId) == "bytedance" && model.Seconds <= byteDanceFlashMaxSeconds && model.Size <= byteDanceFlashMaxBytes { _ = this.module.tasks.SubmitShortAudioTask(model) } else { _ = this.module.tasks.SubmitTranscribeTask(model) } resp = &pb.EchomeetStartTaskResp{} return }