diff --git a/apps/proto/echomeet/echomeet_db.proto b/apps/proto/echomeet/echomeet_db.proto index 8e72cbb2..86286294 100644 --- a/apps/proto/echomeet/echomeet_db.proto +++ b/apps/proto/echomeet/echomeet_db.proto @@ -1,15 +1,8 @@ syntax = "proto3"; option go_package = ".;pb"; -// 转写服务类型 -enum TranscribeService { - TranscribeService_Unknown = 0; // 未知(兼容旧数据) - TranscribeService_ByteDance = 1; // 字节跳动 - TranscribeService_Ali = 2; // 阿里云 DashScope - TranscribeService_Auto = 3; // 自动按语言路由 - TranscribeService_Google = 4; // Google Cloud - TranscribeService_Microsoft = 5; // Azure -} +// 注:原「转写服务类型」枚举 TranscribeService 已删除——服务商不再是固定四家, +// 改为后台编排(echomeet_orch)+ 服务池(svc_config)动态配置,记录里存三段各自的服务id。 enum DBEchoMeetRecordState{ Unknow = 0; //@go_tags(`gorm:"DBEchoMeetRecordState_Unknow"`) 未知状态 @@ -66,7 +59,9 @@ message DBEchoMeetRecord { string translate = 21; //@go_tags(`gorm:"translate" json:"translate"`) 翻译 string overview = 22; //@go_tags(`gorm:"overview" json:"overview"`) 短总结 string summary = 23; //@go_tags(`gorm:"summary" json:"summary"`) 总结 - TranscribeService service_type = 24; //@go_tags(`gorm:"service_type" json:"service_type"`) 转写服务类型 + string asr_svc_id = 30; //@go_tags(`gorm:"asr_svc_id" json:"asr_svc_id"`) 识别服务id(后台编排选路结果) + string mt_svc_id = 31; //@go_tags(`gorm:"mt_svc_id" json:"mt_svc_id"`) 翻译服务id(后台编排选路结果) + string llm_svc_id = 32; //@go_tags(`gorm:"llm_svc_id" json:"llm_svc_id"`) 总结大模型服务id(默认或客户端指定) int64 starttime = 25; //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒 int64 processduration = 26; //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒 int64 lastquerytime = 27; //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率 diff --git a/apps/proto/echomeet/echomeet_msg.proto b/apps/proto/echomeet/echomeet_msg.proto index 220c396d..9e962557 100644 --- a/apps/proto/echomeet/echomeet_msg.proto +++ b/apps/proto/echomeet/echomeet_msg.proto @@ -112,7 +112,7 @@ message EchomeetStartTaskReq{ bool isdistinguishspeaker = 4; //@go_tags(`json:"isdistinguishspeaker"`) //是否区分说话人 uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模版索引id string tid = 6; //@go_tags(`json:"tid"`) //会议记录模板tid - TranscribeService service_type = 7; //@go_tags(`json:"service_type"`) //转写服务类型,留空默认 Auto + string summary_svc_id = 8; //@go_tags(`json:"summary_svc_id"`) //客户端指定的总结大模型服务id(须在可选列表内且后台开关允许,否则用默认);识别/翻译由后台编排选路,客户端不可指定 } message EchomeetStartTaskResp{ @@ -140,11 +140,27 @@ message EchomeetSummaryReq { uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模板id string tid = 6; //@go_tags(`json:"tid"`) //会议记录模板tid string tolanguage = 7; //@go_tags(`json:"tolanguage"`) 语言 - TranscribeService service_type = 8; //@go_tags(`json:"service_type"`) //转写服务类型(用于翻译/总结路由),留空沿用记录已有 + string summary_svc_id = 9; //@go_tags(`json:"summary_svc_id"`) //客户端指定的总结大模型服务id,留空沿用记录已有/后台默认 } message EchomeetSummaryResp{ - + +} + +//获取客户端可选的总结大模型服务列表(识别/翻译由后台编排选路,不下发) +message EchomeetGetSummaryServicesReq{ +} + +//可选总结服务项 +message EchomeetSummaryServiceItem{ + string svc_id = 1; //@go_tags(`json:"svc_id"`) //服务id(starttask/summary 的 summary_svc_id 传它) + string name = 2; //@go_tags(`json:"name"`) //服务显示名 + bool is_default = 3; //@go_tags(`json:"is_default"`) //是否后台默认模型 +} + +message EchomeetGetSummaryServicesResp{ + bool client_choice = 1; //@go_tags(`json:"client_choice"`) //后台是否允许客户端指定总结模型;false 时 services 仅含默认项、传 summary_svc_id 会被忽略 + repeated EchomeetSummaryServiceItem services = 2; //@go_tags(`json:"services"`) //可选服务列表 } //已阅会议记录 diff --git a/apps/services/comm/region.go b/apps/services/comm/region.go index 452844a3..6526d965 100644 --- a/apps/services/comm/region.go +++ b/apps/services/comm/region.go @@ -27,3 +27,14 @@ func RegionCode(region int32) string { } return "" } + +// RegionFromCode 把英文短代码转回 pb.Region 枚举值(int32);空串/未知返回 0。 +// 部署 env 注入的 APP_REGION 是短代码(cn/us/...),编排等表按 int32 分层,需要此反向映射。 +func RegionFromCode(code string) int32 { + for r, c := range regionCodes { + if c == code && c != "" { + return r + } + } + return 0 +} diff --git a/apps/services/comm/svcpool.go b/apps/services/comm/svcpool.go new file mode 100644 index 00000000..32115e98 --- /dev/null +++ b/apps/services/comm/svcpool.go @@ -0,0 +1,85 @@ +package comm + +// 第三方服务池 + 会议记录服务编排 的共享数据结构。 +// +// 这些表都存 console 共享库(postgres):console 后台读写(modules/console 里用 type alias 引用), +// 业务服务(如 home 的 echomeet 模块)运行时直读消费。加密字段与 console 用同一把 +// ${FIELD_ENCRYPT_KEY}(comm.Encrypt/Decrypt,AES-256-GCM),两侧必须配同一个 key。 +// +// 嵌套字段([]SvcField / map[string]string)借助 gorm `serializer:json` 存 jsonb 列。 + +// SvcField 第三方服务的自定义字段项(内嵌于 ThirdSvcConfig.fields jsonb 列,也复用于模板/区域专属字段)。 +// 加密字段:DefValue 存 AES-GCM 密文;console API 对加密字段只返回 "[encrypted]" 占位。 +type SvcField struct { + Key string `json:"key"` // 字段名,同服务内唯一,如 secret_key + Description string `json:"description"` // 字段用途描述,前端展示 + Type string `json:"type"` // 字段类型:空/text 普通文本;langs 支持语言多选(值=逗号分隔 BCP-47);lang 单选默认语言 + Encrypted bool `json:"encrypted"` // 是否加密存储 + DefValue string `json:"def_value"` // 默认值(全区域共用;加密字段存 AES-GCM 密文) + Sort int32 `json:"sort"` // 展示排序,越小越靠前 +} + +// ThirdSvcConfig 第三方服务配置(表 TableSvcConfig,按应用分离)。每行 = 某应用作用域下的一个第三方服务。 +// AppName=''为全局默认、'X'为应用X的覆盖;解析时应用有则用应用的、无则回退全局。 +// (app_name,id) 唯一;categories 存逗号分隔的类别枚举值。凭据全部装在 Fields。 +type ThirdSvcConfig struct { + RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` + AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_svc_config,priority:1" json:"app_name"` // ''=全局默认 + Id string `gorm:"column:id;size:64;uniqueIndex:uidx_svc_config,priority:2" json:"id"` + Name string `gorm:"column:name;size:64" json:"name"` + Provider string `gorm:"column:provider;size:64" json:"provider"` + Categories string `gorm:"column:categories;size:64" json:"categories"` + Description string `gorm:"column:description" json:"description"` + Enable bool `gorm:"column:enable" json:"enable"` + Fields []SvcField `gorm:"column:fields;type:jsonb;serializer:json" json:"fields"` + Createtime int64 `gorm:"column:createtime" json:"createtime"` + Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` +} + +// SvcRegionOverride 第三方服务区域字段覆盖(表 TableSvcRegionOverride)。 +// (app_name,svc_id,region) 唯一;svc_id 引用同作用域下的 ThirdSvcConfig.Id。 +type SvcRegionOverride struct { + Id uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_svc_region,priority:1" json:"app_name"` // ''=全局默认 + SvcId string `gorm:"column:svc_id;size:64;uniqueIndex:uidx_svc_region,priority:2" json:"svc_id"` + Region int32 `gorm:"column:region;uniqueIndex:uidx_svc_region,priority:3" json:"region"` + Overrides map[string]string `gorm:"column:overrides;type:jsonb;serializer:json" json:"overrides"` // 默认字段的值覆盖 + ExtraFields []SvcField `gorm:"column:extra_fields;type:jsonb;serializer:json" json:"extra_fields"` // 区域专属新增字段 + DisabledKeys []string `gorm:"column:disabled_keys;type:jsonb;serializer:json" json:"disabled_keys"` // 区域不适用的默认字段 key + Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` +} + +// 会议记录编排类别(三段)。 +const ( + EchoKindASR int32 = 1 // 识别(录音文件转写) + EchoKindMT int32 = 2 // 翻译 + EchoKindLLM int32 = 3 // 总结(大模型) +) + +// EchoOrchestration 会议记录服务编排(表 TableEchomeetOrch)。一行 = 某作用域(app_name,region) +// 下某类别选中的一个服务引用 + 排序/标记。不存凭据,svc_id 引用 ThirdSvcConfig.Id。 +// 作用域解析顺序:(app,region) → (app,0) → ('',region) → ('',0) 全局默认兜底,越具体越优先。 +type EchoOrchestration struct { + RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` + AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_echo_orch,priority:1" json:"app_name"` // ''=全局默认兜底 + Region int32 `gorm:"column:region;uniqueIndex:uidx_echo_orch,priority:2" json:"region"` // 0=全区域兜底,>0=特定区域 + Kind int32 `gorm:"column:kind;uniqueIndex:uidx_echo_orch,priority:3" json:"kind"` // EchoKindASR/MT/LLM + SvcId string `gorm:"column:svc_id;size:64;uniqueIndex:uidx_echo_orch,priority:4" json:"svc_id"` // 引用 svc_config 的服务 id + Priority int32 `gorm:"column:priority" json:"priority"` // 同类内升序,小=优先 + IsDefault bool `gorm:"column:is_default" json:"is_default"` // 总结用:默认模型,同作用域内至多一条 + ClientVisible bool `gorm:"column:client_visible" json:"client_visible"` // 总结用:是否允许客户端选择 + CostHint float64 `gorm:"column:cost_hint" json:"cost_hint"` // 参考单价,仅排序/展示 + Enabled bool `gorm:"column:enabled" json:"enabled"` + Createtime int64 `gorm:"column:createtime" json:"createtime"` + Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` +} + +// EchoOrchSetting 会议记录编排的作用域级开关(表 TableEchomeetOrchSetting)。每 (app_name, region) 一行。 +type EchoOrchSetting struct { + RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` + AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_echo_orch_set,priority:1" json:"app_name"` + Region int32 `gorm:"column:region;uniqueIndex:uidx_echo_orch_set,priority:2" json:"region"` + // LLMClientChoice 是否允许客户端指定总结模型。false=强制默认模型;true=客户端可在 ClientVisible 且 Enabled 里选。 + LLMClientChoice bool `gorm:"column:llm_client_choice" json:"llm_client_choice"` + Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` +} diff --git a/apps/services/modules/console/api_echomeet_orch.go b/apps/services/modules/console/api_echomeet_orch.go index 52b14411..516667f3 100644 --- a/apps/services/modules/console/api_echomeet_orch.go +++ b/apps/services/modules/console/api_echomeet_orch.go @@ -211,6 +211,7 @@ func (this *serverComp) saveEchoOrch(c *gin.Context) { return } } + broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", o.Region) // 通知 echomeet 运行时重载编排 writeOK(c, gin.H{"row_id": o.RowId}) } @@ -232,6 +233,7 @@ func (this *serverComp) delEchoOrch(c *gin.Context) { if had && old.IsDefault && old.Kind == echoKindLLM { this.promoteEchoOrchDefault(old.AppName, old.Region) } + broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "delete", old.Region) // 通知 echomeet 运行时重载编排 writeOK(c, gin.H{"deleted": req.RowId}) } @@ -284,6 +286,9 @@ func (this *serverComp) reorderEchoOrch(c *gin.Context) { } updated++ } + if updated > 0 { + broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", 0) // 通知 echomeet 运行时重载编排 + } writeOK(c, gin.H{"updated": updated}) } @@ -314,5 +319,6 @@ func (this *serverComp) saveEchoOrchSetting(c *gin.Context) { return } } + broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", req.Region) // 通知 echomeet 运行时重载编排 writeOK(c, gin.H{"llm_client_choice": req.LLMClientChoice}) } diff --git a/apps/services/modules/console/api_svctemplate.go b/apps/services/modules/console/api_svctemplate.go index ca4e625a..c1680d4f 100644 --- a/apps/services/modules/console/api_svctemplate.go +++ b/apps/services/modules/console/api_svctemplate.go @@ -204,10 +204,12 @@ func builtinSvcTemplates() []ThirdSvcTemplate { // MT 机器翻译 tpl("mt_azure", "azure", "微软 Azure 翻译", svcCatMT, 0, sf("subscription_key", "订阅密钥", true), sf("region", "区域,如 eastasia", false)), + // ⚠️ 字段须对齐运行时 sys 客户端:Google 翻译走服务账号 JSON(sys/google/translate), + // 火山翻译走 AK/SK(sys/bytedance/translate)——不是 api_key。 tpl("mt_google", "google", "Google 翻译", svcCatMT, 1, - sf("api_key", "API Key", true)), + sf("json", "服务账号 JSON(内容或路径)", true)), tpl("mt_doubao", "doubao", "豆包(火山)翻译", svcCatMT, 2, - sf("api_key", "API Key", true)), + sf("access_key", "火山 AccessKey", true), sf("secret_key", "火山 SecretKey", true), sf("region", "区域(可选,默认 cn-north-1)", false)), tpl("mt_alibaba", "alibaba", "阿里云 翻译", svcCatMT, 3, sf("access_key_id", "AccessKey ID", true), sf("access_key_secret", "AccessKey Secret", true)), diff --git a/apps/services/modules/console/model_echomeet_orch.go b/apps/services/modules/console/model_echomeet_orch.go index 6511349f..c39e2ad7 100644 --- a/apps/services/modules/console/model_echomeet_orch.go +++ b/apps/services/modules/console/model_echomeet_orch.go @@ -1,5 +1,7 @@ package console +import "yunyan/comm" + // 会议记录服务编排 (echomeet orchestration)。 // // 与旧的 echomeet_svc_config(每应用一份、在业务库、自己存凭据)不同: @@ -20,11 +22,11 @@ package console // (console 才有注册表里的 base_url;业务服务未必知道自己的对外地址,尤其多区域部署)。 // 下发解析顺序:(app,region) → (app,0) → ('',region) → ('',0) 全局默认兜底,越具体越优先。 -// 编排类别(会议记录三段)。 +// 编排类别(会议记录三段)。常量已下沉 comm(echomeet 运行时按 kind 消费编排),此处保留包内别名。 const ( - echoKindASR int32 = 1 // 识别(录音文件转写) - echoKindMT int32 = 2 // 翻译 - echoKindLLM int32 = 3 // 总结(大模型) + echoKindASR = comm.EchoKindASR // 识别(录音文件转写) + echoKindMT = comm.EchoKindMT // 翻译 + echoKindLLM = comm.EchoKindLLM // 总结(大模型) ) // echoKindSvcCats 各编排类别对应的服务池类别(前端选服务时按此过滤,后端校验 svc_id 归属用)。 @@ -34,29 +36,7 @@ var echoKindSvcCats = map[int32][]int32{ echoKindLLM: {svcCatLLM, svcCatLLMVision}, } -// EchoOrchestration 一行 = 某作用域(app_name,region) 下某类别选中的一个服务引用 + 排序/标记。 -// 唯一:(app_name, region, kind, svc_id)。svc_id 引用 ThirdSvcConfig.Id(同作用域解析凭据)。 -type EchoOrchestration struct { - RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` - AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_echo_orch,priority:1" json:"app_name"` // ''=全局默认兜底 - Region int32 `gorm:"column:region;uniqueIndex:uidx_echo_orch,priority:2" json:"region"` // 0=全区域兜底,>0=特定区域 - Kind int32 `gorm:"column:kind;uniqueIndex:uidx_echo_orch,priority:3" json:"kind"` // 1识别/2翻译/3总结 - SvcId string `gorm:"column:svc_id;size:64;uniqueIndex:uidx_echo_orch,priority:4" json:"svc_id"` // 引用 svc_config 的服务 id - Priority int32 `gorm:"column:priority" json:"priority"` // 同类内升序,小=优先 - IsDefault bool `gorm:"column:is_default" json:"is_default"` // 总结用:默认模型,同作用域内至多一条 - ClientVisible bool `gorm:"column:client_visible" json:"client_visible"` // 总结用:是否允许客户端选择 - CostHint float64 `gorm:"column:cost_hint" json:"cost_hint"` // 参考单价,仅排序/展示;识别元/时、翻译元/百万字符、总结元/百万token - Enabled bool `gorm:"column:enabled" json:"enabled"` - Createtime int64 `gorm:"column:createtime" json:"createtime"` - Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` -} - -// EchoOrchSetting 会议记录编排的作用域级开关。每 (app_name, region) 一行。 -type EchoOrchSetting struct { - RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` - AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_echo_orch_set,priority:1" json:"app_name"` - Region int32 `gorm:"column:region;uniqueIndex:uidx_echo_orch_set,priority:2" json:"region"` - // LLMClientChoice 是否允许客户端指定总结模型。false=强制默认模型;true=客户端可在 ClientVisible 且 Enabled 里选。 - LLMClientChoice bool `gorm:"column:llm_client_choice" json:"llm_client_choice"` - Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` -} +// EchoOrchestration / EchoOrchSetting 结构已下沉到 comm/svcpool.go(echomeet 运行时直读消费), +// console 侧用 type alias 引用,包内代码零改动。 +type EchoOrchestration = comm.EchoOrchestration +type EchoOrchSetting = comm.EchoOrchSetting diff --git a/apps/services/modules/console/model_svcconfig.go b/apps/services/modules/console/model_svcconfig.go index d90a9d3a..b09f96d2 100644 --- a/apps/services/modules/console/model_svcconfig.go +++ b/apps/services/modules/console/model_svcconfig.go @@ -10,54 +10,13 @@ package console // gorm v1.26 AutoMigrate + 读写自动 Marshal/Unmarshal,无需手动处理。 // region / mode 直接用 int32(与前端 JSON 一致),不依赖 pb 的 Region/TranslateMode 枚举。 -// SvcField 第三方服务的自定义字段项(内嵌于 ThirdSvcConfig.fields jsonb 列,也复用于模板/区域专属字段)。 -// 加密字段:DefValue 存 AES-GCM 密文;API 对加密字段只返回 "[encrypted]" 占位, -// 更新时若传空串/占位符则保留原密文不变。 -type SvcField struct { - Key string `json:"key"` // 字段名,同服务内唯一,如 secret_key - Description string `json:"description"` // 字段用途描述,前端展示 - Type string `json:"type"` // 字段类型:空/text 普通文本;langs 支持语言多选(值=逗号分隔 BCP-47);lang 单选默认语言。旧数据为空即 text,向后兼容 - Encrypted bool `json:"encrypted"` // 是否加密存储 - DefValue string `json:"def_value"` // 默认值(全区域共用;加密字段存 AES-GCM 密文;langs 存 "zh-CN,en-US",lang 存单个码) - Sort int32 `json:"sort"` // 展示排序,越小越靠前 -} +import "yunyan/comm" -// ThirdSvcConfig 第三方服务配置(表:svc_config,按应用分离)。每行 = 某应用作用域下的一个第三方服务(如火山引擎/阿里云)。 -// categories 存逗号分隔的 ServiceCategory 枚举值,如 "1,2,3"。 -// Provider 记录该服务由哪个服务商模板创建(如 azure/openai),仅供前端展示/分组,可为空。 -// -// 按应用分离:AppName=''为全局默认、'X'为应用X的覆盖;解析时应用有则用应用的、无则回退全局。 -// 业务字符串 Id 不再全局唯一,改用代理主键 RowId + 唯一索引(app_name,id),同一 id 可在全局与各应用并存。 -type ThirdSvcConfig struct { - RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` - AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_svc_config,priority:1" json:"app_name"` // ''=全局默认 - Id string `gorm:"column:id;size:64;uniqueIndex:uidx_svc_config,priority:2" json:"id"` - Name string `gorm:"column:name;size:64" json:"name"` - Provider string `gorm:"column:provider;size:64" json:"provider"` - Categories string `gorm:"column:categories;size:64" json:"categories"` - Description string `gorm:"column:description" json:"description"` - Enable bool `gorm:"column:enable" json:"enable"` - Fields []SvcField `gorm:"column:fields;type:jsonb;serializer:json" json:"fields"` - Createtime int64 `gorm:"column:createtime" json:"createtime"` - Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` -} - -// SvcRegionOverride 第三方服务区域字段覆盖(表:third_svc_region_override)。 -// 每行 = 一个服务 × 一个区域的分叉集合,支持三种差异(均向后兼容:旧行新字段为 nil,行为不变): -// - Overrides:默认字段的“值覆盖”。某字段 key 不在其中则继承 ThirdSvcConfig.fields 对应字段的 DefValue。 -// - ExtraFields:本区域“专属新增字段”(默认字段集里没有的),DefValue 存该区域的值(加密字段存 AES-GCM 密文)。 -// - DisabledKeys:本区域“不适用”的默认字段 key(如阿里国内/国外、微软不同区域节点字段不一致)。 -// 按应用分离:AppName 进唯一索引首位,(app_name,svc_id,region) 唯一;svc_id 引用同作用域下的 ThirdSvcConfig.Id。 -type SvcRegionOverride struct { - Id uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - AppName string `gorm:"column:app_name;size:64;uniqueIndex:uidx_svc_region,priority:1" json:"app_name"` // ''=全局默认 - SvcId string `gorm:"column:svc_id;size:64;uniqueIndex:uidx_svc_region,priority:2" json:"svc_id"` - Region int32 `gorm:"column:region;uniqueIndex:uidx_svc_region,priority:3" json:"region"` - Overrides map[string]string `gorm:"column:overrides;type:jsonb;serializer:json" json:"overrides"` - ExtraFields []SvcField `gorm:"column:extra_fields;type:jsonb;serializer:json" json:"extra_fields"` - DisabledKeys []string `gorm:"column:disabled_keys;type:jsonb;serializer:json" json:"disabled_keys"` - Updatetime int64 `gorm:"column:updatetime" json:"updatetime"` -} +// SvcField / ThirdSvcConfig / SvcRegionOverride 结构已下沉到 comm/svcpool.go(业务服务运行时 +// 也要直读消费,如 echomeet 编排),console 侧用 type alias 引用,包内代码零改动。 +type SvcField = comm.SvcField +type ThirdSvcConfig = comm.ThirdSvcConfig +type SvcRegionOverride = comm.SvcRegionOverride // CallTranslatePair 通话翻译语言对配置(表:call_translate_pair)。 // 每条记录定义一个区域下"源语言→目标语言"的翻译服务编排。 diff --git a/apps/services/modules/echomeet/api_getsummaryservices.go b/apps/services/modules/echomeet/api_getsummaryservices.go new file mode 100644 index 00000000..d22f7ddc --- /dev/null +++ b/apps/services/modules/echomeet/api_getsummaryservices.go @@ -0,0 +1,25 @@ +package echomeet + +import ( + "yunyan/comm" + "yunyan/pb" +) + +// @Summary 获取客户端可选的总结大模型服务列表 +// @Description 由后台「会议记录服务」编排下发:client_choice 为后台开关(关闭时仅返回默认项、 +// starttask/summary 传 summary_svc_id 会被忽略);识别与翻译由后台优先级选路,不下发、客户端不可指定。 +// @Tags Echomeet +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param user body pb.EchomeetGetSummaryServicesReq true "请求数据" +// @Success 200 {object} comm.HttpResult{data=pb.EchomeetGetSummaryServicesResp} "响应数据" +// @Router /api/home/echomeet_getsummaryservices [post] +func (this *apiComp) GetSummaryServices(session comm.IUserSession, req *pb.EchomeetGetSummaryServicesReq) (resp *pb.EchomeetGetSummaryServicesResp, errdata *pb.ErrorData) { + clientChoice, items := this.module.providers.SummaryServices() + resp = &pb.EchomeetGetSummaryServicesResp{ + ClientChoice: clientChoice, + Services: items, + } + return +} diff --git a/apps/services/modules/echomeet/api_starttask.go b/apps/services/modules/echomeet/api_starttask.go index f69db21b..f03d1d86 100644 --- a/apps/services/modules/echomeet/api_starttask.go +++ b/apps/services/modules/echomeet/api_starttask.go @@ -4,7 +4,6 @@ import ( "yunyan/comm" "yunyan/pb" "encoding/json" - "fmt" "time" ) @@ -106,18 +105,28 @@ func (this *apiComp) StartTask(session comm.IUserSession, req *pb.EchomeetStartT model.Isdistinguishspeaker = req.Isdistinguishspeaker model.Formlanguage = req.Formlanguage model.Tolanguage = req.Tolanguage - // 选定服务商:请求显式指定优先,其次配置默认,最后按语言自动路由; - // 再叠加「该服务商的转写引擎是否支持此源语言」校验,不支持则换到支持的那家 - // (否则会把音频送进不认识该语种的引擎,静默得到空转写结果)。 - // 一经确定,撰写/翻译/总结全部走同一服务商。 - model.ServiceType = this.module.providers.ResolveTranscribeService(req.ServiceType, model.Formlanguage) - this.module.model.saverecord(model) - this.module.Infof("StartTask id:%d uid:%s formlang:%s tolang:%s seconds:%d size:%d distinguishSpeaker:%v service:%s", - model.Id, model.Uid, model.Formlanguage, model.Tolanguage, model.Seconds, model.Size, model.Isdistinguishspeaker, ServiceTypeName(model.ServiceType)) + // 三段选路(后台编排,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.Infof("StartTask id:%d → 语音翻译", model.Id) + // 语音翻译:已有转写文本,只走翻译+总结,无需识别选路。 + 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) @@ -131,34 +140,33 @@ func (this *apiComp) StartTask(session comm.IUserSession, req *pb.EchomeetStartT _ = this.module.tasks.SubmitAITask(model) } return - } else { - if model.Audiourl == "" { - errdata = &pb.ErrorData{ - Code: pb.ErrorCode_AudioUrlEmpty, - Message: "音频URL为空", - } - return - } + } - this.module.Infof("StartTask id:%d → service:%s lang:%s", model.Id, ServiceTypeName(model.ServiceType), model.Formlanguage) - switch model.ServiceType { - case pb.TranscribeService_TranscribeService_ByteDance: - if model.Seconds <= byteDanceFlashMaxSeconds && model.Size <= byteDanceFlashMaxBytes { - _ = this.module.tasks.SubmitShortAudioTask(model) - } else { - _ = this.module.tasks.SubmitLongAudioTask(model) - } - case pb.TranscribeService_TranscribeService_Ali: - _ = this.module.tasks.SubmitAliAudioTask(model) - case pb.TranscribeService_TranscribeService_Microsoft, pb.TranscribeService_TranscribeService_Google: - _ = this.module.tasks.SubmitTranscribeTask(model) - default: - errdata = &pb.ErrorData{ - Code: pb.ErrorCode_SystemError, - Message: fmt.Sprintf("unsupported service_type: %d", model.ServiceType), - } - 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 diff --git a/apps/services/modules/echomeet/api_summary.go b/apps/services/modules/echomeet/api_summary.go index 3ee1340e..adbf2f16 100644 --- a/apps/services/modules/echomeet/api_summary.go +++ b/apps/services/modules/echomeet/api_summary.go @@ -55,14 +55,16 @@ func (this *apiComp) Summary(session comm.IUserSession, req *pb.EchomeetSummaryR return } model.Templateid = tmpl.Id - // 选定服务商:请求显式指定优先,否则沿用记录已有的服务商,再否则按配置默认/语言路由。 - // translate 与 AI 总结都会走这个服务商。 - svc := req.ServiceType - if svc == pb.TranscribeService_TranscribeService_Unknown || svc == pb.TranscribeService_TranscribeService_Auto { - svc = model.ServiceType + // 总结模型:请求显式指定(须后台开关允许且在可选列表内)优先,否则沿用记录已有,再否则后台默认。 + if req.SummarySvcId != "" || model.LlmSvcId == "" { + llmSvcId, _, perr := this.module.providers.PickLLM(req.SummarySvcId) + if perr != nil { + errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: perr.Error()} + return + } + model.LlmSvcId = llmSvcId } - model.ServiceType = this.module.providers.ResolveServiceType(svc, model.Formlanguage) - this.module.Infof("Summary id:%d → service:%s", model.Id, ServiceTypeName(model.ServiceType)) + this.module.Infof("Summary id:%d → llm:%s", model.Id, model.LlmSvcId) // 是否需要重新翻译: // - 客户端显式传入 tolanguage(重新总结操作,强制重译以保证 Translate 与 Tolanguage 一致);或 // - 目标语言变化;或 diff --git a/apps/services/modules/echomeet/fix_verify_test.go b/apps/services/modules/echomeet/fix_verify_test.go index e1ccdd00..e409263d 100644 --- a/apps/services/modules/echomeet/fix_verify_test.go +++ b/apps/services/modules/echomeet/fix_verify_test.go @@ -3,87 +3,99 @@ package echomeet import ( "testing" - "yunyan/pb" + "yunyan/comm" ) -// testTranscribers 构造仅含转写能力的 provider 集合(不初始化任何 SDK/凭据)。 -func testTranscribers() map[pb.TranscribeService]Transcriber { - return map[pb.TranscribeService]Transcriber{ - pb.TranscribeService_TranscribeService_ByteDance: &byteDanceTranscriber{}, - pb.TranscribeService_TranscribeService_Ali: &aliTranscriber{}, - } -} - -// 阿里转写的语言支持判定:paraformer-v2 不支持西语等,必须返回 false, -// 否则西语音频会被送进阿里、静默返回 SUCCESS_WITH_NO_VALID_FRAGMENT(看着像"音频没声音")。 -func TestAliTranscriberSupportsLanguage(t *testing.T) { - tr := &aliTranscriber{} - for _, l := range []string{"zh-CN", "zh-TW", "yue-HK", "en-US", "ja-JP", "ko-KR", "de-DE", "fr-FR", "ru-RU"} { - if !tr.SupportsLanguage(l) { - t.Errorf("paraformer-v2 应支持 %s,却返回 false", l) - } +// 编排选路的语言过滤(svcSupportsLanguage)回归: +// 铁律沿袭「未知语言绝不 fallback」——languages 配置了就必须命中(精确或主语言段), +// 防止重蹈「西语送进 paraformer-v2 静默返回空转写」的线上事故(该白名单现由后台 +// 服务池 languages 字段承担,本测试钉住过滤语义)。 +func TestSvcSupportsLanguage(t *testing.T) { + aliLangs := []string{"zh-CN", "yue-CN", "en-US", "ja-JP", "ko-KR", "de-DE", "fr-FR", "ru-RU"} + cases := []struct { + langs []string + want string + ok bool + }{ + {aliLangs, "es-ES", false}, // 西语:阿里 paraformer-v2 不支持,必须被过滤 + {aliLangs, "zh-CN", true}, + {aliLangs, "zh", true}, // 主语言段命中 + {aliLangs, "en-GB", true}, // en 主段命中 + {aliLangs, "pt-BR", false}, + {nil, "es-ES", true}, // 未配置 languages = 不限 + {aliLangs, "", true}, // 空语言 = 不过滤(交给引擎自动检测) } - // 这些语种 bcp47ToDashScope 里有映射,但 paraformer-v2 实际不支持 - for _, l := range []string{"es-ES", "es-MX", "pt-BR", "it-IT", "vi-VN", "tr-TR", "pl-PL", "nl-NL", "uk-UA", "th-TH", "ar-SA", "id-ID", "ms-MY", "fil-PH"} { - if tr.SupportsLanguage(l) { - t.Errorf("paraformer-v2 不支持 %s,却返回 true(会导致空转写)", l) + for _, c := range cases { + if got := svcSupportsLanguage(c.langs, c.want); got != c.ok { + t.Errorf("svcSupportsLanguage(%v, %q) = %v, want %v", c.langs, c.want, got, c.ok) } } - if !tr.SupportsLanguage("") { - t.Error("空语言应允许 DashScope 自动检测") - } } -// 字节转写支持西语,是西语会议兜底的前提。 -func TestByteDanceSupportsSpanish(t *testing.T) { - if !(&byteDanceTranscriber{}).SupportsLanguage("es-ES") { - t.Fatal("字节应支持 es-ES(西语兜底目标)") - } -} - -// 核心回归:即使 DefaultService 被固定成 Ali(线上就是这么配的), -// 西语也不能进阿里,必须改道到支持它的字节。 -func TestPickTranscribeServiceAvoidsUnsupportedLang(t *testing.T) { - trs := testTranscribers() - ali := pb.TranscribeService_TranscribeService_Ali - byteDance := pb.TranscribeService_TranscribeService_ByteDance - +// 作用域具体度(scopeRank)回归:(app,region) > (app,0) > ('',region) > ('',0), +// 非本部署作用域链上的行必须是 0。 +func TestScopeRank(t *testing.T) { + const app, reg = "voitrans", int32(1) cases := []struct { - lang string - resolved pb.TranscribeService // ResolveServiceType 的结果(DefaultService=Ali) - wantSvc pb.TranscribeService - wantChanged bool + appName string + region int32 + want int }{ - {"es-ES", ali, byteDance, true}, // 西语:阿里不支持 → 改道字节(本次修复的 bug) - {"pt-BR", ali, byteDance, true}, // 葡语:同上 - {"zh-CN", ali, ali, false}, // 中文:阿里支持 → 不乱改道 - {"ru-RU", ali, ali, false}, // 俄语:阿里支持 → 维持 - {"en-US", byteDance, byteDance, false}, // 英语:字节支持 → 维持 + {"voitrans", 1, 4}, + {"voitrans", 0, 3}, + {"", 1, 2}, + {"", 0, 1}, + {"otherapp", 1, 0}, // 别的应用 + {"voitrans", 2, 0}, // 别的区域 } for _, c := range cases { - got, changed, supported := pickTranscribeService(trs, c.resolved, c.lang) - if got != c.wantSvc || changed != c.wantChanged { - t.Errorf("lang=%s resolved=%s: got=(%s,changed=%v) want=(%s,changed=%v)", - c.lang, ServiceTypeName(c.resolved), ServiceTypeName(got), changed, - ServiceTypeName(c.wantSvc), c.wantChanged) - } - if !supported { - t.Errorf("lang=%s: 选中的 %s 应支持该语言", c.lang, ServiceTypeName(got)) + if got := scopeRank(c.appName, c.region, app, reg); got != c.want { + t.Errorf("scopeRank(%q,%d) = %d, want %d", c.appName, c.region, got, c.want) } } } -// 没有任何服务商支持该语言时:维持原选择、不改道、并明确报告 unsupported(调用方据此告警)。 -func TestPickTranscribeServiceNoneSupports(t *testing.T) { - // 只放阿里,喂一个阿里不支持的语种 → 无处可去 - trs := map[pb.TranscribeService]Transcriber{ - pb.TranscribeService_TranscribeService_Ali: &aliTranscriber{}, +// 区域覆盖合并 + 解密(resolveSvcFields)回归: +// Overrides 覆盖默认值、ExtraFields 追加、DisabledKeys 剔除;加密字段解不开必须整体报错(fail-closed)。 +func TestResolveSvcFields(t *testing.T) { + const key = "test-field-encrypt-key" + enc, err := comm.Encrypt(key, "sk-plain") + if err != nil { + t.Fatalf("encrypt: %v", err) + } + svc := &comm.ThirdSvcConfig{ + Id: "asr1", Name: "阿里识别", Provider: "alibaba", + Fields: []comm.SvcField{ + {Key: "api_key", Encrypted: true, DefValue: enc}, + {Key: "languages", DefValue: "zh-CN,en-US"}, + {Key: "callback_path", DefValue: "/api/home/echomeet_alibackcall"}, + {Key: "obsolete", DefValue: "x"}, + }, + } + ovr := &comm.SvcRegionOverride{ + SvcId: "asr1", + Overrides: map[string]string{"languages": "ja-JP"}, + ExtraFields: []comm.SvcField{{Key: "extra", DefValue: "y"}}, + DisabledKeys: []string{"obsolete"}, + } + rs, err := resolveSvcFields(svc, ovr, key) + if err != nil { + t.Fatalf("resolve: %v", err) + } + if rs.Fields["api_key"] != "sk-plain" { + t.Errorf("加密字段应解出明文, got %q", rs.Fields["api_key"]) + } + if len(rs.Languages) != 1 || rs.Languages[0] != "ja-JP" { + t.Errorf("languages 应被区域覆盖为 ja-JP, got %v", rs.Languages) + } + if rs.Fields["extra"] != "y" { + t.Errorf("ExtraFields 应追加") } - got, changed, supported := pickTranscribeService(trs, pb.TranscribeService_TranscribeService_Ali, "es-ES") - if got != pb.TranscribeService_TranscribeService_Ali || changed { - t.Errorf("无可用兜底时应维持原选择且不改道,got=%s changed=%v", ServiceTypeName(got), changed) + if _, ok := rs.Fields["obsolete"]; ok { + t.Errorf("DisabledKeys 应剔除 obsolete") } - if supported { - t.Error("应报告 unsupported=false,让调用方告警") + // fail-closed:错的 key 解不开必须报错,绝不把密文当明文用 + if _, err := resolveSvcFields(svc, nil, "wrong-key"); err == nil { + t.Errorf("错误密钥应整体报错(fail-closed),不能静默把密文当明文") } } diff --git a/apps/services/modules/echomeet/options.go b/apps/services/modules/echomeet/options.go index 6db44e28..448fd0bb 100644 --- a/apps/services/modules/echomeet/options.go +++ b/apps/services/modules/echomeet/options.go @@ -11,38 +11,13 @@ type ( modules.IOptions } - // SummaryOptions 总结所用对话模型配置。 - // 字节走豆包:填 Apikey + Model; - // Google 走 Gemini:填 Apikey + Model(Endpoint 可选); - // 阿里 / 微软走 OpenAI 兼容端点(阿里 DashScope、微软 Azure OpenAI):填 BaseURL + Token + Model。 - SummaryOptions struct { - Apikey string // 豆包 / Gemini API Key - Token string // OpenAI 兼容端点 API Key - BaseURL string // OpenAI 兼容端点地址 - Model string // 模型 ID - Endpoint string // Gemini 自定义 endpoint(可选) - } - - // ProviderOptions 单个服务商的三段式配置:转写 / 翻译 / 总结, - // 由 echomeet 模块内部按服务商组合成完整的「会议撰写→翻译→总结」链路。 - // Transcribe、Translate 为原样透传给底层 sys 包 OnInit 的配置块。 - ProviderOptions struct { - Callback string // 转写结果异步回调地址(字节、阿里使用;Google/微软走轮询可留空) - Transcribe map[string]interface{} // 转写服务配置 - Translate map[string]interface{} // 翻译服务配置 - Summary SummaryOptions // 总结(对话模型)配置 - } - + // Options echomeet 模块配置。 + // + // 第三方服务(识别/翻译/总结)不再走 yaml:全部由后台「会议记录服务」编排 + // (console 共享库 echomeet_orch + 服务池 svc_config)配置,运行时直读 + NATS 热重载。 + // 部署身份与解密密钥由 .env 注入:APP_NAME / APP_REGION / FIELD_ENCRYPT_KEY。 Options struct { modules.Options - // DefaultService 默认服务商:留空 / Auto = 按源语言自动路由(以前的方案); - // 指定 ByteDance / Ali / Google / Microsoft = 该会议的撰写、翻译、总结全部走此服务商。 - // 客户端请求若显式带了 ServiceType,则以请求为准、覆盖此默认值。 - DefaultService string - ByteDance ProviderOptions - Ali ProviderOptions - Google ProviderOptions - Microsoft ProviderOptions } ) diff --git a/apps/services/modules/echomeet/orch_resolver.go b/apps/services/modules/echomeet/orch_resolver.go new file mode 100644 index 00000000..07d9a609 --- /dev/null +++ b/apps/services/modules/echomeet/orch_resolver.go @@ -0,0 +1,289 @@ +package echomeet + +import ( + "fmt" + "sort" + "strings" + + "yunyan/comm" + "yunyan/lego/sys/log" + "yunyan/lego/sys/postgres" +) + +// 编排解析层:从 console 共享库(postgres)读「会议记录编排 echomeet_orch + 服务池 svc_config」, +// 解析出本部署 (app_name, region) 作用域下三段(识别/翻译/总结)各自的服务列表与明文凭据。 +// +// - 作用域分层:(app,region) → (app,0) → ('',region) → ('',0),对每个 kind 取「最具体的有条目的层」。 +// - 服务池按 app_name 两层:应用覆盖全局(同 id 应用优先);区域字段覆盖(svc_region_override)按 +// 服务行自身的作用域匹配。 +// - 加密字段用 comm.Decrypt(${FIELD_ENCRYPT_KEY}) 解密;解密失败该服务整体不可用(fail-closed, +// 参照 voitrans 事故:密文当明文用会在深处炸成不可理解的错误)。 +// - ASR 服务的 callback_path 是相对路径,完整回调 = app_registry.base_url + callback_path。 + +// resolvedSvc 解析完成的服务:明文字段 + 语言支持 + 拼好的回调地址。 +type resolvedSvc struct { + SvcId string + Name string + Provider string + Fields map[string]string // 区域合并 + 解密后的明文字段 + Languages []string // fields["languages"] 拆分(BCP-47),空=不限 + CallbackURL string // ASR 用:base_url + callback_path;无 callback_path 则空 +} + +// orchSnapshot 一次完整解析的快照(providersComp 持有,读写锁保护整体替换)。 +type orchSnapshot struct { + ASR []*comm.EchoOrchestration // priority 升序(仅 Enabled 且服务可用的条目) + MT []*comm.EchoOrchestration + LLM []*comm.EchoOrchestration + Setting comm.EchoOrchSetting + // Services 三段引用到的服务解析结果,key=svc_id。构建 provider 实例时消费。 + Services map[string]*resolvedSvc +} + +// scopeRank 作用域具体度:越大越具体。 +func scopeRank(appName string, region int32, app string, reg int32) int { + switch { + case appName == app && region == reg && app != "": + return 4 // (app, region) + case appName == app && region == 0 && app != "": + return 3 // (app, 0) + case appName == "" && region == reg && reg != 0: + return 2 // ('', region) + case appName == "" && region == 0: + return 1 // ('', 0) 全局默认 + default: + return 0 // 不属于本部署作用域链(如别的区域的行) + } +} + +// loadOrchSnapshot 全量加载并解析编排。app/region 是本部署身份(region 为 pb.Region int32, +// regionCode 是 app_registry 用的字符串码),encKey 是字段解密密钥。 +func loadOrchSnapshot(app string, region int32, regionCode, encKey string) (*orchSnapshot, error) { + pg := postgres.GetSys() + if pg == nil { + return nil, fmt.Errorf("postgres 未初始化") + } + + // 1. 拉本部署作用域链上的全部编排行,按 kind 分层挑最具体。 + rows := make([]*comm.EchoOrchestration, 0) + if err := postgres.Find(comm.TableEchomeetOrch, &rows, + "(app_name=? OR app_name='') AND (region=? OR region=0)", app, region); err != nil && err != postgres.ErrNoDocuments { + return nil, fmt.Errorf("读编排表失败: %w", err) + } + byKind := map[int32][]*comm.EchoOrchestration{} + kindRank := map[int32]int{} + for _, r := range rows { + rank := scopeRank(r.AppName, r.Region, app, region) + if rank == 0 { + continue + } + if rank > kindRank[r.Kind] { + kindRank[r.Kind] = rank + byKind[r.Kind] = byKind[r.Kind][:0] + } + if rank == kindRank[r.Kind] { + byKind[r.Kind] = append(byKind[r.Kind], r) + } + } + for _, list := range byKind { + sort.Slice(list, func(i, j int) bool { return list[i].Priority < list[j].Priority }) + } + + // 2. 作用域开关:同样取最具体的一行。 + snap := &orchSnapshot{Services: map[string]*resolvedSvc{}} + settings := make([]*comm.EchoOrchSetting, 0) + if err := postgres.Find(comm.TableEchomeetOrchSetting, &settings, + "(app_name=? OR app_name='') AND (region=? OR region=0)", app, region); err != nil && err != postgres.ErrNoDocuments { + return nil, fmt.Errorf("读编排开关失败: %w", err) + } + best := 0 + for _, s := range settings { + if rank := scopeRank(s.AppName, s.Region, app, region); rank > best { + best = rank + snap.Setting = *s + } + } + + // 3. 收集引用到的 svc_id,拉服务池(应用覆盖全局)。 + idset := map[string]bool{} + for _, list := range byKind { + for _, r := range list { + if r.Enabled { + idset[r.SvcId] = true + } + } + } + if len(idset) > 0 { + ids := make([]string, 0, len(idset)) + for id := range idset { + ids = append(ids, id) + } + svcs := make([]*comm.ThirdSvcConfig, 0) + if err := postgres.Find(comm.TableSvcConfig, &svcs, + "(app_name=? OR app_name='') AND id IN ?", app, ids); err != nil && err != postgres.ErrNoDocuments { + return nil, fmt.Errorf("读服务池失败: %w", err) + } + picked := map[string]*comm.ThirdSvcConfig{} + for _, s := range svcs { + if !s.Enable { + continue + } + if exist, ok := picked[s.Id]; !ok || (exist.AppName == "" && s.AppName == app && app != "") { + picked[s.Id] = s + } + } + + // 4. 区域字段覆盖:按服务行自身的作用域 + 本部署区域匹配。 + ovrs := make([]*comm.SvcRegionOverride, 0) + if region != 0 { + if err := postgres.Find(comm.TableSvcRegionOverride, &ovrs, + "(app_name=? OR app_name='') AND svc_id IN ? AND region=?", app, ids, region); err != nil && err != postgres.ErrNoDocuments { + return nil, fmt.Errorf("读区域覆盖失败: %w", err) + } + } + ovrOf := func(svc *comm.ThirdSvcConfig) *comm.SvcRegionOverride { + for _, o := range ovrs { + if o.SvcId == svc.Id && o.AppName == svc.AppName { + return o + } + } + return nil + } + + // 5. 拼回调所需的部署对外地址(仅 ASR 服务带 callback_path 时用到)。 + baseURL := loadAppBaseURL(app, regionCode) + + for _, svc := range picked { + rs, err := resolveSvcFields(svc, ovrOf(svc), encKey) + if err != nil { + // fail-closed:字段解不开的服务不可用,选路会跳过它。 + log.Warnf("echomeet 编排: 服务 %s 字段解析失败已跳过: %v", svc.Id, err) + continue + } + if p := rs.Fields["callback_path"]; p != "" { + if baseURL == "" { + log.Warnf("echomeet 编排: 服务 %s 配了 callback_path 但查不到部署对外地址(app_registry.base_url),回调留空、转写只能靠轮询", svc.Id) + } else { + rs.CallbackURL = strings.TrimRight(baseURL, "/") + p + } + } + snap.Services[svc.Id] = rs + } + } + + // 6. 只保留「启用且服务已解析成功」的编排条目。 + keep := func(list []*comm.EchoOrchestration) []*comm.EchoOrchestration { + out := make([]*comm.EchoOrchestration, 0, len(list)) + for _, r := range list { + if r.Enabled && snap.Services[r.SvcId] != nil { + out = append(out, r) + } + } + return out + } + snap.ASR = keep(byKind[comm.EchoKindASR]) + snap.MT = keep(byKind[comm.EchoKindMT]) + snap.LLM = keep(byKind[comm.EchoKindLLM]) + return snap, nil +} + +// resolveSvcFields 合并区域覆盖并解密,产出明文字段表。 +// 算法与 console 的 getSvcRegionOverride 同构:DefValue ← Overrides[key] 覆盖, +// 叠加 ExtraFields,剔除 DisabledKeys;Encrypted 字段 comm.Decrypt。 +func resolveSvcFields(svc *comm.ThirdSvcConfig, ovr *comm.SvcRegionOverride, encKey string) (*resolvedSvc, error) { + disabled := map[string]bool{} + if ovr != nil { + for _, k := range ovr.DisabledKeys { + disabled[k] = true + } + } + fields := make(map[string]string, len(svc.Fields)+4) + put := func(f comm.SvcField, val string) error { + if val == "" { + fields[f.Key] = "" + return nil + } + if f.Encrypted { + plain, err := comm.Decrypt(encKey, val) + if err != nil { + return fmt.Errorf("字段 %s 解密失败(检查 FIELD_ENCRYPT_KEY 是否与 console 一致): %w", f.Key, err) + } + fields[f.Key] = plain + return nil + } + fields[f.Key] = val + return nil + } + for _, f := range svc.Fields { + if disabled[f.Key] { + continue + } + val := f.DefValue + if ovr != nil { + if v, ok := ovr.Overrides[f.Key]; ok { + val = v + } + } + if err := put(f, val); err != nil { + return nil, err + } + } + if ovr != nil { + for _, f := range ovr.ExtraFields { + if err := put(f, f.DefValue); err != nil { + return nil, err + } + } + } + rs := &resolvedSvc{SvcId: svc.Id, Name: svc.Name, Provider: svc.Provider, Fields: fields} + if langs := strings.TrimSpace(fields["languages"]); langs != "" { + for _, l := range strings.Split(langs, ",") { + if l = strings.TrimSpace(l); l != "" { + rs.Languages = append(rs.Languages, l) + } + } + } + return rs, nil +} + +// loadAppBaseURL 从 app_registry 取本部署的对外地址(console 注册表,region 用字符串码)。 +// 查不到返回空串(调用侧告警,回调留空走轮询兜底)。 +func loadAppBaseURL(app, regionCode string) string { + if app == "" { + return "" + } + var baseURL string + pg := postgres.GetSys() + if pg == nil { + return "" + } + if err := pg.Table(comm.TableAppRegistry).Where("app_name=? AND region=?", app, regionCode). + Limit(1).Pluck("base_url", &baseURL).Error; err != nil { + log.Warnf("echomeet 编排: 读 app_registry.base_url 失败 app=%s region=%s err=%v", app, regionCode, err) + return "" + } + return baseURL +} + +// svcSupportsLanguage 语言过滤:languages 为空=不限;否则要求精确命中或主语言段一致 +// (zh 命中 zh-CN;zh-CN 命中 zh)。绝不做「未知语言→默认中文」式回退。 +func svcSupportsLanguage(langs []string, bcp47 string) bool { + if len(langs) == 0 { + return true + } + want := strings.TrimSpace(bcp47) + if want == "" { + return true + } + wantBase, _, _ := strings.Cut(want, "-") + for _, l := range langs { + if strings.EqualFold(l, want) { + return true + } + base, _, _ := strings.Cut(l, "-") + if strings.EqualFold(base, wantBase) { + return true + } + } + return false +} diff --git a/apps/services/modules/echomeet/provider_ali.go b/apps/services/modules/echomeet/provider_ali.go index ac0b8adc..902d06ba 100644 --- a/apps/services/modules/echomeet/provider_ali.go +++ b/apps/services/modules/echomeet/provider_ali.go @@ -2,64 +2,29 @@ package echomeet import ( "context" - "yunyan/lego/sys/log" + "fmt" + "yunyan/pb" "yunyan/sys/aliyun/filetrans" - "yunyan/sys/aliyun/translate" - "yunyan/sys/openai" - "fmt" - "strings" + alitranslate "yunyan/sys/aliyun/translate" ) -// newAliProviders 初始化阿里系单元服务(转写=DashScope 录音文件转写、翻译=阿里翻译、 -// 总结=通义千问 DashScope OpenAI 兼容端点),返回组合后的转写/翻译/总结三个 provider。 -func newAliProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { - if err := filetrans.OnInit(opt.Transcribe); err != nil { - log.Errorf("echomeet: init ali filetrans failed: %v", err) - } - if err := translate.OnInit(opt.Translate); err != nil { - log.Errorf("echomeet: init ali translate failed: %v", err) - } - sys, err := openai.NewSys( - openai.SetBaseURL(opt.Summary.BaseURL), - openai.SetToken(opt.Summary.Token), - openai.SetModel(opt.Summary.Model), - openai.SetMaxfunccall(1), - ) - if err != nil { - log.Errorf("echomeet: init ali(qwen) summarizer failed: %v", err) - } - return &aliTranscriber{}, &aliTranslator{}, &aliSummarizer{sys: sys} -} - -// 阿里云 DashScope 转写(paraformer-v2,异步) -type aliTranscriber struct{} - -func (this *aliTranscriber) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Ali -} - -// dashScopeParaformerV2Langs 是 filetrans 所用模型 paraformer-v2 **实际**支持的语种(DashScope 语言码)。 +// 阿里系单元服务实例(由 provider_registry.go 按服务池字段构造): +// 转写=DashScope 录音文件转写(paraformer-v2,异步)、翻译=阿里翻译。 +// 总结(通义千问)走 OpenAI 兼容端点,见 provider_registry.go 的 openaiSummarizer。 // -// 坑:core.go 的 bcp47ToDashScope 里还映射了 es/pt/it/vi/tr/pl/nl/uk/th/ar/id/ms/fil, -// 但 paraformer-v2 并不支持它们——把这些语种的音频送进去,引擎识别不出任何内容, -// 转写查询会返回 SUCCESS_WITH_NO_VALID_FRAGMENT(看起来像"音频没声音",实则是模型不认识该语种)。 -// 若将来把 filetrans.go 的 Model 换成 Fun-ASR / Qwen-ASR(支持更多语种),需同步放开这里。 -var dashScopeParaformerV2Langs = map[string]bool{ - "zh": true, "yue": true, "en": true, "ja": true, "ko": true, - "de": true, "fr": true, "ru": true, -} +// 语言支持过滤已上移到编排层:服务池 asrfile_alibaba 模板的 languages 字段 seed 了 +// paraformer-v2 实际支持的语种,选路时不支持的语言会跳过本服务(原 SupportsLanguage 白名单 +// 的职责由后台配置接管——把西语送进 paraformer-v2 会静默返回空转写,务必别放开 languages)。 -func (this *aliTranscriber) SupportsLanguage(bcp47 string) bool { - if strings.TrimSpace(bcp47) == "" { - return true // 空 = 不指定 hints,交给 DashScope 自动检测 - } - return dashScopeParaformerV2Langs[toDashScopeLang(bcp47)] +// 阿里云 DashScope 转写 +type aliTranscriber struct { + sys filetrans.ISys } func (this *aliTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { aliLang := toDashScopeLang(req.Language) - taskID, err := filetrans.CreateTask(req.AudioURL, aliLang, req.EnableSpeaker, req.CallbackURL) + taskID, err := this.sys.CreateTask(req.AudioURL, aliLang, req.EnableSpeaker, req.CallbackURL) if err != nil { return nil, fmt.Errorf("ali filetrans create task: %w", err) } @@ -67,7 +32,7 @@ func (this *aliTranscriber) Submit(_ context.Context, req SubmitRequest) (*Submi } func (this *aliTranscriber) Query(_ context.Context, taskID, _ string) (*QueryResult, error) { - status, contexts, err := filetrans.QueryTask(taskID) + status, contexts, err := this.sys.QueryTask(taskID) switch status { case filetrans.StatusSuccess: out := make([]*pb.ContextStruct, 0, len(contexts)) @@ -88,39 +53,10 @@ func (this *aliTranscriber) Query(_ context.Context, taskID, _ string) (*QueryRe } // 阿里翻译 -type aliTranslator struct{} - -func (this *aliTranslator) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Ali +type aliTranslator struct { + sys alitranslate.ISys } func (this *aliTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { - return translate.Translate(ctx, toAliTranslateLang(from), toAliTranslateLang(to), texts) -} - -// 通义千问总结(DashScope OpenAI 兼容端点) -type aliSummarizer struct { - sys openai.ISys -} - -func (this *aliSummarizer) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Ali -} - -func (this *aliSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) { - if this.sys == nil { - return "", fmt.Errorf("ali summarizer not initialized") - } - msgs := make([]openai.ChatReq, 0, len(messages)) - for _, m := range messages { - msgs = append(msgs, openai.ChatReq{Role: m.Role, Content: m.Content, Images: m.Images}) - } - resp, err := this.sys.Chat(ctx, msgs) - if err != nil { - return "", err - } - if resp == nil { - return "", fmt.Errorf("ali(qwen) chat: empty response") - } - return resp.Content, nil + return this.sys.Translate(ctx, toAliTranslateLang(from), toAliTranslateLang(to), texts) } diff --git a/apps/services/modules/echomeet/provider_bytedance.go b/apps/services/modules/echomeet/provider_bytedance.go index 430a3f22..e5039a17 100644 --- a/apps/services/modules/echomeet/provider_bytedance.go +++ b/apps/services/modules/echomeet/provider_bytedance.go @@ -2,43 +2,21 @@ package echomeet import ( "context" - "yunyan/lego/sys/log" + "fmt" + "yunyan/pb" "yunyan/sys/bytedance/audomodel" - "yunyan/sys/bytedance/translate" + bdtranslate "yunyan/sys/bytedance/translate" "yunyan/sys/doubao" - "fmt" ) -// newByteDanceProviders 初始化字节系单元服务(转写=audomodel、翻译=火山翻译、总结=豆包), -// 返回组合后的转写/翻译/总结三个 provider。 -func newByteDanceProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { - if err := audomodel.OnInit(opt.Transcribe); err != nil { - log.Errorf("echomeet: init bytedance audomodel failed: %v", err) - } - if err := translate.OnInit(opt.Translate); err != nil { - log.Errorf("echomeet: init bytedance translate failed: %v", err) - } - sys, err := doubao.NewSys( - doubao.SetApikey(opt.Summary.Apikey), - doubao.SetModel(opt.Summary.Model), - ) - if err != nil { - log.Errorf("echomeet: init doubao summarizer failed: %v", err) - } - return &byteDanceTranscriber{}, &byteDanceTranslator{}, &doubaoSummarizer{sys: sys} -} +// 字节系单元服务实例(由 provider_registry.go 按服务池字段构造): +// 转写=audomodel(短音频 flash 同步 + 长音频异步)、翻译=火山翻译、总结=豆包。 +// 语言支持过滤已上移到编排层(服务池 languages 字段),此处只做语言码转换。 // 字节跳动转写:短音频 RecognizeFlash(同步) + 长音频 CreateTask+QueryTask(异步) -type byteDanceTranscriber struct{} - -func (this *byteDanceTranscriber) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_ByteDance -} - -func (this *byteDanceTranscriber) SupportsLanguage(bcp47 string) bool { - _, ok := toByteDanceLang(bcp47) - return ok +type byteDanceTranscriber struct { + sys audomodel.ISys } const ( @@ -50,7 +28,7 @@ func (this *byteDanceTranscriber) Submit(_ context.Context, req SubmitRequest) ( lang, _ := toByteDanceLang(req.Language) // 短音频走 flash 同步通道 if req.Seconds <= byteDanceFlashMaxSeconds && req.SizeBytes <= byteDanceFlashMaxBytes { - statusCode, _, contexts, err := audomodel.RecognizeFlash(req.Uid, req.AudioURL, req.EnableSpeaker, lang) + statusCode, _, contexts, err := this.sys.RecognizeFlash(req.Uid, req.AudioURL, req.EnableSpeaker, lang) if err != nil { return nil, fmt.Errorf("bytedance flash err: status=%s %w", statusCode, err) } @@ -65,8 +43,8 @@ func (this *byteDanceTranscriber) Submit(_ context.Context, req SubmitRequest) ( } return &SubmitResult{Done: true, Contexts: out}, nil } - // 长音频走异步 submit - taskID, logID, err := audomodel.CreateTask(req.Uid, req.AudioURL, req.EnableSpeaker, lang, req.CallbackURL, req.CallbackData) + // 长音频走异步 submit(CallbackURL 由编排层按部署对外地址拼好传入) + taskID, logID, err := this.sys.CreateTask(req.Uid, req.AudioURL, req.EnableSpeaker, lang, req.CallbackURL, req.CallbackData) if err != nil { return nil, fmt.Errorf("bytedance create task: %w", err) } @@ -74,7 +52,7 @@ func (this *byteDanceTranscriber) Submit(_ context.Context, req SubmitRequest) ( } func (this *byteDanceTranscriber) Query(_ context.Context, taskID, logID string) (*QueryResult, error) { - code, contexts, err := audomodel.QueryTask(taskID, logID) + code, contexts, err := this.sys.QueryTask(taskID, logID) switch code { case "20000000": if err != nil { @@ -97,15 +75,13 @@ func (this *byteDanceTranscriber) Query(_ context.Context, taskID, logID string) } } -// 字节翻译 -type byteDanceTranslator struct{} - -func (this *byteDanceTranslator) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_ByteDance +// 火山翻译 +type byteDanceTranslator struct { + sys bdtranslate.ISys } func (this *byteDanceTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { - return translate.Translate(ctx, toByteDanceTranslateLang(from), toByteDanceTranslateLang(to), texts) + return this.sys.Translate(ctx, toByteDanceTranslateLang(from), toByteDanceTranslateLang(to), texts) } // 豆包总结 @@ -113,10 +89,6 @@ type doubaoSummarizer struct { sys doubao.ISys } -func (this *doubaoSummarizer) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_ByteDance -} - func (this *doubaoSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) { if this.sys == nil { return "", fmt.Errorf("doubao summarizer not initialized") diff --git a/apps/services/modules/echomeet/provider_google.go b/apps/services/modules/echomeet/provider_google.go index d9284d92..a26a2ac2 100644 --- a/apps/services/modules/echomeet/provider_google.go +++ b/apps/services/modules/echomeet/provider_google.go @@ -2,48 +2,24 @@ package echomeet import ( "context" - "yunyan/lego/sys/log" + "fmt" + "yunyan/pb" "yunyan/sys/google/gemini" - "yunyan/sys/google/speech" - "yunyan/sys/google/translate" - "fmt" + gspeech "yunyan/sys/google/speech" + gtranslate "yunyan/sys/google/translate" ) -// newGoogleProviders 初始化 Google 系单元服务(转写=Cloud Speech、翻译=Cloud Translation、总结=Gemini), -// 返回组合后的转写/翻译/总结三个 provider。 -func newGoogleProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { - if err := speech.OnInit(opt.Transcribe); err != nil { - log.Errorf("echomeet: init google speech failed: %v", err) - } - if err := translate.OnInit(opt.Translate); err != nil { - log.Errorf("echomeet: init google translate failed: %v", err) - } - sys, err := gemini.NewSys( - gemini.SetApikey(opt.Summary.Apikey), - gemini.SetModel(opt.Summary.Model), - gemini.SetEndpoint(opt.Summary.Endpoint), - ) - if err != nil { - log.Errorf("echomeet: init gemini summarizer failed: %v", err) - } - return &googleTranscriber{}, &googleTranslator{}, &geminiSummarizer{sys: sys} -} +// Google 系单元服务实例(由 provider_registry.go 按服务池字段构造): +// 转写=Cloud Speech、翻译=Cloud Translation、总结=Gemini。 // Google Cloud Speech-to-Text V1 LongRunningRecognize(要求 gs:// 音频) -type googleTranscriber struct{} - -func (this *googleTranscriber) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Google -} - -// Google Speech 支持的语种较广,不做白名单限制(gs:// 限制在 Submit 内) -func (this *googleTranscriber) SupportsLanguage(_ string) bool { - return true +type googleTranscriber struct { + sys gspeech.ISys } func (this *googleTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { - taskID, err := speech.CreateTask(req.AudioURL, req.Language, req.EnableSpeaker, req.CallbackURL) + taskID, err := this.sys.CreateTask(req.AudioURL, req.Language, req.EnableSpeaker, req.CallbackURL) if err != nil { return nil, fmt.Errorf("google speech create task: %w", err) } @@ -51,9 +27,9 @@ func (this *googleTranscriber) Submit(_ context.Context, req SubmitRequest) (*Su } func (this *googleTranscriber) Query(_ context.Context, taskID, _ string) (*QueryResult, error) { - status, contexts, err := speech.QueryTask(taskID) + status, contexts, err := this.sys.QueryTask(taskID) switch status { - case speech.StatusSuccess: + case gspeech.StatusSuccess: out := make([]*pb.ContextStruct, 0, len(contexts)) for _, c := range contexts { out = append(out, &pb.ContextStruct{ @@ -64,7 +40,7 @@ func (this *googleTranscriber) Query(_ context.Context, taskID, _ string) (*Quer }) } return &QueryResult{Status: StatusSuccess, Contexts: out}, nil - case speech.StatusRunning: + case gspeech.StatusRunning: return &QueryResult{Status: StatusRunning}, nil default: return &QueryResult{Status: StatusFailed}, fmt.Errorf("google speech query failed: %w", err) @@ -72,14 +48,12 @@ func (this *googleTranscriber) Query(_ context.Context, taskID, _ string) (*Quer } // Google Cloud Translation v3 -type googleTranslator struct{} - -func (this *googleTranslator) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Google +type googleTranslator struct { + sys gtranslate.ISys } func (this *googleTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { - return translate.Translate(ctx, toGoogleTranslateLang(from), toGoogleTranslateLang(to), texts) + return this.sys.Translate(ctx, toGoogleTranslateLang(from), toGoogleTranslateLang(to), texts) } // Gemini 总结 @@ -87,10 +61,6 @@ type geminiSummarizer struct { sys gemini.ISys } -func (this *geminiSummarizer) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Google -} - func (this *geminiSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) { if this.sys == nil { return "", fmt.Errorf("gemini summarizer not initialized") diff --git a/apps/services/modules/echomeet/provider_microsoft.go b/apps/services/modules/echomeet/provider_microsoft.go index dc62971a..1b134a49 100644 --- a/apps/services/modules/echomeet/provider_microsoft.go +++ b/apps/services/modules/echomeet/provider_microsoft.go @@ -2,50 +2,25 @@ package echomeet import ( "context" - "yunyan/lego/sys/log" + "fmt" + "yunyan/pb" - "yunyan/sys/microsoft/speech" - "yunyan/sys/microsoft/translate" + msspeech "yunyan/sys/microsoft/speech" + mstranslate "yunyan/sys/microsoft/translate" "yunyan/sys/openai" - "fmt" ) -// newMicrosoftProviders 初始化微软系单元服务(转写=Azure Speech、翻译=Azure Translator、 -// 总结=Azure OpenAI 兼容端点),返回组合后的转写/翻译/总结三个 provider。 -func newMicrosoftProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { - if err := speech.OnInit(opt.Transcribe); err != nil { - log.Errorf("echomeet: init microsoft speech failed: %v", err) - } - if err := translate.OnInit(opt.Translate); err != nil { - log.Errorf("echomeet: init microsoft translate failed: %v", err) - } - sys, err := openai.NewSys( - openai.SetBaseURL(opt.Summary.BaseURL), - openai.SetToken(opt.Summary.Token), - openai.SetModel(opt.Summary.Model), - openai.SetMaxfunccall(1), - ) - if err != nil { - log.Errorf("echomeet: init microsoft(azure openai) summarizer failed: %v", err) - } - return µsoftTranscriber{}, µsoftTranslator{}, µsoftSummarizer{sys: sys} -} +// 微软系单元服务实例(由 provider_registry.go 按服务池字段构造): +// 转写=Azure Speech Batch、翻译=Azure Translator。 +// 总结(Azure OpenAI)走 OpenAI 兼容端点(openaiSummarizer)。 // Azure Speech Batch Transcription -type microsoftTranscriber struct{} - -func (this *microsoftTranscriber) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Microsoft -} - -// Azure Speech 支持的语种较广,原生使用 BCP-47 locale,不做白名单限制; -// 不支持时由 Azure 直接返回错误,统一在 Submit 失败路径处理。 -func (this *microsoftTranscriber) SupportsLanguage(_ string) bool { - return true +type microsoftTranscriber struct { + sys msspeech.ISys } func (this *microsoftTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { - taskID, err := speech.CreateTask(req.AudioURL, req.Language, req.EnableSpeaker, req.CallbackURL) + taskID, err := this.sys.CreateTask(req.AudioURL, req.Language, req.EnableSpeaker, req.CallbackURL) if err != nil { return nil, fmt.Errorf("microsoft speech create task: %w", err) } @@ -53,9 +28,9 @@ func (this *microsoftTranscriber) Submit(_ context.Context, req SubmitRequest) ( } func (this *microsoftTranscriber) Query(_ context.Context, taskID, _ string) (*QueryResult, error) { - status, contexts, err := speech.QueryTask(taskID) + status, contexts, err := this.sys.QueryTask(taskID) switch status { - case speech.StatusSuccess: + case msspeech.StatusSuccess: out := make([]*pb.ContextStruct, 0, len(contexts)) for _, c := range contexts { out = append(out, &pb.ContextStruct{ @@ -66,7 +41,7 @@ func (this *microsoftTranscriber) Query(_ context.Context, taskID, _ string) (*Q }) } return &QueryResult{Status: StatusSuccess, Contexts: out}, nil - case speech.StatusRunning: + case msspeech.StatusRunning: return &QueryResult{Status: StatusRunning}, nil default: return &QueryResult{Status: StatusFailed}, fmt.Errorf("microsoft speech query failed: %w", err) @@ -74,28 +49,22 @@ func (this *microsoftTranscriber) Query(_ context.Context, taskID, _ string) (*Q } // Azure Translator -type microsoftTranslator struct{} - -func (this *microsoftTranslator) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Microsoft +type microsoftTranslator struct { + sys mstranslate.ISys } func (this *microsoftTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { - return translate.Translate(ctx, toMicrosoftTranslateLang(from), toMicrosoftTranslateLang(to), texts) + return this.sys.Translate(ctx, toMicrosoftTranslateLang(from), toMicrosoftTranslateLang(to), texts) } -// Azure OpenAI 总结(OpenAI 兼容端点) -type microsoftSummarizer struct { +// openaiSummarizer OpenAI 兼容端点总结(openai/qwen/deepseek/azure openai…共用)。 +type openaiSummarizer struct { sys openai.ISys } -func (this *microsoftSummarizer) Name() pb.TranscribeService { - return pb.TranscribeService_TranscribeService_Microsoft -} - -func (this *microsoftSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) { +func (this *openaiSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) { if this.sys == nil { - return "", fmt.Errorf("microsoft summarizer not initialized") + return "", fmt.Errorf("openai-compatible summarizer not initialized") } msgs := make([]openai.ChatReq, 0, len(messages)) for _, m := range messages { @@ -106,7 +75,7 @@ func (this *microsoftSummarizer) Chat(ctx context.Context, messages []ChatMessag return "", err } if resp == nil { - return "", fmt.Errorf("microsoft(azure openai) chat: empty response") + return "", fmt.Errorf("openai-compatible chat: empty response") } return resp.Content, nil } diff --git a/apps/services/modules/echomeet/provider_registry.go b/apps/services/modules/echomeet/provider_registry.go new file mode 100644 index 00000000..8977ee54 --- /dev/null +++ b/apps/services/modules/echomeet/provider_registry.go @@ -0,0 +1,229 @@ +package echomeet + +import ( + "fmt" + "strings" + + alitranslate "yunyan/sys/aliyun/translate" + + "yunyan/sys/aliyun/filetrans" + "yunyan/sys/bytedance/audomodel" + bdtranslate "yunyan/sys/bytedance/translate" + "yunyan/sys/doubao" + "yunyan/sys/google/gemini" + gspeech "yunyan/sys/google/speech" + gtranslate "yunyan/sys/google/translate" + msspeech "yunyan/sys/microsoft/speech" + mstranslate "yunyan/sys/microsoft/translate" + "yunyan/sys/openai" +) + +// 服务池 provider 标识 → 三类能力实例的构造注册表。 +// 字段 key 与 console 服务商模板 seed(modules/console/api_svctemplate.go)严格对齐; +// 关键凭据缺失直接报错(fail-closed),不让空凭据实例静默失败在业务深处。 + +// need 取必填字段,缺失报错。 +func need(f map[string]string, provider, key string) (string, error) { + v := strings.TrimSpace(f[key]) + if v == "" { + return "", fmt.Errorf("%s 缺少必填字段 %s", provider, key) + } + return v, nil +} + +// buildTranscriber 构建识别(录音文件转写)实例。 +func buildTranscriber(svc *resolvedSvc) (Transcriber, error) { + f := svc.Fields + switch strings.ToLower(svc.Provider) { + case "bytedance": + appid, err := need(f, "bytedance", "appid") + if err != nil { + return nil, err + } + token, err := need(f, "bytedance", "token") + if err != nil { + return nil, err + } + sys, err := audomodel.NewSys(audomodel.SetAppID(appid), audomodel.SetToken(token)) + if err != nil { + return nil, err + } + return &byteDanceTranscriber{sys: sys}, nil + case "alibaba": + key, err := need(f, "alibaba", "api_key") + if err != nil { + return nil, err + } + sys, err := filetrans.NewSys(filetrans.SetApiKey(key)) + if err != nil { + return nil, err + } + return &aliTranscriber{sys: sys}, nil + case "google": + sys, err := newGoogleSpeech(f) + if err != nil { + return nil, err + } + return &googleTranscriber{sys: sys}, nil + case "azure": + key, err := need(f, "azure", "subscription_key") + if err != nil { + return nil, err + } + sys, err := msspeech.NewSys(msspeech.SetKey(key), msspeech.SetRegion(f["region"])) + if err != nil { + return nil, err + } + return µsoftTranscriber{sys: sys}, nil + default: + return nil, fmt.Errorf("未支持的识别服务商: %s", svc.Provider) + } +} + +// newGoogleSpeech Google 语音客户端:json 字段既可存服务账号 JSON 内容({ 开头)也可存文件路径。 +func newGoogleSpeech(f map[string]string) (gspeech.ISys, error) { + j, err := need(f, "google", "json") + if err != nil { + return nil, err + } + if strings.HasPrefix(strings.TrimSpace(j), "{") { + return gspeech.NewSys(gspeech.SetJsonContent([]byte(j))) + } + return gspeech.NewSys(gspeech.SetJsonPath(j)) +} + +// buildTranslator 构建翻译实例。 +func buildTranslator(svc *resolvedSvc) (Translator, error) { + f := svc.Fields + switch strings.ToLower(svc.Provider) { + case "doubao", "volcengine", "bytedance": // 火山翻译 + ak, err := need(f, "volc", "access_key") + if err != nil { + return nil, err + } + sk, err := need(f, "volc", "secret_key") + if err != nil { + return nil, err + } + opts := []bdtranslate.Option{bdtranslate.SetAccessKey(ak), bdtranslate.SetSecretKey(sk)} + if f["region"] != "" { + opts = append(opts, bdtranslate.SetRegion(f["region"])) + } + sys, err := bdtranslate.NewSys(opts...) + if err != nil { + return nil, err + } + return &byteDanceTranslator{sys: sys}, nil + case "alibaba": + ak, err := need(f, "alibaba", "access_key_id") + if err != nil { + return nil, err + } + sk, err := need(f, "alibaba", "access_key_secret") + if err != nil { + return nil, err + } + sys, err := alitranslate.NewSys(alitranslate.SetAccessKeyId(ak), alitranslate.SetAccessKeySecret(sk)) + if err != nil { + return nil, err + } + return &aliTranslator{sys: sys}, nil + case "google": + j, err := need(f, "google", "json") + if err != nil { + return nil, err + } + var sys gtranslate.ISys + if strings.HasPrefix(strings.TrimSpace(j), "{") { + sys, err = gtranslate.NewSys(gtranslate.SetJsonContent([]byte(j))) + } else { + sys, err = gtranslate.NewSys(gtranslate.SetJsonPath(j)) + } + if err != nil { + return nil, err + } + return &googleTranslator{sys: sys}, nil + case "azure": + key, err := need(f, "azure", "subscription_key") + if err != nil { + return nil, err + } + sys, err := mstranslate.NewSys(mstranslate.SetKey(key), mstranslate.SetRegion(f["region"])) + if err != nil { + return nil, err + } + return µsoftTranslator{sys: sys}, nil + default: + return nil, fmt.Errorf("未支持的翻译服务商: %s", svc.Provider) + } +} + +// openAICompatBase 各 OpenAI 兼容服务商的默认接口地址(服务字段 base_url 可覆盖)。 +var openAICompatBase = map[string]string{ + "qwen": "https://dashscope.aliyuncs.com/compatible-mode/v1", + "deepseek": "https://api.deepseek.com/v1", +} + +// buildSummarizer 构建总结(大模型)实例。 +// doubao 走豆包 SDK、google 走 Gemini,其余(openai/qwen/deepseek/azure 兼容端点…)统一走 OpenAI 兼容协议。 +func buildSummarizer(svc *resolvedSvc) (Summarizer, error) { + f := svc.Fields + provider := strings.ToLower(svc.Provider) + switch provider { + case "doubao", "volcengine": + key, err := need(f, "doubao", "api_key") + if err != nil { + return nil, err + } + model := strings.TrimSpace(f["endpoint_id"]) + if model == "" { + model = strings.TrimSpace(f["model"]) + } + if model == "" { + return nil, fmt.Errorf("doubao 缺少 endpoint_id/model") + } + sys, err := doubao.NewSys(doubao.SetApikey(key), doubao.SetModel(model)) + if err != nil { + return nil, err + } + return &doubaoSummarizer{sys: sys}, nil + case "google", "gemini": + key, err := need(f, "gemini", "api_key") + if err != nil { + return nil, err + } + model, err := need(f, "gemini", "model") + if err != nil { + return nil, err + } + sys, err := gemini.NewSys(gemini.SetApikey(key), gemini.SetModel(model), gemini.SetEndpoint(f["endpoint"])) + if err != nil { + return nil, err + } + return &geminiSummarizer{sys: sys}, nil + case "anthropic": + return nil, fmt.Errorf("anthropic 暂无对应 sys 客户端,请改用 OpenAI 兼容代理接入") + default: // openai / qwen / deepseek / azure 等 OpenAI 兼容端点 + key, err := need(f, provider, "api_key") + if err != nil { + return nil, err + } + model, err := need(f, provider, "model") + if err != nil { + return nil, err + } + base := strings.TrimSpace(f["base_url"]) + if base == "" { + base = openAICompatBase[provider] + } + opts := []openai.Option{openai.SetToken(key), openai.SetModel(model), openai.SetMaxfunccall(1)} + if base != "" { + opts = append(opts, openai.SetBaseURL(base)) + } + sys, err := openai.NewSys(opts...) + if err != nil { + return nil, err + } + return &openaiSummarizer{sys: sys}, nil + } +} diff --git a/apps/services/modules/echomeet/providers.go b/apps/services/modules/echomeet/providers.go index 096bc3bc..36e24ee3 100644 --- a/apps/services/modules/echomeet/providers.go +++ b/apps/services/modules/echomeet/providers.go @@ -2,24 +2,47 @@ package echomeet import ( "context" + "encoding/json" + "fmt" + "os" + "strings" + "sync" + + "yunyan/comm" "yunyan/lego/core" "yunyan/lego/core/cbase" + "yunyan/lego/sys/cron" "yunyan/pb" - "fmt" - "strings" + natssys "yunyan/sys/nats" + + gonats "github.com/nats-io/nats.go" ) -// providersComp 服务聚合组件: -// - Init 阶段按 echomeet 模块配置初始化字节/阿里/Google/微软四套单元服务(转写+翻译+总结), -// 并把它们组合成「按服务商成套」的 provider 集合; -// - 运行期把转写/翻译/总结三类能力按 ServiceType 路由到同一服务商的 provider。 +// providersComp 会议记录服务编排消费组件。 +// +// 不再是「yaml 四套凭据 + 一会一服务商」:三段(识别/翻译/总结)各自由后台编排 +// (console 共享库 echomeet_orch + 服务池 svc_config)决定—— +// - 识别/翻译:按 Priority 升序 + 语言支持过滤选路,客户端不可指定; +// - 总结:后台默认模型(IsDefault),作用域开关 LLMClientChoice 打开时客户端可在 +// ClientVisible 且 Enabled 的服务里指定(starttask/summary 的 summary_svc_id)。 +// +// 编排未配置即报错(fail-closed),提示去后台「会议记录服务」页配置,不回退任何旧模式。 +// 快照整体替换(RWMutex),重载途径:启动加载 + NATS ConfigChanged(echomeet_orch/third_svc) + 10 分钟 cron 兜底。 type providersComp struct { cbase.ModuleCompBase module *Echomeet - transcribers map[pb.TranscribeService]Transcriber - translators map[pb.TranscribeService]Translator - summarizers map[pb.TranscribeService]Summarizer + // 部署身份(env 注入,与 api 服务 LoadOrSeedServiceConfig 同源)。 + appName string + regionCode string // APP_REGION 字符串码(app_registry 用) + region int32 // pb.Region 数值(编排表用) + encKey string // FIELD_ENCRYPT_KEY,服务池加密字段解密 + + mu sync.RWMutex + snap *orchSnapshot + transcribers map[string]Transcriber // key=svc_id + translators map[string]Translator + summarizers map[string]Summarizer } // 任务状态常量,对齐底层 sys 包 @@ -41,7 +64,7 @@ type SubmitRequest struct { CallbackData string } -// SubmitResult 转写提交结果。Done=true 时同步完成(仅 ByteDance flash),直接取 Contexts; +// SubmitResult 转写提交结果。Done=true 时同步完成(如字节短音频 flash),直接取 Contexts; // 否则需要后续调用 Query 拉取异步结果。 type SubmitResult struct { Done bool @@ -57,8 +80,7 @@ type QueryResult struct { } // ChatMessage 对齐 sys/doubao / sys/google/gemini 的统一消息结构。 -// Images 为可选的图片 url 列表(多模态总结用):非空时该条消息以「文本 + 图片」的多模态形式 -// 发给模型;为空时退化为纯文本,行为与以前一致。要求所配模型支持视觉输入。 +// Images 为可选的图片 url 列表(多模态总结用)。 type ChatMessage struct { Role string Content string @@ -67,21 +89,17 @@ type ChatMessage struct { // Transcriber 转写 provider 接口 type Transcriber interface { - Name() pb.TranscribeService - SupportsLanguage(bcp47 string) bool Submit(ctx context.Context, req SubmitRequest) (*SubmitResult, error) Query(ctx context.Context, taskID, logID string) (*QueryResult, error) } // Translator 翻译 provider 接口 type Translator interface { - Name() pb.TranscribeService Translate(ctx context.Context, from, to string, texts []string) ([]string, error) } // Summarizer AI 总结 provider 接口 type Summarizer interface { - Name() pb.TranscribeService Chat(ctx context.Context, messages []ChatMessage) (string, error) } @@ -90,159 +108,257 @@ type Summarizer interface { func (this *providersComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, options core.IModuleOptions) (err error) { this.ModuleCompBase.Init(service, module, comp, options) this.module = module.(*Echomeet) - opt := options.(*Options) - - // 按服务商成套初始化:每套各自完成转写/翻译/总结三个单元服务的装配。 - bdTranscriber, bdTranslator, bdSummarizer := newByteDanceProviders(opt.ByteDance) - aliTranscriber, aliTranslator, aliSummarizer := newAliProviders(opt.Ali) - ggTranscriber, ggTranslator, ggSummarizer := newGoogleProviders(opt.Google) - msTranscriber, msTranslator, msSummarizer := newMicrosoftProviders(opt.Microsoft) - - this.transcribers = map[pb.TranscribeService]Transcriber{ - pb.TranscribeService_TranscribeService_ByteDance: bdTranscriber, - pb.TranscribeService_TranscribeService_Ali: aliTranscriber, - pb.TranscribeService_TranscribeService_Google: ggTranscriber, - pb.TranscribeService_TranscribeService_Microsoft: msTranscriber, - } - this.translators = map[pb.TranscribeService]Translator{ - pb.TranscribeService_TranscribeService_ByteDance: bdTranslator, - pb.TranscribeService_TranscribeService_Ali: aliTranslator, - pb.TranscribeService_TranscribeService_Google: ggTranslator, - pb.TranscribeService_TranscribeService_Microsoft: msTranslator, - } - this.summarizers = map[pb.TranscribeService]Summarizer{ - pb.TranscribeService_TranscribeService_ByteDance: bdSummarizer, - pb.TranscribeService_TranscribeService_Ali: aliSummarizer, - pb.TranscribeService_TranscribeService_Google: ggSummarizer, - pb.TranscribeService_TranscribeService_Microsoft: msSummarizer, - } - this.module.Infof("echomeet providers 初始化完成 DefaultService=%s", opt.DefaultService) + // 部署身份由 .env 注入(APP_NAME/APP_REGION/FIELD_ENCRYPT_KEY),与 api 服务同源。 + this.appName = os.Getenv("APP_NAME") + this.regionCode = os.Getenv("APP_REGION") + this.region = comm.RegionFromCode(this.regionCode) + this.encKey = os.Getenv("FIELD_ENCRYPT_KEY") + this.snap = &orchSnapshot{Services: map[string]*resolvedSvc{}} + this.transcribers = map[string]Transcriber{} + this.translators = map[string]Translator{} + this.summarizers = map[string]Summarizer{} return } -// ==================== 服务商解析 ==================== +func (this *providersComp) Start() (err error) { + if err = this.ModuleCompBase.Start(); err != nil { + return + } + go this.Reload() // 异步首载(共享库在海外,别阻塞启动) + this.subscribeConfigChanged() + cron.AddFunc("0 */10 * * * ?", func() { this.Reload() }) // 定时兜底,广播丢失也能收敛 + this.module.Infof("echomeet 编排组件启动 app=%s region=%s(%d)", this.appName, this.regionCode, this.region) + return +} -// parseServiceType 把配置里的服务商名称解析为枚举;空 / auto / 未识别返回 Auto。 -func parseServiceType(s string) pb.TranscribeService { - switch strings.ToLower(strings.TrimSpace(s)) { - case "bytedance", "byte", "字节": - return pb.TranscribeService_TranscribeService_ByteDance - case "ali", "aliyun", "阿里", "阿里云": - return pb.TranscribeService_TranscribeService_Ali - case "google", "谷歌": - return pb.TranscribeService_TranscribeService_Google - case "microsoft", "azure", "微软": - return pb.TranscribeService_TranscribeService_Microsoft - default: - return pb.TranscribeService_TranscribeService_Auto +// subscribeConfigChanged 订阅 console 配置变更广播:编排本身或服务池(凭据/区域覆盖)变了都重载。 +func (this *providersComp) subscribeConfigChanged() { + conn := natssys.Conn() + if conn == nil { + this.module.Warnf("echomeet 编排: NATS 未就绪,跳过订阅(cron 定时兜底)") + return + } + _, err := conn.Subscribe(comm.Nats_ConfigChangedSubject, func(msg *gonats.Msg) { + var ev comm.ConfigChangedEvent + if err := json.Unmarshal(msg.Data, &ev); err != nil { + return + } + switch ev.Kind { + case comm.ConfigKindEchomeetOrch, comm.ConfigKindThirdSvc: + this.module.Infof("echomeet 编排: 收到配置变更(kind=%s action=%s),重载", ev.Kind, ev.Action) + this.Reload() + } + }) + if err != nil { + this.module.Warnf("echomeet 编排: 订阅配置变更失败: %v", err) } } -// ResolveServiceType 解析本次会议最终使用的服务商。优先级: -// 1. 请求显式指定 ServiceType → 直接采用(覆盖配置); -// 2. 模块配置 DefaultService 指定了服务商 → 采用配置; -// 3. 默认模式(以前的方案):按源语言自动路由——字节支持该语言走字节,否则走阿里。 -// -// 解析结果一旦确定,会议撰写、翻译、总结三步都走同一服务商。 -func (this *providersComp) ResolveServiceType(svcType pb.TranscribeService, language string) pb.TranscribeService { - if svcType != pb.TranscribeService_TranscribeService_Auto && svcType != pb.TranscribeService_TranscribeService_Unknown { - return svcType +// Reload 全量重载编排快照并重建 provider 实例。失败保留旧快照(好过清空导致全部会议失败)。 +func (this *providersComp) Reload() { + snap, err := loadOrchSnapshot(this.appName, this.region, this.regionCode, this.encKey) + if err != nil { + this.module.Errorf("echomeet 编排: 加载失败,保留现有快照: %v", err) + return } - if d := parseServiceType(this.module.options.DefaultService); d != pb.TranscribeService_TranscribeService_Auto { - return d + transcribers := map[string]Transcriber{} + translators := map[string]Translator{} + summarizers := map[string]Summarizer{} + build := func(list []*comm.EchoOrchestration, kind int32) { + for _, r := range list { + svc := snap.Services[r.SvcId] + switch kind { + case comm.EchoKindASR: + if _, ok := transcribers[r.SvcId]; ok { + continue + } + t, err := buildTranscriber(svc) + if err != nil { + this.module.Errorf("echomeet 编排: 构建识别服务 %s(%s) 失败已跳过: %v", svc.SvcId, svc.Provider, err) + continue + } + transcribers[r.SvcId] = t + case comm.EchoKindMT: + if _, ok := translators[r.SvcId]; ok { + continue + } + t, err := buildTranslator(svc) + if err != nil { + this.module.Errorf("echomeet 编排: 构建翻译服务 %s(%s) 失败已跳过: %v", svc.SvcId, svc.Provider, err) + continue + } + translators[r.SvcId] = t + case comm.EchoKindLLM: + if _, ok := summarizers[r.SvcId]; ok { + continue + } + s, err := buildSummarizer(svc) + if err != nil { + this.module.Errorf("echomeet 编排: 构建总结服务 %s(%s) 失败已跳过: %v", svc.SvcId, svc.Provider, err) + continue + } + summarizers[r.SvcId] = s + } + } } - if _, ok := toByteDanceLang(language); ok { - return pb.TranscribeService_TranscribeService_ByteDance + build(snap.ASR, comm.EchoKindASR) + build(snap.MT, comm.EchoKindMT) + build(snap.LLM, comm.EchoKindLLM) + + this.mu.Lock() + this.snap = snap + this.transcribers = transcribers + this.translators = translators + this.summarizers = summarizers + this.mu.Unlock() + this.module.Infof("echomeet 编排: 重载完成 识别=%d 翻译=%d 总结=%d (llm_client_choice=%v)", + len(snap.ASR), len(snap.MT), len(snap.LLM), snap.Setting.LLMClientChoice) +} + +// errOrchNotConfigured 统一的未配置提示(fail-closed,不回退旧 yaml 模式)。 +func errOrchNotConfigured(what string) error { + return fmt.Errorf("会议记录%s服务未配置或不可用,请在后台「会议记录服务」页配置", what) +} + +// ==================== 选路 ==================== + +// PickASR 识别选路:按后台 Priority 升序,跳过不支持源语言的服务(languages 字段过滤), +// 选第一个可用的。返回 (svc_id, 实例, 回调地址)。 +func (this *providersComp) PickASR(language string) (svcId string, t Transcriber, callbackURL string, err error) { + this.mu.RLock() + defer this.mu.RUnlock() + for _, r := range this.snap.ASR { + svc := this.snap.Services[r.SvcId] + inst := this.transcribers[r.SvcId] + if svc == nil || inst == nil { + continue + } + if !svcSupportsLanguage(svc.Languages, language) { + continue + } + return r.SvcId, inst, svc.CallbackURL, nil } - return pb.TranscribeService_TranscribeService_Ali + return "", nil, "", errOrchNotConfigured("识别(支持语言 " + language + " 的)") } -// transcribeFallbackOrder 转写兜底顺序:字节的 SupportsLanguage 有真实语言表,最可信,排首位。 -var transcribeFallbackOrder = []pb.TranscribeService{ - pb.TranscribeService_TranscribeService_ByteDance, - pb.TranscribeService_TranscribeService_Ali, - pb.TranscribeService_TranscribeService_Microsoft, - pb.TranscribeService_TranscribeService_Google, +// PickMT 翻译选路:按后台 Priority 升序(服务配了 languages 则过滤,未配不限)。 +func (this *providersComp) PickMT(language string) (svcId string, t Translator, err error) { + this.mu.RLock() + defer this.mu.RUnlock() + for _, r := range this.snap.MT { + svc := this.snap.Services[r.SvcId] + inst := this.translators[r.SvcId] + if svc == nil || inst == nil { + continue + } + if !svcSupportsLanguage(svc.Languages, language) { + continue + } + return r.SvcId, inst, nil + } + return "", nil, errOrchNotConfigured("翻译") } -// pickTranscribeService 纯函数:若 resolved 的转写引擎不支持 language,则按 transcribeFallbackOrder -// 选第一个支持的服务商。返回 (选中的服务商, 是否改道, 选中的服务商是否真的支持该语言)。 -func pickTranscribeService(transcribers map[pb.TranscribeService]Transcriber, resolved pb.TranscribeService, language string) (svc pb.TranscribeService, changed bool, supported bool) { - if t, ok := transcribers[resolved]; ok && t != nil && t.SupportsLanguage(language) { - return resolved, false, true +// PickLLM 总结选路:clientSvcId 非空且后台开关允许客户端指定时,在 ClientVisible 且已构建 +// 的服务里精确匹配;否则默认模型(IsDefault);再否则按 Priority 第一个可用的。 +func (this *providersComp) PickLLM(clientSvcId string) (svcId string, s Summarizer, err error) { + this.mu.RLock() + defer this.mu.RUnlock() + if clientSvcId != "" && this.snap.Setting.LLMClientChoice { + for _, r := range this.snap.LLM { + if r.SvcId == clientSvcId && r.ClientVisible && this.summarizers[r.SvcId] != nil { + return r.SvcId, this.summarizers[r.SvcId], nil + } + } + this.module.Warnf("echomeet 编排: 客户端指定的总结服务 %s 不在可选列表,回退默认", clientSvcId) } - for _, s := range transcribeFallbackOrder { - if s == resolved { + var first *comm.EchoOrchestration + for _, r := range this.snap.LLM { + if this.summarizers[r.SvcId] == nil { continue } - if t, ok := transcribers[s]; ok && t != nil && t.SupportsLanguage(language) { - return s, true, true + if r.IsDefault { + return r.SvcId, this.summarizers[r.SvcId], nil } + if first == nil { + first = r + } + } + if first != nil { + return first.SvcId, this.summarizers[first.SvcId], nil } - return resolved, false, false + return "", nil, errOrchNotConfigured("总结大模型") } -// ResolveTranscribeService 在 ResolveServiceType 之上叠加「所选服务商是否支持该源语言」的校验, -// 不支持时换到第一个支持的服务商。 -// -// 为什么需要:ResolveServiceType 只看请求/配置/字节语言表,一旦 DefaultService 固定成某家, -// 就会把引擎不认识的语种也硬塞进去。典型事故是西语被送进阿里 paraformer-v2(该模型不支持西语), -// 引擎静默返回 SUCCESS_WITH_NO_VALID_FRAGMENT,表现得像「音频没声音」,极难排查。 -// 只作用于**转写**选路;翻译/总结仍沿用记录已定的 ServiceType(三段同源不变)。 -func (this *providersComp) ResolveTranscribeService(svcType pb.TranscribeService, language string) pb.TranscribeService { - resolved := this.ResolveServiceType(svcType, language) - picked, changed, supported := pickTranscribeService(this.transcribers, resolved, language) - if changed { - this.module.Warnf("转写服务 %s 不支持语言 %s,改用 %s", ServiceTypeName(resolved), language, ServiceTypeName(picked)) - } else if !supported { - this.module.Warnf("无转写服务支持语言 %s,维持 %s(转写可能失败)", language, ServiceTypeName(picked)) - } - return picked -} - -// ==================== 工厂方法 ==================== - -// GetTranscriber 取转写 provider;svcType=Auto/Unknown 时先 ResolveServiceType。 -func (this *providersComp) GetTranscriber(svcType pb.TranscribeService, language string) (Transcriber, error) { - resolved := this.ResolveServiceType(svcType, language) - if p, ok := this.transcribers[resolved]; ok && p != nil { - return p, nil - } - return nil, fmt.Errorf("transcriber not registered: service=%s", ServiceTypeName(resolved)) -} - -// GetTranslator 取翻译 provider;与转写同服务商,svcType=Auto/Unknown 时先 ResolveServiceType。 -func (this *providersComp) GetTranslator(svcType pb.TranscribeService, language string) (Translator, error) { - resolved := this.ResolveServiceType(svcType, language) - if p, ok := this.translators[resolved]; ok && p != nil { - return p, nil - } - return nil, fmt.Errorf("translator not registered: service=%s", ServiceTypeName(resolved)) -} - -// GetSummarizer 取总结 provider;与转写同服务商,svcType=Auto/Unknown 时先 ResolveServiceType。 -func (this *providersComp) GetSummarizer(svcType pb.TranscribeService, language string) (Summarizer, error) { - resolved := this.ResolveServiceType(svcType, language) - if p, ok := this.summarizers[resolved]; ok && p != nil { - return p, nil - } - return nil, fmt.Errorf("summarizer not registered: service=%s", ServiceTypeName(resolved)) -} - -// ServiceTypeName 返回服务类型的友好名称,便于日志输出 -func ServiceTypeName(svcType pb.TranscribeService) string { - switch svcType { - case pb.TranscribeService_TranscribeService_ByteDance: - return "ByteDance" - case pb.TranscribeService_TranscribeService_Ali: - return "Ali" - case pb.TranscribeService_TranscribeService_Auto: - return "Auto" - case pb.TranscribeService_TranscribeService_Google: - return "Google" - case pb.TranscribeService_TranscribeService_Microsoft: - return "Microsoft" - default: - return fmt.Sprintf("Unknown(%d)", int32(svcType)) +// ==================== 按 svc_id 取实例(record 存量路由:轮询/翻译/总结) ==================== + +func (this *providersComp) GetTranscriber(svcId string) (Transcriber, error) { + this.mu.RLock() + defer this.mu.RUnlock() + if t, ok := this.transcribers[svcId]; ok && t != nil { + return t, nil + } + return nil, fmt.Errorf("识别服务 %s 已不在编排中(可能被后台移除)", svcId) +} + +func (this *providersComp) GetTranslator(svcId string) (Translator, error) { + this.mu.RLock() + defer this.mu.RUnlock() + if t, ok := this.translators[svcId]; ok && t != nil { + return t, nil + } + return nil, fmt.Errorf("翻译服务 %s 已不在编排中(可能被后台移除)", svcId) +} + +func (this *providersComp) GetSummarizer(svcId string) (Summarizer, error) { + this.mu.RLock() + defer this.mu.RUnlock() + if s, ok := this.summarizers[svcId]; ok && s != nil { + return s, nil } + return nil, fmt.Errorf("总结服务 %s 已不在编排中(可能被后台移除)", svcId) +} + +// ASRProvider 返回某识别服务的 provider 标识(bytedance/alibaba/google/azure), +// starttask 用它判断是否走字节短音频专用限流队列。 +func (this *providersComp) ASRProvider(svcId string) string { + this.mu.RLock() + defer this.mu.RUnlock() + if svc := this.snap.Services[svcId]; svc != nil { + return strings.ToLower(svc.Provider) + } + return "" +} + +// ASRCallbackURL 取某识别服务拼好的回调地址(tasks 提交异步转写时用)。 +func (this *providersComp) ASRCallbackURL(svcId string) string { + this.mu.RLock() + defer this.mu.RUnlock() + if svc := this.snap.Services[svcId]; svc != nil { + return svc.CallbackURL + } + return "" +} + +// SummaryServices 客户端可选总结服务列表(echomeet_getsummaryservices 接口用)。 +// 开关关闭时仅返回默认项(客户端只读展示)。 +func (this *providersComp) SummaryServices() (clientChoice bool, items []*pb.EchomeetSummaryServiceItem) { + this.mu.RLock() + defer this.mu.RUnlock() + clientChoice = this.snap.Setting.LLMClientChoice + for _, r := range this.snap.LLM { + svc := this.snap.Services[r.SvcId] + if svc == nil || this.summarizers[r.SvcId] == nil { + continue + } + if !r.IsDefault && (!clientChoice || !r.ClientVisible) { + continue + } + items = append(items, &pb.EchomeetSummaryServiceItem{ + SvcId: r.SvcId, + Name: svc.Name, + IsDefault: r.IsDefault, + }) + } + return } diff --git a/apps/services/modules/echomeet/tasks.go b/apps/services/modules/echomeet/tasks.go index 5b1a4723..9a84b036 100644 --- a/apps/services/modules/echomeet/tasks.go +++ b/apps/services/modules/echomeet/tasks.go @@ -10,8 +10,6 @@ import ( "yunyan/lego/sys/mysql" redissys "yunyan/lego/sys/redis" "yunyan/pb" - "yunyan/sys/aliyun/filetrans" - "yunyan/sys/bytedance/audomodel" "yunyan/utils" "encoding/json" "fmt" @@ -169,33 +167,15 @@ func (this *tasksComp) SubmitShortAudioTask(task *pb.DBEchoMeetRecord) (err erro return } -// 提交阿里云音频转写任务(字节不支持的语言走此通道) -func (this *tasksComp) SubmitAliAudioTask(task *pb.DBEchoMeetRecord) (err error) { - this.module.Infof("SubmitAliAudioTask id:%d uid:%s lang:%s → 调用阿里云转写", task.Id, task.Uid, task.Formlanguage) - aliLang := toDashScopeLang(task.Formlanguage) - if aliLang == "" { - this.module.Infof("SubmitAliAudioTask id:%d 语言 %s 无DashScope映射,改用自动检测", task.Id, task.Formlanguage) - } - taskID, err := filetrans.CreateTask(task.Audiourl, aliLang, task.Isdistinguishspeaker, this.module.options.Ali.Callback) - if err != nil { - this.module.Errorf("SubmitAliAudioTask id:%d 提交失败 err:%v", task.Id, err) - task.State = pb.DBEchoMeetRecordState_TranscribeFail - this.module.model.saverecord(task) - return err - } - task.Taskid = taskID - task.State = pb.DBEchoMeetRecordState_Transcribing - this.module.model.saverecord(task) - this.module.Infof("SubmitAliAudioTask id:%d → 阿里云任务已提交 taskID:%s", task.Id, taskID) - return -} - -// SubmitTranscribeTask 通用异步转写:通过 providersComp 路由到 Microsoft/Google/... -// 这些 provider 的 CreateTask 都是单次远程提交(无需 Redis 速率队列),失败直接置为 TranscribeFail。 +// SubmitTranscribeTask 统一异步转写提交:按 record.AsrSvcId(后台编排选路结果)取实例, +// 单次远程提交(无需 Redis 速率队列),失败直接置为 TranscribeFail。 +// 回调地址来自编排解析(部署对外地址 + 服务 callback_path),无回调的服务商靠轮询兜底。 +// 字节实例对短音频会同步完成(Done=true)——那种情况应走 SubmitShortAudioTask 限流队列, +// 但即便走到这里也能正确落结果。 func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err error) { - this.module.Infof("SubmitTranscribeTask id:%d uid:%s service:%s lang:%s", - task.Id, task.Uid, ServiceTypeName(task.ServiceType), task.Formlanguage) - transcriber, err := this.module.providers.GetTranscriber(task.ServiceType, task.Formlanguage) + this.module.Infof("SubmitTranscribeTask id:%d uid:%s svc:%s lang:%s", + task.Id, task.Uid, task.AsrSvcId, task.Formlanguage) + transcriber, err := this.module.providers.GetTranscriber(task.AsrSvcId) if err != nil { this.module.Errorf("SubmitTranscribeTask id:%d 路由失败 err:%v", task.Id, err) task.State = pb.DBEchoMeetRecordState_TranscribeFail @@ -209,6 +189,8 @@ func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err erro EnableSpeaker: task.Isdistinguishspeaker, Seconds: task.Seconds, SizeBytes: task.Size, + CallbackURL: this.module.providers.ASRCallbackURL(task.AsrSvcId), + CallbackData: fmt.Sprintf("%d", task.Id), }) if err != nil { this.module.Errorf("SubmitTranscribeTask id:%d 提交失败 err:%v", task.Id, err) @@ -216,6 +198,10 @@ func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err erro this.module.model.saverecord(task) return err } + if result.Done { // 同步完成(字节 flash):直接走完成收尾 + this.finishSyncTranscribe(task, result.Contexts) + return + } task.Taskid = result.TaskID task.Logid = result.LogID task.State = pb.DBEchoMeetRecordState_Transcribing @@ -224,25 +210,6 @@ func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err erro return } -// 提交长音频任务 -func (this *tasksComp) SubmitLongAudioTask(task *pb.DBEchoMeetRecord) (err error) { - this.module.Infof("SubmitLongAudioTask id:%d uid:%s lang:%s → 调用字节长音频", task.Id, task.Uid, task.Formlanguage) - bdLang, _ := toByteDanceLang(task.Formlanguage) - taskID, logID, err := audomodel.CreateTask(task.Uid, task.Audiourl, task.Isdistinguishspeaker, bdLang, this.module.options.ByteDance.Callback, fmt.Sprintf("%d", task.Id)) - if err != nil { - this.module.Errorf("SubmitLongAudioTask id:%d 提交失败 err:%v", task.Id, err) - task.State = pb.DBEchoMeetRecordState_TranscribeFail - this.module.model.saverecord(task) - return err - } - task.Taskid = taskID - task.Logid = logID - task.State = pb.DBEchoMeetRecordState_Transcribing - this.module.model.saverecord(task) - this.module.Infof("SubmitLongAudioTask id:%d → 字节任务已提交 taskID:%s logID:%s", task.Id, taskID, logID) - return -} - // 提交AI任务 func (this *tasksComp) SubmitAITask(task *pb.DBEchoMeetRecord) (err error) { this.module.Infof("SubmitAITask id:%d uid:%s → 进入AI等待队列", task.Id, task.Uid) @@ -271,11 +238,24 @@ func (this *tasksComp) ShortAudioProcess(task interface{}) { if !ok || rec == nil { return } - this.module.Infof("ShortAudioProcess id:%d uid:%s lang:%s → 开始转写", rec.Id, rec.Uid, rec.Formlanguage) - bdLang, _ := toByteDanceLang(rec.Formlanguage) - statusCode, logID, contexts, err := audomodel.RecognizeFlash(rec.Uid, rec.Audiourl, rec.Isdistinguishspeaker, bdLang) + this.module.Infof("ShortAudioProcess id:%d uid:%s svc:%s lang:%s → 开始转写", rec.Id, rec.Uid, rec.AsrSvcId, rec.Formlanguage) + var result *SubmitResult + transcriber, err := this.module.providers.GetTranscriber(rec.AsrSvcId) + if err == nil { + result, err = transcriber.Submit(context.Background(), SubmitRequest{ + Uid: rec.Uid, + AudioURL: rec.Audiourl, + Language: rec.Formlanguage, + EnableSpeaker: rec.Isdistinguishspeaker, + Seconds: rec.Seconds, + SizeBytes: rec.Size, + }) + if err == nil && (result == nil || !result.Done) { + err = fmt.Errorf("短音频通道期望同步完成,实际返回异步结果") + } + } if err != nil { - this.module.Errorf("ShortAudioProcess id:%d 转写失败 statusCode:%s logID:%s err:%v", rec.Id, statusCode, logID, err) + this.module.Errorf("ShortAudioProcess id:%d 转写失败 err:%v", rec.Id, err) rec.State = pb.DBEchoMeetRecordState_TranscribeFail this.module.model.saverecord(rec) if user, err := this.module.model.getuser(rec.Uid); err == nil { @@ -310,31 +290,32 @@ func (this *tasksComp) ShortAudioProcess(task interface{}) { _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() return } - this.module.Infof("ShortAudioProcess id:%d → 转写完成,共 %d 句", rec.Id, len(contexts)) + this.module.Infof("ShortAudioProcess id:%d → 转写完成,共 %d 句", rec.Id, len(result.Contexts)) + this.finishSyncTranscribe(rec, result.Contexts) + // 最小实现:完成即移除在执行集合 + procKey := this.keyShortProc() + _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() +} + +// finishSyncTranscribe 同步转写完成(字节 flash 等 Done=true 通道)的收尾: +// 补齐说话人/原文 → 翻译 → 落库 → 提交 AI 总结。 +func (this *tasksComp) finishSyncTranscribe(rec *pb.DBEchoMeetRecord, contexts []*pb.ContextStruct) { personnels := make(map[string]struct{}) - pbcontexts := make([]*pb.ContextStruct, 0, len(contexts)) - for _, ctx := range contexts { - pbcontexts = append(pbcontexts, &pb.ContextStruct{ - Meetingid: rec.Id, - Content: ctx.Content, - Starttime: ctx.StartTime, - Endtime: ctx.EndTime, - Speaker: fmt.Sprintf("Speaker %s", ctx.Speaker), - }) - personnels[ctx.Speaker] = struct{}{} + for _, c := range contexts { + c.Meetingid = rec.Id + if c.Speaker != "" && !strings.HasPrefix(c.Speaker, "Speaker") { + c.Speaker = fmt.Sprintf("Speaker %s", c.Speaker) + } + personnels[c.Speaker] = struct{}{} } rec.Personnel = "" - for k, _ := range personnels { + for k := range personnels { rec.Personnel += fmt.Sprintf("[%s] ", k) } - - rec.Original = utils.ToString(pbcontexts) + rec.Original = utils.ToString(contexts) rec.State = pb.DBEchoMeetRecordState_AwaitSummarizing - this.TranslateProcess(rec, pbcontexts) + this.TranslateProcess(rec, contexts) this.module.model.saverecord(rec) - // 最小实现:完成即移除在执行集合 - procKey := this.keyShortProc() - _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() _ = this.SubmitAITask(rec) } @@ -354,7 +335,17 @@ func (this *tasksComp) TranslateProcess(rec *pb.DBEchoMeetRecord, contexts []*pb for _, item := range contexts { tests = append(tests, item.Content) } - translator, err := this.module.providers.GetTranslator(rec.ServiceType, rec.Formlanguage) + // 翻译服务:优先用记录已选路的 MtSvcId;为空(如创建时源=目标语言未选路,后来改了目标语言) + // 则现场按编排补选并回填记录。 + if rec.MtSvcId == "" { + svcId, _, perr := this.module.providers.PickMT(rec.Formlanguage) + if perr != nil { + this.module.Errorf("TranslateProcess id:%d 翻译选路失败 err:%v", rec.Id, perr) + return perr + } + rec.MtSvcId = svcId + } + translator, err := this.module.providers.GetTranslator(rec.MtSvcId) if err != nil { this.module.Errorf("TranslateProcess id:%d 取 translator 失败 err:%v", rec.Id, err) return err @@ -365,14 +356,14 @@ func (this *tasksComp) TranslateProcess(rec *pb.DBEchoMeetRecord, contexts []*pb // 判定「结果是否可用」只认长度对齐,不能只看 results==nil: // 有的 provider(如字节)出错时用具名返回值裸 return,会带回一个长度不足的非 nil 切片。 if err != nil && len(results) != len(tests) { - this.module.Errorf("TranslateProcess id:%d 翻译失败 service:%s got=%d want=%d err:%v", - rec.Id, ServiceTypeName(translator.Name()), len(results), len(tests), err) + this.module.Errorf("TranslateProcess id:%d 翻译失败 svc:%s got=%d want=%d err:%v", + rec.Id, rec.MtSvcId, len(results), len(tests), err) return err } if err != nil { // 部分失败但结果完整(provider 已用原文占位):记日志后继续,不让整篇译文作废。 - this.module.Warnf("TranslateProcess id:%d 部分句子翻译失败(已用原文占位) service:%s err:%v", - rec.Id, ServiceTypeName(translator.Name()), err) + this.module.Warnf("TranslateProcess id:%d 部分句子翻译失败(已用原文占位) svc:%s err:%v", + rec.Id, rec.MtSvcId, err) } for i, item := range contexts { if i >= len(results) { @@ -456,7 +447,20 @@ func (this *tasksComp) AIProcess(task interface{}) { _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() return } - summarizer, err := this.module.providers.GetSummarizer(rec.ServiceType, rec.Formlanguage) + // 总结服务:优先用记录已定的 LlmSvcId(默认或客户端指定);为空则按编排取默认。 + if rec.LlmSvcId == "" { + svcId, _, perr := this.module.providers.PickLLM("") + if perr != nil { + this.module.Errorf("AIProcess id:%d 总结选路失败 err:%v", rec.Id, perr) + rec.State = pb.DBEchoMeetRecordState_SummarizFail + this.module.model.saverecord(rec) + procKey := this.keyAIProc() + _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() + return + } + rec.LlmSvcId = svcId + } + summarizer, err := this.module.providers.GetSummarizer(rec.LlmSvcId) if err != nil { this.module.Errorf("AIProcess id:%d 取 summarizer 失败 err:%v", rec.Id, err) rec.State = pb.DBEchoMeetRecordState_SummarizFail @@ -465,7 +469,7 @@ func (this *tasksComp) AIProcess(task interface{}) { _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() return } - this.module.Infof("AIProcess id:%d → service:%s", rec.Id, ServiceTypeName(summarizer.Name())) + this.module.Infof("AIProcess id:%d → svc:%s", rec.Id, rec.LlmSvcId) // 注释图片:用户创建记录时填写的 imageurls(JSON 数组字符串),解析后作为多模态内容随 // user 消息发给模型,让总结结合图片(白板/PPT/截图等)。为空/解析失败时 Images 为 nil, // provider 退化为纯文本。 @@ -518,17 +522,17 @@ func (this *tasksComp) AIProcess(task interface{}) { _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() } -// PollTranscribe 主动查询第三方转写任务状态(统一入口,按 record.ServiceType 路由到对应 provider) +// PollTranscribe 主动查询第三方转写任务状态(统一入口,按 record.AsrSvcId 路由到对应 provider) // 客户端在 GetRecord/GetRecords 拿到状态为 Transcribing 的记录时驱动调用;TranscribeQueryMinInterval // 限制了查询频率。 func (this *tasksComp) PollTranscribe(rec *pb.DBEchoMeetRecord) { - transcriber, err := this.module.providers.GetTranscriber(rec.ServiceType, rec.Formlanguage) + transcriber, err := this.module.providers.GetTranscriber(rec.AsrSvcId) if err != nil { this.module.Errorf("转写轮询 id:%d 路由失败 err:%v", rec.Id, err) return } this.module.Infof("转写轮询[%s] id:%d taskID:%s logID:%s", - ServiceTypeName(transcriber.Name()), rec.Id, rec.Taskid, rec.Logid) + rec.AsrSvcId, rec.Id, rec.Taskid, rec.Logid) result, err := transcriber.Query(context.Background(), rec.Taskid, rec.Logid) if err != nil { this.module.Errorf("转写轮询 id:%d 查询失败 err:%v,状态置为失败", rec.Id, err) diff --git a/apps/services/pb/echomeet_db.pb.go b/apps/services/pb/echomeet_db.pb.go index 4fd458b6..0f52183e 100644 --- a/apps/services/pb/echomeet_db.pb.go +++ b/apps/services/pb/echomeet_db.pb.go @@ -21,65 +21,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// 转写服务类型 -type TranscribeService int32 - -const ( - TranscribeService_TranscribeService_Unknown TranscribeService = 0 // 未知(兼容旧数据) - TranscribeService_TranscribeService_ByteDance TranscribeService = 1 // 字节跳动 - TranscribeService_TranscribeService_Ali TranscribeService = 2 // 阿里云 DashScope - TranscribeService_TranscribeService_Auto TranscribeService = 3 // 自动按语言路由 - TranscribeService_TranscribeService_Google TranscribeService = 4 // Google Cloud - TranscribeService_TranscribeService_Microsoft TranscribeService = 5 // Azure -) - -// Enum value maps for TranscribeService. -var ( - TranscribeService_name = map[int32]string{ - 0: "TranscribeService_Unknown", - 1: "TranscribeService_ByteDance", - 2: "TranscribeService_Ali", - 3: "TranscribeService_Auto", - 4: "TranscribeService_Google", - 5: "TranscribeService_Microsoft", - } - TranscribeService_value = map[string]int32{ - "TranscribeService_Unknown": 0, - "TranscribeService_ByteDance": 1, - "TranscribeService_Ali": 2, - "TranscribeService_Auto": 3, - "TranscribeService_Google": 4, - "TranscribeService_Microsoft": 5, - } -) - -func (x TranscribeService) Enum() *TranscribeService { - p := new(TranscribeService) - *p = x - return p -} - -func (x TranscribeService) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (TranscribeService) Descriptor() protoreflect.EnumDescriptor { - return file_echomeet_echomeet_db_proto_enumTypes[0].Descriptor() -} - -func (TranscribeService) Type() protoreflect.EnumType { - return &file_echomeet_echomeet_db_proto_enumTypes[0] -} - -func (x TranscribeService) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use TranscribeService.Descriptor instead. -func (TranscribeService) EnumDescriptor() ([]byte, []int) { - return file_echomeet_echomeet_db_proto_rawDescGZIP(), []int{0} -} - type DBEchoMeetRecordState int32 const ( @@ -131,11 +72,11 @@ func (x DBEchoMeetRecordState) String() string { } func (DBEchoMeetRecordState) Descriptor() protoreflect.EnumDescriptor { - return file_echomeet_echomeet_db_proto_enumTypes[1].Descriptor() + return file_echomeet_echomeet_db_proto_enumTypes[0].Descriptor() } func (DBEchoMeetRecordState) Type() protoreflect.EnumType { - return &file_echomeet_echomeet_db_proto_enumTypes[1] + return &file_echomeet_echomeet_db_proto_enumTypes[0] } func (x DBEchoMeetRecordState) Number() protoreflect.EnumNumber { @@ -144,24 +85,24 @@ func (x DBEchoMeetRecordState) Number() protoreflect.EnumNumber { // Deprecated: Use DBEchoMeetRecordState.Descriptor instead. func (DBEchoMeetRecordState) EnumDescriptor() ([]byte, []int) { - return file_echomeet_echomeet_db_proto_rawDescGZIP(), []int{1} + return file_echomeet_echomeet_db_proto_rawDescGZIP(), []int{0} } // 会议记录模板 type DBEchoMeetTemplate struct { state protoimpl.MessageState `protogen:"open.v1"` - Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty" gorm:"id;primaryKey;autoIncrement json:"` //@go_tags(`gorm:"id;primaryKey;autoIncrement json:"id"`) id - Tid string `protobuf:"bytes,2,opt,name=tid,proto3" json:"tid,omitempty" gorm:"tid;index"` //@go_tags(`gorm:"tid;index"`) 模板id - Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty" gorm:"source;index"` //@go_tags(`gorm:"source;index"`) 来源 - Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty" gorm:"title"` //@go_tags(`gorm:"title"`) 标题 - Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"description"` //@go_tags(`gorm:"description"`) 描述 - Language string `protobuf:"bytes,6,opt,name=language,proto3" json:"language,omitempty" gorm:"language;index"` //@go_tags(`gorm:"language;index"`) 语言 - Ttype string `protobuf:"bytes,7,opt,name=ttype,proto3" json:"ttype,omitempty" gorm:"ttype;index"` //@go_tags(`gorm:"ttype;index"`) 类型 - Tags string `protobuf:"bytes,8,opt,name=tags,proto3" json:"tags,omitempty" gorm:"tags"` //@go_tags(`gorm:"tags"`) 标签 - Icon string `protobuf:"bytes,9,opt,name=icon,proto3" json:"icon,omitempty" gorm:"icon"` //@go_tags(`gorm:"icon"`) 图标 - Outline string `protobuf:"bytes,10,opt,name=outline,proto3" json:"outline,omitempty" gorm:"outline"` //@go_tags(`gorm:"outline"`) 大纲 - Template string `protobuf:"bytes,11,opt,name=template,proto3" json:"template,omitempty" gorm:"template"` //@go_tags(`gorm:"template"`) 模板 - Sort int32 `protobuf:"varint,12,opt,name=sort,proto3" json:"sort,omitempty" gorm:"sort;index"` //@go_tags(`gorm:"sort;index"`) 显示优先级,数值越大越靠前 + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty" gorm:"id;primaryKey;autoIncrement json:"` //@go_tags(`gorm:"id;primaryKey;autoIncrement json:"id"`) id + Tid string `protobuf:"bytes,2,opt,name=tid,proto3" json:"tid,omitempty" gorm:"tid;index"` //@go_tags(`gorm:"tid;index"`) 模板id + Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty" gorm:"source;index"` //@go_tags(`gorm:"source;index"`) 来源 + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty" gorm:"title"` //@go_tags(`gorm:"title"`) 标题 + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"description"` //@go_tags(`gorm:"description"`) 描述 + Language string `protobuf:"bytes,6,opt,name=language,proto3" json:"language,omitempty" gorm:"language;index"` //@go_tags(`gorm:"language;index"`) 语言 + Ttype string `protobuf:"bytes,7,opt,name=ttype,proto3" json:"ttype,omitempty" gorm:"ttype;index"` //@go_tags(`gorm:"ttype;index"`) 类型 + Tags string `protobuf:"bytes,8,opt,name=tags,proto3" json:"tags,omitempty" gorm:"tags"` //@go_tags(`gorm:"tags"`) 标签 + Icon string `protobuf:"bytes,9,opt,name=icon,proto3" json:"icon,omitempty" gorm:"icon"` //@go_tags(`gorm:"icon"`) 图标 + Outline string `protobuf:"bytes,10,opt,name=outline,proto3" json:"outline,omitempty" gorm:"outline"` //@go_tags(`gorm:"outline"`) 大纲 + Template string `protobuf:"bytes,11,opt,name=template,proto3" json:"template,omitempty" gorm:"template"` //@go_tags(`gorm:"template"`) 模板 + Sort int32 `protobuf:"varint,12,opt,name=sort,proto3" json:"sort,omitempty" gorm:"sort;index"` //@go_tags(`gorm:"sort;index"`) 显示优先级,数值越大越靠前 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -283,35 +224,37 @@ func (x *DBEchoMeetTemplate) GetSort() int32 { // 会议记录数据结构 type DBEchoMeetRecord struct { state protoimpl.MessageState `protogen:"open.v1"` - Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty" gorm:"id:50;primaryKey;autoIncrement json:"` //@go_tags(`gorm:"id:50;primaryKey;autoIncrement json:"id"`) 索引id - Uid string `protobuf:"bytes,2,opt,name=uid,proto3" json:"uid" gorm:"uid;index"` //@go_tags(`gorm:"uid;index" json:"uid"`) 用户ID - State DBEchoMeetRecordState `protobuf:"varint,3,opt,name=state,proto3,enum=DBEchoMeetRecordState" json:"state" gorm:"state;index"` //@go_tags(`gorm:"state;index" json:"state"`) 状态 - Creationtime int64 `protobuf:"varint,4,opt,name=creationtime,proto3" json:"creationtime" gorm:"creationtime;index"` //@go_tags(`gorm:"creationtime;index" json:"creationtime"`) 時間 - Rtype string `protobuf:"bytes,5,opt,name=rtype,proto3" json:"rtype" gorm:"rtype;index"` //@go_tags(`gorm:"rtype;index" json:"rtype"`) 类型 - Filepath string `protobuf:"bytes,6,opt,name=filepath,proto3" json:"filepath" gorm:"filepath"` //@go_tags(`gorm:"filepath" json:"filepath"`) 本地音频文件路径 - Audiourl string `protobuf:"bytes,7,opt,name=audiourl,proto3" json:"audiourl" gorm:"audiourl"` //@go_tags(`gorm:"audiourl" json:"audiourl"`) 音频url - Title string `protobuf:"bytes,8,opt,name=title,proto3" json:"title" gorm:"title"` //@go_tags(`gorm:"title" json:"title"`) 标题 - Address string `protobuf:"bytes,9,opt,name=address,proto3" json:"address" gorm:"address"` //@go_tags(`gorm:"address" json:"address"`) 地址 - Personnel string `protobuf:"bytes,10,opt,name=personnel,proto3" json:"personnel" gorm:"personnel"` //@go_tags(`gorm:"personnel" json:"personnel"`) 成员 - Tasktype int32 `protobuf:"varint,11,opt,name=tasktype,proto3" json:"tasktype" gorm:"tasktype"` //@go_tags(`gorm:"tasktype" json:"tasktype"`) 任务类型 - Taskid string `protobuf:"bytes,12,opt,name=taskid,proto3" json:"taskid" gorm:"taskid"` //@go_tags(`gorm:"taskid" json:"taskid"`) 任务id - Logid string `protobuf:"bytes,13,opt,name=logid,proto3" json:"logid" gorm:"logid"` //@go_tags(`gorm:"logid" json:"logid"`) 日志id - Isdistinguishspeaker bool `protobuf:"varint,14,opt,name=isdistinguishspeaker,proto3" json:"isdistinguishspeaker" gorm:"isdistinguishspeaker"` //@go_tags(`gorm:"isdistinguishspeaker" json:"isdistinguishspeaker"`) 是否区分说话人 - Templateid uint64 `protobuf:"varint,15,opt,name=templateid,proto3" json:"templateid" gorm:"templateid"` //@go_tags(`gorm:"templateid" json:"templateid"`) 模板id - Formlanguage string `protobuf:"bytes,16,opt,name=formlanguage,proto3" json:"formlanguage" gorm:"formlanguage"` //@go_tags(`gorm:"formlanguage" json:"formlanguage"`) 语言 - Tolanguage string `protobuf:"bytes,17,opt,name=tolanguage,proto3" json:"tolanguage" gorm:"tolanguage"` //@go_tags(`gorm:"tolanguage" json:"tolanguage"`) 目标语言 - Seconds int32 `protobuf:"varint,18,opt,name=seconds,proto3" json:"seconds" gorm:"seconds"` //@go_tags(`gorm:"seconds" json:"seconds"`) 时长 - Size int64 `protobuf:"varint,19,opt,name=size,proto3" json:"size" gorm:"column:size;type:bigint"` //@go_tags(`gorm:"column:size;type:bigint" json:"size"`) 文件大小 - Original string `protobuf:"bytes,20,opt,name=original,proto3" json:"original" gorm:"original"` //@go_tags(`gorm:"original" json:"original"`) 听写内容 - Translate string `protobuf:"bytes,21,opt,name=translate,proto3" json:"translate" gorm:"translate"` //@go_tags(`gorm:"translate" json:"translate"`) 翻译 - Overview string `protobuf:"bytes,22,opt,name=overview,proto3" json:"overview" gorm:"overview"` //@go_tags(`gorm:"overview" json:"overview"`) 短总结 - Summary string `protobuf:"bytes,23,opt,name=summary,proto3" json:"summary" gorm:"summary"` //@go_tags(`gorm:"summary" json:"summary"`) 总结 - ServiceType TranscribeService `protobuf:"varint,24,opt,name=service_type,json=serviceType,proto3,enum=TranscribeService" json:"service_type" gorm:"service_type"` //@go_tags(`gorm:"service_type" json:"service_type"`) 转写服务类型 - Starttime int64 `protobuf:"varint,25,opt,name=starttime,proto3" json:"starttime" gorm:"starttime"` //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒 - Processduration int64 `protobuf:"varint,26,opt,name=processduration,proto3" json:"processduration" gorm:"processduration"` //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒 - Lastquerytime int64 `protobuf:"varint,27,opt,name=lastquerytime,proto3" json:"lastquerytime" gorm:"lastquerytime"` //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率 - Remark string `protobuf:"bytes,28,opt,name=remark,proto3" json:"remark" gorm:"remark"` //@go_tags(`gorm:"remark" json:"remark"`) 用户备注(创建记录时填写,参与AI总结) - Imageurls string `protobuf:"bytes,29,opt,name=imageurls,proto3" json:"imageurls" gorm:"imageurls"` //@go_tags(`gorm:"imageurls" json:"imageurls"`) 注释图片url列表(JSON数组字符串,编码方式同original),作为多模态内容参与AI总结 + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty" gorm:"id:50;primaryKey;autoIncrement json:"` //@go_tags(`gorm:"id:50;primaryKey;autoIncrement json:"id"`) 索引id + Uid string `protobuf:"bytes,2,opt,name=uid,proto3" json:"uid" gorm:"uid;index"` //@go_tags(`gorm:"uid;index" json:"uid"`) 用户ID + State DBEchoMeetRecordState `protobuf:"varint,3,opt,name=state,proto3,enum=DBEchoMeetRecordState" json:"state" gorm:"state;index"` //@go_tags(`gorm:"state;index" json:"state"`) 状态 + Creationtime int64 `protobuf:"varint,4,opt,name=creationtime,proto3" json:"creationtime" gorm:"creationtime;index"` //@go_tags(`gorm:"creationtime;index" json:"creationtime"`) 時間 + Rtype string `protobuf:"bytes,5,opt,name=rtype,proto3" json:"rtype" gorm:"rtype;index"` //@go_tags(`gorm:"rtype;index" json:"rtype"`) 类型 + Filepath string `protobuf:"bytes,6,opt,name=filepath,proto3" json:"filepath" gorm:"filepath"` //@go_tags(`gorm:"filepath" json:"filepath"`) 本地音频文件路径 + Audiourl string `protobuf:"bytes,7,opt,name=audiourl,proto3" json:"audiourl" gorm:"audiourl"` //@go_tags(`gorm:"audiourl" json:"audiourl"`) 音频url + Title string `protobuf:"bytes,8,opt,name=title,proto3" json:"title" gorm:"title"` //@go_tags(`gorm:"title" json:"title"`) 标题 + Address string `protobuf:"bytes,9,opt,name=address,proto3" json:"address" gorm:"address"` //@go_tags(`gorm:"address" json:"address"`) 地址 + Personnel string `protobuf:"bytes,10,opt,name=personnel,proto3" json:"personnel" gorm:"personnel"` //@go_tags(`gorm:"personnel" json:"personnel"`) 成员 + Tasktype int32 `protobuf:"varint,11,opt,name=tasktype,proto3" json:"tasktype" gorm:"tasktype"` //@go_tags(`gorm:"tasktype" json:"tasktype"`) 任务类型 + Taskid string `protobuf:"bytes,12,opt,name=taskid,proto3" json:"taskid" gorm:"taskid"` //@go_tags(`gorm:"taskid" json:"taskid"`) 任务id + Logid string `protobuf:"bytes,13,opt,name=logid,proto3" json:"logid" gorm:"logid"` //@go_tags(`gorm:"logid" json:"logid"`) 日志id + Isdistinguishspeaker bool `protobuf:"varint,14,opt,name=isdistinguishspeaker,proto3" json:"isdistinguishspeaker" gorm:"isdistinguishspeaker"` //@go_tags(`gorm:"isdistinguishspeaker" json:"isdistinguishspeaker"`) 是否区分说话人 + Templateid uint64 `protobuf:"varint,15,opt,name=templateid,proto3" json:"templateid" gorm:"templateid"` //@go_tags(`gorm:"templateid" json:"templateid"`) 模板id + Formlanguage string `protobuf:"bytes,16,opt,name=formlanguage,proto3" json:"formlanguage" gorm:"formlanguage"` //@go_tags(`gorm:"formlanguage" json:"formlanguage"`) 语言 + Tolanguage string `protobuf:"bytes,17,opt,name=tolanguage,proto3" json:"tolanguage" gorm:"tolanguage"` //@go_tags(`gorm:"tolanguage" json:"tolanguage"`) 目标语言 + Seconds int32 `protobuf:"varint,18,opt,name=seconds,proto3" json:"seconds" gorm:"seconds"` //@go_tags(`gorm:"seconds" json:"seconds"`) 时长 + Size int64 `protobuf:"varint,19,opt,name=size,proto3" json:"size" gorm:"column:size;type:bigint"` //@go_tags(`gorm:"column:size;type:bigint" json:"size"`) 文件大小 + Original string `protobuf:"bytes,20,opt,name=original,proto3" json:"original" gorm:"original"` //@go_tags(`gorm:"original" json:"original"`) 听写内容 + Translate string `protobuf:"bytes,21,opt,name=translate,proto3" json:"translate" gorm:"translate"` //@go_tags(`gorm:"translate" json:"translate"`) 翻译 + Overview string `protobuf:"bytes,22,opt,name=overview,proto3" json:"overview" gorm:"overview"` //@go_tags(`gorm:"overview" json:"overview"`) 短总结 + Summary string `protobuf:"bytes,23,opt,name=summary,proto3" json:"summary" gorm:"summary"` //@go_tags(`gorm:"summary" json:"summary"`) 总结 + AsrSvcId string `protobuf:"bytes,30,opt,name=asr_svc_id,json=asrSvcId,proto3" json:"asr_svc_id" gorm:"asr_svc_id"` //@go_tags(`gorm:"asr_svc_id" json:"asr_svc_id"`) 识别服务id(后台编排选路结果) + MtSvcId string `protobuf:"bytes,31,opt,name=mt_svc_id,json=mtSvcId,proto3" json:"mt_svc_id" gorm:"mt_svc_id"` //@go_tags(`gorm:"mt_svc_id" json:"mt_svc_id"`) 翻译服务id(后台编排选路结果) + LlmSvcId string `protobuf:"bytes,32,opt,name=llm_svc_id,json=llmSvcId,proto3" json:"llm_svc_id" gorm:"llm_svc_id"` //@go_tags(`gorm:"llm_svc_id" json:"llm_svc_id"`) 总结大模型服务id(默认或客户端指定) + Starttime int64 `protobuf:"varint,25,opt,name=starttime,proto3" json:"starttime" gorm:"starttime"` //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒 + Processduration int64 `protobuf:"varint,26,opt,name=processduration,proto3" json:"processduration" gorm:"processduration"` //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒 + Lastquerytime int64 `protobuf:"varint,27,opt,name=lastquerytime,proto3" json:"lastquerytime" gorm:"lastquerytime"` //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率 + Remark string `protobuf:"bytes,28,opt,name=remark,proto3" json:"remark" gorm:"remark"` //@go_tags(`gorm:"remark" json:"remark"`) 用户备注(创建记录时填写,参与AI总结) + Imageurls string `protobuf:"bytes,29,opt,name=imageurls,proto3" json:"imageurls" gorm:"imageurls"` //@go_tags(`gorm:"imageurls" json:"imageurls"`) 注释图片url列表(JSON数组字符串,编码方式同original),作为多模态内容参与AI总结 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -507,11 +450,25 @@ func (x *DBEchoMeetRecord) GetSummary() string { return "" } -func (x *DBEchoMeetRecord) GetServiceType() TranscribeService { +func (x *DBEchoMeetRecord) GetAsrSvcId() string { + if x != nil { + return x.AsrSvcId + } + return "" +} + +func (x *DBEchoMeetRecord) GetMtSvcId() string { if x != nil { - return x.ServiceType + return x.MtSvcId } - return TranscribeService_TranscribeService_Unknown + return "" +} + +func (x *DBEchoMeetRecord) GetLlmSvcId() string { + if x != nil { + return x.LlmSvcId + } + return "" } func (x *DBEchoMeetRecord) GetStarttime() int64 { @@ -993,7 +950,7 @@ const file_echomeet_echomeet_db_proto_rawDesc = "" + "\aoutline\x18\n" + " \x01(\tR\aoutline\x12\x1a\n" + "\btemplate\x18\v \x01(\tR\btemplate\x12\x12\n" + - "\x04sort\x18\f \x01(\x05R\x04sort\"\xfd\x06\n" + + "\x04sort\x18\f \x01(\x05R\x04sort\"\x9e\a\n" + "\x10DBEchoMeetRecord\x12\x0e\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x10\n" + "\x03uid\x18\x02 \x01(\tR\x03uid\x12,\n" + @@ -1022,8 +979,12 @@ const file_echomeet_echomeet_db_proto_rawDesc = "" + "\boriginal\x18\x14 \x01(\tR\boriginal\x12\x1c\n" + "\ttranslate\x18\x15 \x01(\tR\ttranslate\x12\x1a\n" + "\boverview\x18\x16 \x01(\tR\boverview\x12\x18\n" + - "\asummary\x18\x17 \x01(\tR\asummary\x125\n" + - "\fservice_type\x18\x18 \x01(\x0e2\x12.TranscribeServiceR\vserviceType\x12\x1c\n" + + "\asummary\x18\x17 \x01(\tR\asummary\x12\x1c\n" + + "\n" + + "asr_svc_id\x18\x1e \x01(\tR\basrSvcId\x12\x1a\n" + + "\tmt_svc_id\x18\x1f \x01(\tR\amtSvcId\x12\x1c\n" + + "\n" + + "llm_svc_id\x18 \x01(\tR\bllmSvcId\x12\x1c\n" + "\tstarttime\x18\x19 \x01(\x03R\tstarttime\x12(\n" + "\x0fprocessduration\x18\x1a \x01(\x03R\x0fprocessduration\x12$\n" + "\rlastquerytime\x18\x1b \x01(\x03R\rlastquerytime\x12\x16\n" + @@ -1064,14 +1025,7 @@ const file_echomeet_echomeet_db_proto_rawDesc = "" + "utterances\x12.\n" + "\tadditions\x18\x03 \x01(\v2\x10.ResultAdditionsR\tadditions\"'\n" + "\tAudioInfo\x12\x1a\n" + - "\bduration\x18\x01 \x01(\x03R\bduration*\xc9\x01\n" + - "\x11TranscribeService\x12\x1d\n" + - "\x19TranscribeService_Unknown\x10\x00\x12\x1f\n" + - "\x1bTranscribeService_ByteDance\x10\x01\x12\x19\n" + - "\x15TranscribeService_Ali\x10\x02\x12\x1a\n" + - "\x16TranscribeService_Auto\x10\x03\x12\x1c\n" + - "\x18TranscribeService_Google\x10\x04\x12\x1f\n" + - "\x1bTranscribeService_Microsoft\x10\x05*\xb6\x01\n" + + "\bduration\x18\x01 \x01(\x03R\bduration*\xb6\x01\n" + "\x15DBEchoMeetRecordState\x12\n" + "\n" + "\x06Unknow\x10\x00\x12\x15\n" + @@ -1097,33 +1051,31 @@ func file_echomeet_echomeet_db_proto_rawDescGZIP() []byte { return file_echomeet_echomeet_db_proto_rawDescData } -var file_echomeet_echomeet_db_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_echomeet_echomeet_db_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_echomeet_echomeet_db_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_echomeet_echomeet_db_proto_goTypes = []any{ - (TranscribeService)(0), // 0: TranscribeService - (DBEchoMeetRecordState)(0), // 1: DBEchoMeetRecordState - (*DBEchoMeetTemplate)(nil), // 2: DBEchoMeetTemplate - (*DBEchoMeetRecord)(nil), // 3: DBEchoMeetRecord - (*ContextStruct)(nil), // 4: ContextStruct - (*Word)(nil), // 5: Word - (*UtteranceAdditions)(nil), // 6: UtteranceAdditions - (*Utterance)(nil), // 7: Utterance - (*ResultAdditions)(nil), // 8: ResultAdditions - (*RecognitionResult)(nil), // 9: RecognitionResult - (*AudioInfo)(nil), // 10: AudioInfo + (DBEchoMeetRecordState)(0), // 0: DBEchoMeetRecordState + (*DBEchoMeetTemplate)(nil), // 1: DBEchoMeetTemplate + (*DBEchoMeetRecord)(nil), // 2: DBEchoMeetRecord + (*ContextStruct)(nil), // 3: ContextStruct + (*Word)(nil), // 4: Word + (*UtteranceAdditions)(nil), // 5: UtteranceAdditions + (*Utterance)(nil), // 6: Utterance + (*ResultAdditions)(nil), // 7: ResultAdditions + (*RecognitionResult)(nil), // 8: RecognitionResult + (*AudioInfo)(nil), // 9: AudioInfo } var file_echomeet_echomeet_db_proto_depIdxs = []int32{ - 1, // 0: DBEchoMeetRecord.state:type_name -> DBEchoMeetRecordState - 0, // 1: DBEchoMeetRecord.service_type:type_name -> TranscribeService - 5, // 2: Utterance.words:type_name -> Word - 6, // 3: Utterance.additions:type_name -> UtteranceAdditions - 7, // 4: RecognitionResult.utterances:type_name -> Utterance - 8, // 5: RecognitionResult.additions:type_name -> ResultAdditions - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 0, // 0: DBEchoMeetRecord.state:type_name -> DBEchoMeetRecordState + 4, // 1: Utterance.words:type_name -> Word + 5, // 2: Utterance.additions:type_name -> UtteranceAdditions + 6, // 3: RecognitionResult.utterances:type_name -> Utterance + 7, // 4: RecognitionResult.additions:type_name -> ResultAdditions + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_echomeet_echomeet_db_proto_init() } @@ -1136,7 +1088,7 @@ func file_echomeet_echomeet_db_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_db_proto_rawDesc), len(file_echomeet_echomeet_db_proto_rawDesc)), - NumEnums: 2, + NumEnums: 1, NumMessages: 9, NumExtensions: 0, NumServices: 0, diff --git a/apps/services/pb/echomeet_msg.pb.go b/apps/services/pb/echomeet_msg.pb.go index d5193638..bc64f7e0 100644 --- a/apps/services/pb/echomeet_msg.pb.go +++ b/apps/services/pb/echomeet_msg.pb.go @@ -727,14 +727,14 @@ func (*EchomeetPutRecordsResp) Descriptor() ([]byte, []int) { type EchomeetAddRecordReq struct { state protoimpl.MessageState `protogen:"open.v1"` - Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title"` //@go_tags(`json:"title"`) //标题 - Rtype string `protobuf:"bytes,2,opt,name=rtype,proto3" json:"rtype"` //@go_tags(`json:"rtype"`) //类型 - Filepath string `protobuf:"bytes,3,opt,name=filepath,proto3" json:"filepath"` //@go_tags(`json:"filepath"`) //音频文件路径 - Original []*ContextStruct `protobuf:"bytes,4,rep,name=original,proto3" json:"original" gorm:"original"` //@go_tags(`gorm:"original" json:"original"`) 听写内容 - Seconds int32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds"` //@go_tags(`json:"seconds"`) //时长 - Size int64 `protobuf:"varint,6,opt,name=size,proto3" json:"size"` //@go_tags(`json:"size"`) //文件大小 - Remark string `protobuf:"bytes,7,opt,name=remark,proto3" json:"remark"` //@go_tags(`json:"remark"`) //用户备注(参与AI总结) - Imageurls []string `protobuf:"bytes,8,rep,name=imageurls,proto3" json:"imageurls"` //@go_tags(`json:"imageurls"`) //注释图片url列表(作为多模态内容参与AI总结) + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title"` //@go_tags(`json:"title"`) //标题 + Rtype string `protobuf:"bytes,2,opt,name=rtype,proto3" json:"rtype"` //@go_tags(`json:"rtype"`) //类型 + Filepath string `protobuf:"bytes,3,opt,name=filepath,proto3" json:"filepath"` //@go_tags(`json:"filepath"`) //音频文件路径 + Original []*ContextStruct `protobuf:"bytes,4,rep,name=original,proto3" json:"original" gorm:"original"` //@go_tags(`gorm:"original" json:"original"`) 听写内容 + Seconds int32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds"` //@go_tags(`json:"seconds"`) //时长 + Size int64 `protobuf:"varint,6,opt,name=size,proto3" json:"size"` //@go_tags(`json:"size"`) //文件大小 + Remark string `protobuf:"bytes,7,opt,name=remark,proto3" json:"remark"` //@go_tags(`json:"remark"`) //用户备注(参与AI总结) + Imageurls []string `protobuf:"bytes,8,rep,name=imageurls,proto3" json:"imageurls"` //@go_tags(`json:"imageurls"`) //注释图片url列表(作为多模态内容参与AI总结) unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -969,13 +969,13 @@ func (x *EchomeetUpRecordResp) GetRecord() *DBEchoMeetRecord { type EchomeetStartTaskReq struct { state protoimpl.MessageState `protogen:"open.v1"` - Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id - Formlanguage string `protobuf:"bytes,2,opt,name=formlanguage,proto3" json:"formlanguage"` //@go_tags(`json:"formlanguage"`) //语言 - Tolanguage string `protobuf:"bytes,3,opt,name=tolanguage,proto3" json:"tolanguage"` //@go_tags(`json:"tolanguage"`) //目标语言 - Isdistinguishspeaker bool `protobuf:"varint,4,opt,name=isdistinguishspeaker,proto3" json:"isdistinguishspeaker"` //@go_tags(`json:"isdistinguishspeaker"`) //是否区分说话人 - Templateid uint64 `protobuf:"varint,5,opt,name=templateid,proto3" json:"templateid"` //@go_tags(`json:"templateid"`) //会议记录模版索引id - Tid string `protobuf:"bytes,6,opt,name=tid,proto3" json:"tid"` //@go_tags(`json:"tid"`) //会议记录模板tid - ServiceType TranscribeService `protobuf:"varint,7,opt,name=service_type,json=serviceType,proto3,enum=TranscribeService" json:"service_type"` //@go_tags(`json:"service_type"`) //转写服务类型,留空默认 Auto + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id + Formlanguage string `protobuf:"bytes,2,opt,name=formlanguage,proto3" json:"formlanguage"` //@go_tags(`json:"formlanguage"`) //语言 + Tolanguage string `protobuf:"bytes,3,opt,name=tolanguage,proto3" json:"tolanguage"` //@go_tags(`json:"tolanguage"`) //目标语言 + Isdistinguishspeaker bool `protobuf:"varint,4,opt,name=isdistinguishspeaker,proto3" json:"isdistinguishspeaker"` //@go_tags(`json:"isdistinguishspeaker"`) //是否区分说话人 + Templateid uint64 `protobuf:"varint,5,opt,name=templateid,proto3" json:"templateid"` //@go_tags(`json:"templateid"`) //会议记录模版索引id + Tid string `protobuf:"bytes,6,opt,name=tid,proto3" json:"tid"` //@go_tags(`json:"tid"`) //会议记录模板tid + SummarySvcId string `protobuf:"bytes,8,opt,name=summary_svc_id,json=summarySvcId,proto3" json:"summary_svc_id"` //@go_tags(`json:"summary_svc_id"`) //客户端指定的总结大模型服务id(须在可选列表内且后台开关允许,否则用默认);识别/翻译由后台编排选路,客户端不可指定 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1052,11 +1052,11 @@ func (x *EchomeetStartTaskReq) GetTid() string { return "" } -func (x *EchomeetStartTaskReq) GetServiceType() TranscribeService { +func (x *EchomeetStartTaskReq) GetSummarySvcId() string { if x != nil { - return x.ServiceType + return x.SummarySvcId } - return TranscribeService_TranscribeService_Unknown + return "" } type EchomeetStartTaskResp struct { @@ -1219,11 +1219,11 @@ func (*EchomeetCallbackResp) Descriptor() ([]byte, []int) { // 重新总结请求 type EchomeetSummaryReq struct { state protoimpl.MessageState `protogen:"open.v1"` - Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id - Templateid uint64 `protobuf:"varint,5,opt,name=templateid,proto3" json:"templateid"` //@go_tags(`json:"templateid"`) //会议记录模板id - Tid string `protobuf:"bytes,6,opt,name=tid,proto3" json:"tid"` //@go_tags(`json:"tid"`) //会议记录模板tid - Tolanguage string `protobuf:"bytes,7,opt,name=tolanguage,proto3" json:"tolanguage"` //@go_tags(`json:"tolanguage"`) 语言 - ServiceType TranscribeService `protobuf:"varint,8,opt,name=service_type,json=serviceType,proto3,enum=TranscribeService" json:"service_type"` //@go_tags(`json:"service_type"`) //转写服务类型(用于翻译/总结路由),留空沿用记录已有 + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id + Templateid uint64 `protobuf:"varint,5,opt,name=templateid,proto3" json:"templateid"` //@go_tags(`json:"templateid"`) //会议记录模板id + Tid string `protobuf:"bytes,6,opt,name=tid,proto3" json:"tid"` //@go_tags(`json:"tid"`) //会议记录模板tid + Tolanguage string `protobuf:"bytes,7,opt,name=tolanguage,proto3" json:"tolanguage"` //@go_tags(`json:"tolanguage"`) 语言 + SummarySvcId string `protobuf:"bytes,9,opt,name=summary_svc_id,json=summarySvcId,proto3" json:"summary_svc_id"` //@go_tags(`json:"summary_svc_id"`) //客户端指定的总结大模型服务id,留空沿用记录已有/后台默认 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1286,11 +1286,11 @@ func (x *EchomeetSummaryReq) GetTolanguage() string { return "" } -func (x *EchomeetSummaryReq) GetServiceType() TranscribeService { +func (x *EchomeetSummaryReq) GetSummarySvcId() string { if x != nil { - return x.ServiceType + return x.SummarySvcId } - return TranscribeService_TranscribeService_Unknown + return "" } type EchomeetSummaryResp struct { @@ -1329,6 +1329,156 @@ func (*EchomeetSummaryResp) Descriptor() ([]byte, []int) { return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{25} } +// 获取客户端可选的总结大模型服务列表(识别/翻译由后台编排选路,不下发) +type EchomeetGetSummaryServicesReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EchomeetGetSummaryServicesReq) Reset() { + *x = EchomeetGetSummaryServicesReq{} + mi := &file_echomeet_echomeet_msg_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EchomeetGetSummaryServicesReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchomeetGetSummaryServicesReq) ProtoMessage() {} + +func (x *EchomeetGetSummaryServicesReq) ProtoReflect() protoreflect.Message { + mi := &file_echomeet_echomeet_msg_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EchomeetGetSummaryServicesReq.ProtoReflect.Descriptor instead. +func (*EchomeetGetSummaryServicesReq) Descriptor() ([]byte, []int) { + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{26} +} + +// 可选总结服务项 +type EchomeetSummaryServiceItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + SvcId string `protobuf:"bytes,1,opt,name=svc_id,json=svcId,proto3" json:"svc_id"` //@go_tags(`json:"svc_id"`) //服务id(starttask/summary 的 summary_svc_id 传它) + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name"` //@go_tags(`json:"name"`) //服务显示名 + IsDefault bool `protobuf:"varint,3,opt,name=is_default,json=isDefault,proto3" json:"is_default"` //@go_tags(`json:"is_default"`) //是否后台默认模型 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EchomeetSummaryServiceItem) Reset() { + *x = EchomeetSummaryServiceItem{} + mi := &file_echomeet_echomeet_msg_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EchomeetSummaryServiceItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchomeetSummaryServiceItem) ProtoMessage() {} + +func (x *EchomeetSummaryServiceItem) ProtoReflect() protoreflect.Message { + mi := &file_echomeet_echomeet_msg_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EchomeetSummaryServiceItem.ProtoReflect.Descriptor instead. +func (*EchomeetSummaryServiceItem) Descriptor() ([]byte, []int) { + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{27} +} + +func (x *EchomeetSummaryServiceItem) GetSvcId() string { + if x != nil { + return x.SvcId + } + return "" +} + +func (x *EchomeetSummaryServiceItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *EchomeetSummaryServiceItem) GetIsDefault() bool { + if x != nil { + return x.IsDefault + } + return false +} + +type EchomeetGetSummaryServicesResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + ClientChoice bool `protobuf:"varint,1,opt,name=client_choice,json=clientChoice,proto3" json:"client_choice"` //@go_tags(`json:"client_choice"`) //后台是否允许客户端指定总结模型;false 时 services 仅含默认项、传 summary_svc_id 会被忽略 + Services []*EchomeetSummaryServiceItem `protobuf:"bytes,2,rep,name=services,proto3" json:"services"` //@go_tags(`json:"services"`) //可选服务列表 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EchomeetGetSummaryServicesResp) Reset() { + *x = EchomeetGetSummaryServicesResp{} + mi := &file_echomeet_echomeet_msg_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EchomeetGetSummaryServicesResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EchomeetGetSummaryServicesResp) ProtoMessage() {} + +func (x *EchomeetGetSummaryServicesResp) ProtoReflect() protoreflect.Message { + mi := &file_echomeet_echomeet_msg_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EchomeetGetSummaryServicesResp.ProtoReflect.Descriptor instead. +func (*EchomeetGetSummaryServicesResp) Descriptor() ([]byte, []int) { + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{28} +} + +func (x *EchomeetGetSummaryServicesResp) GetClientChoice() bool { + if x != nil { + return x.ClientChoice + } + return false +} + +func (x *EchomeetGetSummaryServicesResp) GetServices() []*EchomeetSummaryServiceItem { + if x != nil { + return x.Services + } + return nil +} + // 已阅会议记录 type EchomeetReadRecordReq struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1339,7 +1489,7 @@ type EchomeetReadRecordReq struct { func (x *EchomeetReadRecordReq) Reset() { *x = EchomeetReadRecordReq{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[26] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1351,7 +1501,7 @@ func (x *EchomeetReadRecordReq) String() string { func (*EchomeetReadRecordReq) ProtoMessage() {} func (x *EchomeetReadRecordReq) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[26] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1364,7 +1514,7 @@ func (x *EchomeetReadRecordReq) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetReadRecordReq.ProtoReflect.Descriptor instead. func (*EchomeetReadRecordReq) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{26} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{29} } func (x *EchomeetReadRecordReq) GetId() uint64 { @@ -1382,7 +1532,7 @@ type EchomeetReadRecordResp struct { func (x *EchomeetReadRecordResp) Reset() { *x = EchomeetReadRecordResp{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[27] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1394,7 +1544,7 @@ func (x *EchomeetReadRecordResp) String() string { func (*EchomeetReadRecordResp) ProtoMessage() {} func (x *EchomeetReadRecordResp) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[27] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1407,7 +1557,7 @@ func (x *EchomeetReadRecordResp) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetReadRecordResp.ProtoReflect.Descriptor instead. func (*EchomeetReadRecordResp) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{27} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{30} } // 修改会议记录 @@ -1425,7 +1575,7 @@ type EchomeetModifyRecordsReq struct { func (x *EchomeetModifyRecordsReq) Reset() { *x = EchomeetModifyRecordsReq{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[28] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1437,7 +1587,7 @@ func (x *EchomeetModifyRecordsReq) String() string { func (*EchomeetModifyRecordsReq) ProtoMessage() {} func (x *EchomeetModifyRecordsReq) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[28] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1450,7 +1600,7 @@ func (x *EchomeetModifyRecordsReq) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetModifyRecordsReq.ProtoReflect.Descriptor instead. func (*EchomeetModifyRecordsReq) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{28} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{31} } func (x *EchomeetModifyRecordsReq) GetId() uint64 { @@ -1504,7 +1654,7 @@ type EchomeetModifyRecordsResp struct { func (x *EchomeetModifyRecordsResp) Reset() { *x = EchomeetModifyRecordsResp{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[29] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1516,7 +1666,7 @@ func (x *EchomeetModifyRecordsResp) String() string { func (*EchomeetModifyRecordsResp) ProtoMessage() {} func (x *EchomeetModifyRecordsResp) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[29] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1529,7 +1679,7 @@ func (x *EchomeetModifyRecordsResp) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetModifyRecordsResp.ProtoReflect.Descriptor instead. func (*EchomeetModifyRecordsResp) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{29} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{32} } func (x *EchomeetModifyRecordsResp) GetRecord() *DBEchoMeetRecord { @@ -1549,7 +1699,7 @@ type EchomeetDelRecordsReq struct { func (x *EchomeetDelRecordsReq) Reset() { *x = EchomeetDelRecordsReq{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[30] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1561,7 +1711,7 @@ func (x *EchomeetDelRecordsReq) String() string { func (*EchomeetDelRecordsReq) ProtoMessage() {} func (x *EchomeetDelRecordsReq) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[30] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1574,7 +1724,7 @@ func (x *EchomeetDelRecordsReq) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetDelRecordsReq.ProtoReflect.Descriptor instead. func (*EchomeetDelRecordsReq) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{30} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{33} } func (x *EchomeetDelRecordsReq) GetIds() []uint64 { @@ -1593,7 +1743,7 @@ type EchomeetDelRecordsResp struct { func (x *EchomeetDelRecordsResp) Reset() { *x = EchomeetDelRecordsResp{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[31] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1605,7 +1755,7 @@ func (x *EchomeetDelRecordsResp) String() string { func (*EchomeetDelRecordsResp) ProtoMessage() {} func (x *EchomeetDelRecordsResp) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[31] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1618,7 +1768,7 @@ func (x *EchomeetDelRecordsResp) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetDelRecordsResp.ProtoReflect.Descriptor instead. func (*EchomeetDelRecordsResp) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{31} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{34} } func (x *EchomeetDelRecordsResp) GetIds() []uint64 { @@ -1642,7 +1792,7 @@ type EchomeetAliCallbackReq struct { func (x *EchomeetAliCallbackReq) Reset() { *x = EchomeetAliCallbackReq{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[32] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1654,7 +1804,7 @@ func (x *EchomeetAliCallbackReq) String() string { func (*EchomeetAliCallbackReq) ProtoMessage() {} func (x *EchomeetAliCallbackReq) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[32] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1667,7 +1817,7 @@ func (x *EchomeetAliCallbackReq) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetAliCallbackReq.ProtoReflect.Descriptor instead. func (*EchomeetAliCallbackReq) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{32} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{35} } func (x *EchomeetAliCallbackReq) GetStatusCode() int32 { @@ -1714,7 +1864,7 @@ type EchomeetAliCallbackResult struct { func (x *EchomeetAliCallbackResult) Reset() { *x = EchomeetAliCallbackResult{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[33] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1726,7 +1876,7 @@ func (x *EchomeetAliCallbackResult) String() string { func (*EchomeetAliCallbackResult) ProtoMessage() {} func (x *EchomeetAliCallbackResult) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[33] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1739,7 +1889,7 @@ func (x *EchomeetAliCallbackResult) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetAliCallbackResult.ProtoReflect.Descriptor instead. func (*EchomeetAliCallbackResult) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{33} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{36} } func (x *EchomeetAliCallbackResult) GetSentences() []*EchomeetAliSentence { @@ -1762,7 +1912,7 @@ type EchomeetAliSentence struct { func (x *EchomeetAliSentence) Reset() { *x = EchomeetAliSentence{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[34] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1774,7 +1924,7 @@ func (x *EchomeetAliSentence) String() string { func (*EchomeetAliSentence) ProtoMessage() {} func (x *EchomeetAliSentence) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[34] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1787,7 +1937,7 @@ func (x *EchomeetAliSentence) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetAliSentence.ProtoReflect.Descriptor instead. func (*EchomeetAliSentence) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{34} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{37} } func (x *EchomeetAliSentence) GetText() string { @@ -1833,7 +1983,7 @@ type EchomeetAliCallbackResp struct { func (x *EchomeetAliCallbackResp) Reset() { *x = EchomeetAliCallbackResp{} - mi := &file_echomeet_echomeet_msg_proto_msgTypes[35] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1845,7 +1995,7 @@ func (x *EchomeetAliCallbackResp) String() string { func (*EchomeetAliCallbackResp) ProtoMessage() {} func (x *EchomeetAliCallbackResp) ProtoReflect() protoreflect.Message { - mi := &file_echomeet_echomeet_msg_proto_msgTypes[35] + mi := &file_echomeet_echomeet_msg_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1858,7 +2008,7 @@ func (x *EchomeetAliCallbackResp) ProtoReflect() protoreflect.Message { // Deprecated: Use EchomeetAliCallbackResp.ProtoReflect.Descriptor instead. func (*EchomeetAliCallbackResp) Descriptor() ([]byte, []int) { - return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{35} + return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{38} } var File_echomeet_echomeet_msg_proto protoreflect.FileDescriptor @@ -1912,7 +2062,7 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x1a\n" + "\baudiourl\x18\x02 \x01(\tR\baudiourl\"A\n" + "\x14EchomeetUpRecordResp\x12)\n" + - "\x06record\x18\x01 \x01(\v2\x11.DBEchoMeetRecordR\x06record\"\x87\x02\n" + + "\x06record\x18\x01 \x01(\v2\x11.DBEchoMeetRecordR\x06record\"\xf6\x01\n" + "\x14EchomeetStartTaskReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\"\n" + "\fformlanguage\x18\x02 \x01(\tR\fformlanguage\x12\x1e\n" + @@ -1923,8 +2073,8 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" + "\n" + "templateid\x18\x05 \x01(\x04R\n" + "templateid\x12\x10\n" + - "\x03tid\x18\x06 \x01(\tR\x03tid\x125\n" + - "\fservice_type\x18\a \x01(\x0e2\x12.TranscribeServiceR\vserviceType\"\x17\n" + + "\x03tid\x18\x06 \x01(\tR\x03tid\x12$\n" + + "\x0esummary_svc_id\x18\b \x01(\tR\fsummarySvcId\"\x17\n" + "\x15EchomeetStartTaskResp\"\xcf\x01\n" + "\x13EchomeetCallbackReq\x12\x12\n" + "\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" + @@ -1935,7 +2085,7 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" + "audio_info\x18\x05 \x01(\v2\n" + ".AudioInfoR\taudioInfo\x12#\n" + "\rcallback_data\x18\x06 \x01(\tR\fcallbackData\"\x16\n" + - "\x14EchomeetCallbackResp\"\xad\x01\n" + + "\x14EchomeetCallbackResp\"\x9c\x01\n" + "\x12EchomeetSummaryReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x1e\n" + "\n" + @@ -1944,9 +2094,18 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" + "\x03tid\x18\x06 \x01(\tR\x03tid\x12\x1e\n" + "\n" + "tolanguage\x18\a \x01(\tR\n" + - "tolanguage\x125\n" + - "\fservice_type\x18\b \x01(\x0e2\x12.TranscribeServiceR\vserviceType\"\x15\n" + - "\x13EchomeetSummaryResp\"'\n" + + "tolanguage\x12$\n" + + "\x0esummary_svc_id\x18\t \x01(\tR\fsummarySvcId\"\x15\n" + + "\x13EchomeetSummaryResp\"\x1f\n" + + "\x1dEchomeetGetSummaryServicesReq\"f\n" + + "\x1aEchomeetSummaryServiceItem\x12\x15\n" + + "\x06svc_id\x18\x01 \x01(\tR\x05svcId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1d\n" + + "\n" + + "is_default\x18\x03 \x01(\bR\tisDefault\"~\n" + + "\x1eEchomeetGetSummaryServicesResp\x12#\n" + + "\rclient_choice\x18\x01 \x01(\bR\fclientChoice\x127\n" + + "\bservices\x18\x02 \x03(\v2\x1b.EchomeetSummaryServiceItemR\bservices\"'\n" + "\x15EchomeetReadRecordReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x04R\x02id\"\x18\n" + "\x16EchomeetReadRecordResp\"\xb0\x01\n" + @@ -1995,76 +2154,77 @@ func file_echomeet_echomeet_msg_proto_rawDescGZIP() []byte { return file_echomeet_echomeet_msg_proto_rawDescData } -var file_echomeet_echomeet_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_echomeet_echomeet_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 39) var file_echomeet_echomeet_msg_proto_goTypes = []any{ - (*EchomeetGetTemplatesReq)(nil), // 0: EchomeetGetTemplatesReq - (*EchomeetGetTemplatesResp)(nil), // 1: EchomeetGetTemplatesResp - (*EchomeetAddTemplateReq)(nil), // 2: EchomeetAddTemplateReq - (*EchomeetAddTemplateResp)(nil), // 3: EchomeetAddTemplateResp - (*EchomeetUpdateTemplateReq)(nil), // 4: EchomeetUpdateTemplateReq - (*EchomeetUpdateTemplateResp)(nil), // 5: EchomeetUpdateTemplateResp - (*EchomeetDeleteTemplateReq)(nil), // 6: EchomeetDeleteTemplateReq - (*EchomeetDeleteTemplateResp)(nil), // 7: EchomeetDeleteTemplateResp - (*EchomeetGetAllRecordsReq)(nil), // 8: EchomeetGetAllRecordsReq - (*EchomeetGetAllRecordsResp)(nil), // 9: EchomeetGetAllRecordsResp - (*EchomeetGetRecordsReq)(nil), // 10: EchomeetGetRecordsReq - (*EchomeetGetRecordsResp)(nil), // 11: EchomeetGetRecordsResp - (*EchomeetGetRecordReq)(nil), // 12: EchomeetGetRecordReq - (*EchomeetGetRecordResp)(nil), // 13: EchomeetGetRecordResp - (*EchomeetPutRecordsReq)(nil), // 14: EchomeetPutRecordsReq - (*EchomeetPutRecordsResp)(nil), // 15: EchomeetPutRecordsResp - (*EchomeetAddRecordReq)(nil), // 16: EchomeetAddRecordReq - (*EchomeetAddRecordResp)(nil), // 17: EchomeetAddRecordResp - (*EchomeetUpRecordReq)(nil), // 18: EchomeetUpRecordReq - (*EchomeetUpRecordResp)(nil), // 19: EchomeetUpRecordResp - (*EchomeetStartTaskReq)(nil), // 20: EchomeetStartTaskReq - (*EchomeetStartTaskResp)(nil), // 21: EchomeetStartTaskResp - (*EchomeetCallbackReq)(nil), // 22: EchomeetCallbackReq - (*EchomeetCallbackResp)(nil), // 23: EchomeetCallbackResp - (*EchomeetSummaryReq)(nil), // 24: EchomeetSummaryReq - (*EchomeetSummaryResp)(nil), // 25: EchomeetSummaryResp - (*EchomeetReadRecordReq)(nil), // 26: EchomeetReadRecordReq - (*EchomeetReadRecordResp)(nil), // 27: EchomeetReadRecordResp - (*EchomeetModifyRecordsReq)(nil), // 28: EchomeetModifyRecordsReq - (*EchomeetModifyRecordsResp)(nil), // 29: EchomeetModifyRecordsResp - (*EchomeetDelRecordsReq)(nil), // 30: EchomeetDelRecordsReq - (*EchomeetDelRecordsResp)(nil), // 31: EchomeetDelRecordsResp - (*EchomeetAliCallbackReq)(nil), // 32: EchomeetAliCallbackReq - (*EchomeetAliCallbackResult)(nil), // 33: EchomeetAliCallbackResult - (*EchomeetAliSentence)(nil), // 34: EchomeetAliSentence - (*EchomeetAliCallbackResp)(nil), // 35: EchomeetAliCallbackResp - (*DBEchoMeetTemplate)(nil), // 36: DBEchoMeetTemplate - (*DBEchoMeetRecord)(nil), // 37: DBEchoMeetRecord - (*ContextStruct)(nil), // 38: ContextStruct - (TranscribeService)(0), // 39: TranscribeService - (*RecognitionResult)(nil), // 40: RecognitionResult - (*AudioInfo)(nil), // 41: AudioInfo + (*EchomeetGetTemplatesReq)(nil), // 0: EchomeetGetTemplatesReq + (*EchomeetGetTemplatesResp)(nil), // 1: EchomeetGetTemplatesResp + (*EchomeetAddTemplateReq)(nil), // 2: EchomeetAddTemplateReq + (*EchomeetAddTemplateResp)(nil), // 3: EchomeetAddTemplateResp + (*EchomeetUpdateTemplateReq)(nil), // 4: EchomeetUpdateTemplateReq + (*EchomeetUpdateTemplateResp)(nil), // 5: EchomeetUpdateTemplateResp + (*EchomeetDeleteTemplateReq)(nil), // 6: EchomeetDeleteTemplateReq + (*EchomeetDeleteTemplateResp)(nil), // 7: EchomeetDeleteTemplateResp + (*EchomeetGetAllRecordsReq)(nil), // 8: EchomeetGetAllRecordsReq + (*EchomeetGetAllRecordsResp)(nil), // 9: EchomeetGetAllRecordsResp + (*EchomeetGetRecordsReq)(nil), // 10: EchomeetGetRecordsReq + (*EchomeetGetRecordsResp)(nil), // 11: EchomeetGetRecordsResp + (*EchomeetGetRecordReq)(nil), // 12: EchomeetGetRecordReq + (*EchomeetGetRecordResp)(nil), // 13: EchomeetGetRecordResp + (*EchomeetPutRecordsReq)(nil), // 14: EchomeetPutRecordsReq + (*EchomeetPutRecordsResp)(nil), // 15: EchomeetPutRecordsResp + (*EchomeetAddRecordReq)(nil), // 16: EchomeetAddRecordReq + (*EchomeetAddRecordResp)(nil), // 17: EchomeetAddRecordResp + (*EchomeetUpRecordReq)(nil), // 18: EchomeetUpRecordReq + (*EchomeetUpRecordResp)(nil), // 19: EchomeetUpRecordResp + (*EchomeetStartTaskReq)(nil), // 20: EchomeetStartTaskReq + (*EchomeetStartTaskResp)(nil), // 21: EchomeetStartTaskResp + (*EchomeetCallbackReq)(nil), // 22: EchomeetCallbackReq + (*EchomeetCallbackResp)(nil), // 23: EchomeetCallbackResp + (*EchomeetSummaryReq)(nil), // 24: EchomeetSummaryReq + (*EchomeetSummaryResp)(nil), // 25: EchomeetSummaryResp + (*EchomeetGetSummaryServicesReq)(nil), // 26: EchomeetGetSummaryServicesReq + (*EchomeetSummaryServiceItem)(nil), // 27: EchomeetSummaryServiceItem + (*EchomeetGetSummaryServicesResp)(nil), // 28: EchomeetGetSummaryServicesResp + (*EchomeetReadRecordReq)(nil), // 29: EchomeetReadRecordReq + (*EchomeetReadRecordResp)(nil), // 30: EchomeetReadRecordResp + (*EchomeetModifyRecordsReq)(nil), // 31: EchomeetModifyRecordsReq + (*EchomeetModifyRecordsResp)(nil), // 32: EchomeetModifyRecordsResp + (*EchomeetDelRecordsReq)(nil), // 33: EchomeetDelRecordsReq + (*EchomeetDelRecordsResp)(nil), // 34: EchomeetDelRecordsResp + (*EchomeetAliCallbackReq)(nil), // 35: EchomeetAliCallbackReq + (*EchomeetAliCallbackResult)(nil), // 36: EchomeetAliCallbackResult + (*EchomeetAliSentence)(nil), // 37: EchomeetAliSentence + (*EchomeetAliCallbackResp)(nil), // 38: EchomeetAliCallbackResp + (*DBEchoMeetTemplate)(nil), // 39: DBEchoMeetTemplate + (*DBEchoMeetRecord)(nil), // 40: DBEchoMeetRecord + (*ContextStruct)(nil), // 41: ContextStruct + (*RecognitionResult)(nil), // 42: RecognitionResult + (*AudioInfo)(nil), // 43: AudioInfo } var file_echomeet_echomeet_msg_proto_depIdxs = []int32{ - 36, // 0: EchomeetGetTemplatesResp.templates:type_name -> DBEchoMeetTemplate - 36, // 1: EchomeetAddTemplateReq.template:type_name -> DBEchoMeetTemplate - 36, // 2: EchomeetAddTemplateResp.template:type_name -> DBEchoMeetTemplate - 36, // 3: EchomeetUpdateTemplateReq.template:type_name -> DBEchoMeetTemplate - 36, // 4: EchomeetUpdateTemplateResp.template:type_name -> DBEchoMeetTemplate - 37, // 5: EchomeetGetAllRecordsResp.records:type_name -> DBEchoMeetRecord - 37, // 6: EchomeetGetRecordsResp.records:type_name -> DBEchoMeetRecord - 37, // 7: EchomeetGetRecordResp.record:type_name -> DBEchoMeetRecord - 37, // 8: EchomeetPutRecordsReq.records:type_name -> DBEchoMeetRecord - 38, // 9: EchomeetAddRecordReq.original:type_name -> ContextStruct - 37, // 10: EchomeetAddRecordResp.record:type_name -> DBEchoMeetRecord - 37, // 11: EchomeetUpRecordResp.record:type_name -> DBEchoMeetRecord - 39, // 12: EchomeetStartTaskReq.service_type:type_name -> TranscribeService - 40, // 13: EchomeetCallbackReq.result:type_name -> RecognitionResult - 41, // 14: EchomeetCallbackReq.audio_info:type_name -> AudioInfo - 39, // 15: EchomeetSummaryReq.service_type:type_name -> TranscribeService - 37, // 16: EchomeetModifyRecordsResp.record:type_name -> DBEchoMeetRecord - 33, // 17: EchomeetAliCallbackReq.Result:type_name -> EchomeetAliCallbackResult - 34, // 18: EchomeetAliCallbackResult.Sentences:type_name -> EchomeetAliSentence - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 39, // 0: EchomeetGetTemplatesResp.templates:type_name -> DBEchoMeetTemplate + 39, // 1: EchomeetAddTemplateReq.template:type_name -> DBEchoMeetTemplate + 39, // 2: EchomeetAddTemplateResp.template:type_name -> DBEchoMeetTemplate + 39, // 3: EchomeetUpdateTemplateReq.template:type_name -> DBEchoMeetTemplate + 39, // 4: EchomeetUpdateTemplateResp.template:type_name -> DBEchoMeetTemplate + 40, // 5: EchomeetGetAllRecordsResp.records:type_name -> DBEchoMeetRecord + 40, // 6: EchomeetGetRecordsResp.records:type_name -> DBEchoMeetRecord + 40, // 7: EchomeetGetRecordResp.record:type_name -> DBEchoMeetRecord + 40, // 8: EchomeetPutRecordsReq.records:type_name -> DBEchoMeetRecord + 41, // 9: EchomeetAddRecordReq.original:type_name -> ContextStruct + 40, // 10: EchomeetAddRecordResp.record:type_name -> DBEchoMeetRecord + 40, // 11: EchomeetUpRecordResp.record:type_name -> DBEchoMeetRecord + 42, // 12: EchomeetCallbackReq.result:type_name -> RecognitionResult + 43, // 13: EchomeetCallbackReq.audio_info:type_name -> AudioInfo + 27, // 14: EchomeetGetSummaryServicesResp.services:type_name -> EchomeetSummaryServiceItem + 40, // 15: EchomeetModifyRecordsResp.record:type_name -> DBEchoMeetRecord + 36, // 16: EchomeetAliCallbackReq.Result:type_name -> EchomeetAliCallbackResult + 37, // 17: EchomeetAliCallbackResult.Sentences:type_name -> EchomeetAliSentence + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_echomeet_echomeet_msg_proto_init() } @@ -2079,7 +2239,7 @@ func file_echomeet_echomeet_msg_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_msg_proto_rawDesc), len(file_echomeet_echomeet_msg_proto_rawDesc)), NumEnums: 0, - NumMessages: 36, + NumMessages: 39, NumExtensions: 0, NumServices: 0, }, diff --git a/deploy/voitrans/confs/home.yaml.example b/deploy/voitrans/confs/home.yaml.example index 8e9cf20d..b5656599 100644 --- a/deploy/voitrans/confs/home.yaml.example +++ b/deploy/voitrans/confs/home.yaml.example @@ -134,53 +134,10 @@ modules: CBCKey: "" allhelp: max_summary_process: 5 - echomeet: #会议记录:四套服务商各自的转写/翻译/总结配置 + echomeet: #会议记录:识别/翻译/总结服务不再走 yaml——全部由后台「会议记录服务」编排 + # (console 共享库 echomeet_orch + 服务池 svc_config)配置,运行时直读 + NATS 热重载。 + # 部署身份与解密密钥由 .env 注入:APP_NAME / APP_REGION / FIELD_ENCRYPT_KEY。 Debug: ${LOG_DEBUG:-false} - DefaultService: "Auto" - ByteDance: - Callback: "" - Transcribe: - AppID: "8595121680" - Token: "" - Translate: - AccessKey: "" - SecretKey: "" - Region: "cn-beijing" - Summary: - Apikey: "" - Model: "doubao-1-5-pro-32k-250115" - Ali: - Callback: "" - Transcribe: - ApiKey: "" - Translate: - AccessKeyId: "" - AccessKeySecret: "" - Region: "cn-hangzhou" - Summary: - BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1" - Token: "" - Model: "qwen-plus" - Google: #需放置 ./smart-bluetooth-447104-2b269474bd72.json - Transcribe: - JsonPath: ./smart-bluetooth-447104-2b269474bd72.json - Translate: - JsonPath: ./smart-bluetooth-447104-2b269474bd72.json - Summary: - Apikey: "" - Model: "gemini-2.5-flash" - Microsoft: #未配置则该服务商不可用 - Transcribe: - Key: "" - Region: "eastasia" - Translate: - Key: "" - Region: "eastasia" - Endpoint: "https://api.cognitive.microsofttranslator.com" - Summary: - BaseURL: "" - Token: "" - Model: "" pay: Debug: ${LOG_DEBUG:-false} WxAppId: "wx5bbb7be9324f3e2d" diff --git a/deploy/voitrans/env/env.example b/deploy/voitrans/env/env.example index a1548a8f..761a9dc5 100644 --- a/deploy/voitrans/env/env.example +++ b/deploy/voitrans/env/env.example @@ -31,6 +31,13 @@ NATS_URL=nats://nats:4222 # 发出去,表现为 401 "API key is invalid",而后台却显示配置已填好,极难排查。 FIELD_ENCRYPT_KEY= +# ── 部署身份(须与 console 注册表 app_registry 的 app_name/region 一致)── +# api 的应用服务配置托管、echomeet 的会议记录服务编排都按 (APP_NAME, APP_REGION) 作用域解析; +# 不配则落到「全局默认」作用域('',0)——编排的全局默认层仍然生效,但按应用/区域的细分配置不生效。 +# APP_REGION 用区域短代码:cn/us/ea/sea/...(见 comm/region.go)。 +APP_NAME= +APP_REGION= + # ── 统计上报(须与 console 注册表中的应用名/区域一致)── ANALYZE_APP_NAME=voitrans-a11-dev ANALYZE_REGION=1