Browse Source

会议记录接入后台服务编排:三段独立选路+客户端可选总结模型,移除旧 yaml 四套凭据模式

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
main
liwei1dao 3 months ago
parent
commit
af9e210f9a
  1. 15
      apps/proto/echomeet/echomeet_db.proto
  2. 20
      apps/proto/echomeet/echomeet_msg.proto
  3. 11
      apps/services/comm/region.go
  4. 85
      apps/services/comm/svcpool.go
  5. 6
      apps/services/modules/console/api_echomeet_orch.go
  6. 6
      apps/services/modules/console/api_svctemplate.go
  7. 40
      apps/services/modules/console/model_echomeet_orch.go
  8. 53
      apps/services/modules/console/model_svcconfig.go
  9. 25
      apps/services/modules/echomeet/api_getsummaryservices.go
  10. 80
      apps/services/modules/echomeet/api_starttask.go
  11. 16
      apps/services/modules/echomeet/api_summary.go
  12. 142
      apps/services/modules/echomeet/fix_verify_test.go
  13. 35
      apps/services/modules/echomeet/options.go
  14. 289
      apps/services/modules/echomeet/orch_resolver.go
  15. 98
      apps/services/modules/echomeet/provider_ali.go
  16. 60
      apps/services/modules/echomeet/provider_bytedance.go
  17. 60
      apps/services/modules/echomeet/provider_google.go
  18. 73
      apps/services/modules/echomeet/provider_microsoft.go
  19. 229
      apps/services/modules/echomeet/provider_registry.go
  20. 414
      apps/services/modules/echomeet/providers.go
  21. 162
      apps/services/modules/echomeet/tasks.go
  22. 234
      apps/services/pb/echomeet_db.pb.go
  23. 422
      apps/services/pb/echomeet_msg.pb.go
  24. 49
      deploy/voitrans/confs/home.yaml.example
  25. 7
      deploy/voitrans/env/env.example

15
apps/proto/echomeet/echomeet_db.proto

@ -1,15 +1,8 @@
syntax = "proto3"; syntax = "proto3";
option go_package = ".;pb"; option go_package = ".;pb";
// 转写服务类型 // 注:原「转写服务类型」枚举 TranscribeService 已删除——服务商不再是固定四家,
enum TranscribeService { // 改为后台编排(echomeet_orch)+ 服务池(svc_config)动态配置,记录里存三段各自的服务id。
TranscribeService_Unknown = 0; // 未知(兼容旧数据)
TranscribeService_ByteDance = 1; // 字节跳动
TranscribeService_Ali = 2; // 阿里云 DashScope
TranscribeService_Auto = 3; // 自动按语言路由
TranscribeService_Google = 4; // Google Cloud
TranscribeService_Microsoft = 5; // Azure
}
enum DBEchoMeetRecordState{ enum DBEchoMeetRecordState{
Unknow = 0; //@go_tags(`gorm:"DBEchoMeetRecordState_Unknow"`) 未知状态 Unknow = 0; //@go_tags(`gorm:"DBEchoMeetRecordState_Unknow"`) 未知状态
@ -66,7 +59,9 @@ message DBEchoMeetRecord {
string translate = 21; //@go_tags(`gorm:"translate" json:"translate"`) 翻译 string translate = 21; //@go_tags(`gorm:"translate" json:"translate"`) 翻译
string overview = 22; //@go_tags(`gorm:"overview" json:"overview"`) 短总结 string overview = 22; //@go_tags(`gorm:"overview" json:"overview"`) 短总结
string summary = 23; //@go_tags(`gorm:"summary" json:"summary"`) 总结 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 starttime = 25; //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒
int64 processduration = 26; //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒 int64 processduration = 26; //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒
int64 lastquerytime = 27; //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率 int64 lastquerytime = 27; //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率

20
apps/proto/echomeet/echomeet_msg.proto

@ -112,7 +112,7 @@ message EchomeetStartTaskReq{
bool isdistinguishspeaker = 4; //@go_tags(`json:"isdistinguishspeaker"`) //是否区分说话人 bool isdistinguishspeaker = 4; //@go_tags(`json:"isdistinguishspeaker"`) //是否区分说话人
uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模版索引id uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模版索引id
string tid = 6; //@go_tags(`json:"tid"`) //会议记录模板tid 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{ message EchomeetStartTaskResp{
@ -140,13 +140,29 @@ message EchomeetSummaryReq {
uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模板id uint64 templateid = 5; //@go_tags(`json:"templateid"`) //会议记录模板id
string tid = 6; //@go_tags(`json:"tid"`) //会议记录模板tid string tid = 6; //@go_tags(`json:"tid"`) //会议记录模板tid
string tolanguage = 7; //@go_tags(`json:"tolanguage"`) 语言 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 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"`) //可选服务列表
}
//已阅会议记录 //已阅会议记录
message EchomeetReadRecordReq{ message EchomeetReadRecordReq{
uint64 id = 1; //@go_tags(`json:"id"`) //会议记录id uint64 id = 1; //@go_tags(`json:"id"`) //会议记录id

11
apps/services/comm/region.go

@ -27,3 +27,14 @@ func RegionCode(region int32) string {
} }
return "" 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
}

85
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"`
}

6
apps/services/modules/console/api_echomeet_orch.go

@ -211,6 +211,7 @@ func (this *serverComp) saveEchoOrch(c *gin.Context) {
return return
} }
} }
broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", o.Region) // 通知 echomeet 运行时重载编排
writeOK(c, gin.H{"row_id": o.RowId}) 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 { if had && old.IsDefault && old.Kind == echoKindLLM {
this.promoteEchoOrchDefault(old.AppName, old.Region) this.promoteEchoOrchDefault(old.AppName, old.Region)
} }
broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "delete", old.Region) // 通知 echomeet 运行时重载编排
writeOK(c, gin.H{"deleted": req.RowId}) writeOK(c, gin.H{"deleted": req.RowId})
} }
@ -284,6 +286,9 @@ func (this *serverComp) reorderEchoOrch(c *gin.Context) {
} }
updated++ updated++
} }
if updated > 0 {
broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", 0) // 通知 echomeet 运行时重载编排
}
writeOK(c, gin.H{"updated": updated}) writeOK(c, gin.H{"updated": updated})
} }
@ -314,5 +319,6 @@ func (this *serverComp) saveEchoOrchSetting(c *gin.Context) {
return return
} }
} }
broadcastConfigChanged(comm.ConfigKindEchomeetOrch, "update", req.Region) // 通知 echomeet 运行时重载编排
writeOK(c, gin.H{"llm_client_choice": req.LLMClientChoice}) writeOK(c, gin.H{"llm_client_choice": req.LLMClientChoice})
} }

6
apps/services/modules/console/api_svctemplate.go

@ -204,10 +204,12 @@ func builtinSvcTemplates() []ThirdSvcTemplate {
// MT 机器翻译 // MT 机器翻译
tpl("mt_azure", "azure", "微软 Azure 翻译", svcCatMT, 0, tpl("mt_azure", "azure", "微软 Azure 翻译", svcCatMT, 0,
sf("subscription_key", "订阅密钥", true), sf("region", "区域,如 eastasia", false)), 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, tpl("mt_google", "google", "Google 翻译", svcCatMT, 1,
sf("api_key", "API Key", true)), sf("json", "服务账号 JSON(内容或路径)", true)),
tpl("mt_doubao", "doubao", "豆包(火山)翻译", svcCatMT, 2, 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, tpl("mt_alibaba", "alibaba", "阿里云 翻译", svcCatMT, 3,
sf("access_key_id", "AccessKey ID", true), sf("access_key_secret", "AccessKey Secret", true)), sf("access_key_id", "AccessKey ID", true), sf("access_key_secret", "AccessKey Secret", true)),

40
apps/services/modules/console/model_echomeet_orch.go

@ -1,5 +1,7 @@
package console package console
import "yunyan/comm"
// 会议记录服务编排 (echomeet orchestration)。 // 会议记录服务编排 (echomeet orchestration)。
// //
// 与旧的 echomeet_svc_config(每应用一份、在业务库、自己存凭据)不同: // 与旧的 echomeet_svc_config(每应用一份、在业务库、自己存凭据)不同:
@ -20,11 +22,11 @@ package console
// (console 才有注册表里的 base_url;业务服务未必知道自己的对外地址,尤其多区域部署)。 // (console 才有注册表里的 base_url;业务服务未必知道自己的对外地址,尤其多区域部署)。
// 下发解析顺序:(app,region) → (app,0) → ('',region) → ('',0) 全局默认兜底,越具体越优先。 // 下发解析顺序:(app,region) → (app,0) → ('',region) → ('',0) 全局默认兜底,越具体越优先。
// 编排类别(会议记录三段)。 // 编排类别(会议记录三段)。常量已下沉 comm(echomeet 运行时按 kind 消费编排),此处保留包内别名。
const ( const (
echoKindASR int32 = 1 // 识别(录音文件转写) echoKindASR = comm.EchoKindASR // 识别(录音文件转写)
echoKindMT int32 = 2 // 翻译 echoKindMT = comm.EchoKindMT // 翻译
echoKindLLM int32 = 3 // 总结(大模型) echoKindLLM = comm.EchoKindLLM // 总结(大模型)
) )
// echoKindSvcCats 各编排类别对应的服务池类别(前端选服务时按此过滤,后端校验 svc_id 归属用)。 // echoKindSvcCats 各编排类别对应的服务池类别(前端选服务时按此过滤,后端校验 svc_id 归属用)。
@ -34,29 +36,7 @@ var echoKindSvcCats = map[int32][]int32{
echoKindLLM: {svcCatLLM, svcCatLLMVision}, echoKindLLM: {svcCatLLM, svcCatLLMVision},
} }
// EchoOrchestration 一行 = 某作用域(app_name,region) 下某类别选中的一个服务引用 + 排序/标记。 // EchoOrchestration / EchoOrchSetting 结构已下沉到 comm/svcpool.go(echomeet 运行时直读消费),
// 唯一:(app_name, region, kind, svc_id)。svc_id 引用 ThirdSvcConfig.Id(同作用域解析凭据)。 // console 侧用 type alias 引用,包内代码零改动。
type EchoOrchestration struct { type EchoOrchestration = comm.EchoOrchestration
RowId uint64 `gorm:"primaryKey;autoIncrement;column:row_id" json:"row_id"` type EchoOrchSetting = comm.EchoOrchSetting
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"`
}

53
apps/services/modules/console/model_svcconfig.go

@ -10,54 +10,13 @@ package console
// gorm v1.26 AutoMigrate + 读写自动 Marshal/Unmarshal,无需手动处理。 // gorm v1.26 AutoMigrate + 读写自动 Marshal/Unmarshal,无需手动处理。
// region / mode 直接用 int32(与前端 JSON 一致),不依赖 pb 的 Region/TranslateMode 枚举。 // region / mode 直接用 int32(与前端 JSON 一致),不依赖 pb 的 Region/TranslateMode 枚举。
// SvcField 第三方服务的自定义字段项(内嵌于 ThirdSvcConfig.fields jsonb 列,也复用于模板/区域专属字段)。 import "yunyan/comm"
// 加密字段: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"` // 展示排序,越小越靠前
}
// ThirdSvcConfig 第三方服务配置(表:svc_config,按应用分离)。每行 = 某应用作用域下的一个第三方服务(如火山引擎/阿里云)。 // SvcField / ThirdSvcConfig / SvcRegionOverride 结构已下沉到 comm/svcpool.go(业务服务运行时
// categories 存逗号分隔的 ServiceCategory 枚举值,如 "1,2,3"。 // 也要直读消费,如 echomeet 编排),console 侧用 type alias 引用,包内代码零改动。
// Provider 记录该服务由哪个服务商模板创建(如 azure/openai),仅供前端展示/分组,可为空。 type SvcField = comm.SvcField
// type ThirdSvcConfig = comm.ThirdSvcConfig
// 按应用分离:AppName=''为全局默认、'X'为应用X的覆盖;解析时应用有则用应用的、无则回退全局。 type SvcRegionOverride = comm.SvcRegionOverride
// 业务字符串 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"`
}
// CallTranslatePair 通话翻译语言对配置(表:call_translate_pair)。 // CallTranslatePair 通话翻译语言对配置(表:call_translate_pair)。
// 每条记录定义一个区域下"源语言→目标语言"的翻译服务编排。 // 每条记录定义一个区域下"源语言→目标语言"的翻译服务编排。

25
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
}

80
apps/services/modules/echomeet/api_starttask.go

@ -4,7 +4,6 @@ import (
"yunyan/comm" "yunyan/comm"
"yunyan/pb" "yunyan/pb"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
) )
@ -106,18 +105,28 @@ func (this *apiComp) StartTask(session comm.IUserSession, req *pb.EchomeetStartT
model.Isdistinguishspeaker = req.Isdistinguishspeaker model.Isdistinguishspeaker = req.Isdistinguishspeaker
model.Formlanguage = req.Formlanguage model.Formlanguage = req.Formlanguage
model.Tolanguage = req.Tolanguage 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", // 三段选路(后台编排,fail-closed:未配置直接报错提示去后台配置):
model.Id, model.Uid, model.Formlanguage, model.Tolanguage, model.Seconds, model.Size, model.Isdistinguishspeaker, ServiceTypeName(model.ServiceType)) // - 识别/翻译:按后台优先级 + 语言支持过滤,客户端不可指定;
// - 总结:后台默认模型;开关允许时客户端可用 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" { 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 var contexts []*pb.ContextStruct
if err = json.Unmarshal([]byte(model.Original), &contexts); err != nil { if err = json.Unmarshal([]byte(model.Original), &contexts); err != nil {
this.module.Errorf("Summary id:%d 解析 Original 失败 err:%v", model.Id, err) 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) _ = this.module.tasks.SubmitAITask(model)
} }
return 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) if model.Audiourl == "" {
switch model.ServiceType { errdata = &pb.ErrorData{
case pb.TranscribeService_TranscribeService_ByteDance: Code: pb.ErrorCode_AudioUrlEmpty,
if model.Seconds <= byteDanceFlashMaxSeconds && model.Size <= byteDanceFlashMaxBytes { Message: "音频URL为空",
_ = 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
} }
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{} resp = &pb.EchomeetStartTaskResp{}
return return

16
apps/services/modules/echomeet/api_summary.go

@ -55,14 +55,16 @@ func (this *apiComp) Summary(session comm.IUserSession, req *pb.EchomeetSummaryR
return return
} }
model.Templateid = tmpl.Id model.Templateid = tmpl.Id
// 选定服务商:请求显式指定优先,否则沿用记录已有的服务商,再否则按配置默认/语言路由。 // 总结模型:请求显式指定(须后台开关允许且在可选列表内)优先,否则沿用记录已有,再否则后台默认。
// translate 与 AI 总结都会走这个服务商。 if req.SummarySvcId != "" || model.LlmSvcId == "" {
svc := req.ServiceType llmSvcId, _, perr := this.module.providers.PickLLM(req.SummarySvcId)
if svc == pb.TranscribeService_TranscribeService_Unknown || svc == pb.TranscribeService_TranscribeService_Auto { if perr != nil {
svc = model.ServiceType 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 → llm:%s", model.Id, model.LlmSvcId)
this.module.Infof("Summary id:%d → service:%s", model.Id, ServiceTypeName(model.ServiceType))
// 是否需要重新翻译: // 是否需要重新翻译:
// - 客户端显式传入 tolanguage(重新总结操作,强制重译以保证 Translate 与 Tolanguage 一致);或 // - 客户端显式传入 tolanguage(重新总结操作,强制重译以保证 Translate 与 Tolanguage 一致);或
// - 目标语言变化;或 // - 目标语言变化;或

142
apps/services/modules/echomeet/fix_verify_test.go

@ -3,87 +3,99 @@ package echomeet
import ( import (
"testing" "testing"
"yunyan/pb" "yunyan/comm"
) )
// testTranscribers 构造仅含转写能力的 provider 集合(不初始化任何 SDK/凭据)。 // 编排选路的语言过滤(svcSupportsLanguage)回归:
func testTranscribers() map[pb.TranscribeService]Transcriber { // 铁律沿袭「未知语言绝不 fallback」——languages 配置了就必须命中(精确或主语言段),
return map[pb.TranscribeService]Transcriber{ // 防止重蹈「西语送进 paraformer-v2 静默返回空转写」的线上事故(该白名单现由后台
pb.TranscribeService_TranscribeService_ByteDance: &byteDanceTranscriber{}, // 服务池 languages 字段承担,本测试钉住过滤语义)。
pb.TranscribeService_TranscribeService_Ali: &aliTranscriber{}, 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
// 阿里转写的语言支持判定:paraformer-v2 不支持西语等,必须返回 false, want string
// 否则西语音频会被送进阿里、静默返回 SUCCESS_WITH_NO_VALID_FRAGMENT(看着像"音频没声音")。 ok bool
func TestAliTranscriberSupportsLanguage(t *testing.T) { }{
tr := &aliTranscriber{} {aliLangs, "es-ES", false}, // 西语:阿里 paraformer-v2 不支持,必须被过滤
for _, l := range []string{"zh-CN", "zh-TW", "yue-HK", "en-US", "ja-JP", "ko-KR", "de-DE", "fr-FR", "ru-RU"} { {aliLangs, "zh-CN", true},
if !tr.SupportsLanguage(l) { {aliLangs, "zh", true}, // 主语言段命中
t.Errorf("paraformer-v2 应支持 %s,却返回 false", l) {aliLangs, "en-GB", true}, // en 主段命中
} {aliLangs, "pt-BR", false},
{nil, "es-ES", true}, // 未配置 languages = 不限
{aliLangs, "", true}, // 空语言 = 不过滤(交给引擎自动检测)
} }
// 这些语种 bcp47ToDashScope 里有映射,但 paraformer-v2 实际不支持 for _, c := range cases {
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 got := svcSupportsLanguage(c.langs, c.want); got != c.ok {
if tr.SupportsLanguage(l) { t.Errorf("svcSupportsLanguage(%v, %q) = %v, want %v", c.langs, c.want, got, c.ok)
t.Errorf("paraformer-v2 不支持 %s,却返回 true(会导致空转写)", l)
} }
} }
if !tr.SupportsLanguage("") {
t.Error("空语言应允许 DashScope 自动检测")
}
} }
// 字节转写支持西语,是西语会议兜底的前提。 // 作用域具体度(scopeRank)回归:(app,region) > (app,0) > ('',region) > ('',0),
func TestByteDanceSupportsSpanish(t *testing.T) { // 非本部署作用域链上的行必须是 0。
if !(&byteDanceTranscriber{}).SupportsLanguage("es-ES") { func TestScopeRank(t *testing.T) {
t.Fatal("字节应支持 es-ES(西语兜底目标)") const app, reg = "voitrans", int32(1)
}
}
// 核心回归:即使 DefaultService 被固定成 Ali(线上就是这么配的),
// 西语也不能进阿里,必须改道到支持它的字节。
func TestPickTranscribeServiceAvoidsUnsupportedLang(t *testing.T) {
trs := testTranscribers()
ali := pb.TranscribeService_TranscribeService_Ali
byteDance := pb.TranscribeService_TranscribeService_ByteDance
cases := []struct { cases := []struct {
lang string appName string
resolved pb.TranscribeService // ResolveServiceType 的结果(DefaultService=Ali) region int32
wantSvc pb.TranscribeService want int
wantChanged bool
}{ }{
{"es-ES", ali, byteDance, true}, // 西语:阿里不支持 → 改道字节(本次修复的 bug) {"voitrans", 1, 4},
{"pt-BR", ali, byteDance, true}, // 葡语:同上 {"voitrans", 0, 3},
{"zh-CN", ali, ali, false}, // 中文:阿里支持 → 不乱改道 {"", 1, 2},
{"ru-RU", ali, ali, false}, // 俄语:阿里支持 → 维持 {"", 0, 1},
{"en-US", byteDance, byteDance, false}, // 英语:字节支持 → 维持 {"otherapp", 1, 0}, // 别的应用
{"voitrans", 2, 0}, // 别的区域
} }
for _, c := range cases { for _, c := range cases {
got, changed, supported := pickTranscribeService(trs, c.resolved, c.lang) if got := scopeRank(c.appName, c.region, app, reg); got != c.want {
if got != c.wantSvc || changed != c.wantChanged { t.Errorf("scopeRank(%q,%d) = %d, want %d", c.appName, c.region, got, c.want)
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))
} }
} }
} }
// 没有任何服务商支持该语言时:维持原选择、不改道、并明确报告 unsupported(调用方据此告警)。 // 区域覆盖合并 + 解密(resolveSvcFields)回归:
func TestPickTranscribeServiceNoneSupports(t *testing.T) { // Overrides 覆盖默认值、ExtraFields 追加、DisabledKeys 剔除;加密字段解不开必须整体报错(fail-closed)。
// 只放阿里,喂一个阿里不支持的语种 → 无处可去 func TestResolveSvcFields(t *testing.T) {
trs := map[pb.TranscribeService]Transcriber{ const key = "test-field-encrypt-key"
pb.TranscribeService_TranscribeService_Ali: &aliTranscriber{}, 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 _, ok := rs.Fields["obsolete"]; ok {
if got != pb.TranscribeService_TranscribeService_Ali || changed { t.Errorf("DisabledKeys 应剔除 obsolete")
t.Errorf("无可用兜底时应维持原选择且不改道,got=%s changed=%v", ServiceTypeName(got), changed)
} }
if supported { // fail-closed:错的 key 解不开必须报错,绝不把密文当明文用
t.Error("应报告 unsupported=false,让调用方告警") if _, err := resolveSvcFields(svc, nil, "wrong-key"); err == nil {
t.Errorf("错误密钥应整体报错(fail-closed),不能静默把密文当明文")
} }
} }

35
apps/services/modules/echomeet/options.go

@ -11,38 +11,13 @@ type (
modules.IOptions modules.IOptions
} }
// SummaryOptions 总结所用对话模型配置。 // Options echomeet 模块配置。
// 字节走豆包:填 Apikey + Model; //
// Google 走 Gemini:填 Apikey + Model(Endpoint 可选); // 第三方服务(识别/翻译/总结)不再走 yaml:全部由后台「会议记录服务」编排
// 阿里 / 微软走 OpenAI 兼容端点(阿里 DashScope、微软 Azure OpenAI):填 BaseURL + Token + Model。 // (console 共享库 echomeet_orch + 服务池 svc_config)配置,运行时直读 + NATS 热重载。
SummaryOptions struct { // 部署身份与解密密钥由 .env 注入:APP_NAME / APP_REGION / FIELD_ENCRYPT_KEY。
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 struct { Options struct {
modules.Options modules.Options
// DefaultService 默认服务商:留空 / Auto = 按源语言自动路由(以前的方案);
// 指定 ByteDance / Ali / Google / Microsoft = 该会议的撰写、翻译、总结全部走此服务商。
// 客户端请求若显式带了 ServiceType,则以请求为准、覆盖此默认值。
DefaultService string
ByteDance ProviderOptions
Ali ProviderOptions
Google ProviderOptions
Microsoft ProviderOptions
} }
) )

289
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
}

98
apps/services/modules/echomeet/provider_ali.go

@ -2,64 +2,29 @@ package echomeet
import ( import (
"context" "context"
"yunyan/lego/sys/log" "fmt"
"yunyan/pb" "yunyan/pb"
"yunyan/sys/aliyun/filetrans" "yunyan/sys/aliyun/filetrans"
"yunyan/sys/aliyun/translate" alitranslate "yunyan/sys/aliyun/translate"
"yunyan/sys/openai"
"fmt"
"strings"
) )
// newAliProviders 初始化阿里系单元服务(转写=DashScope 录音文件转写、翻译=阿里翻译、 // 阿里系单元服务实例(由 provider_registry.go 按服务池字段构造):
// 总结=通义千问 DashScope OpenAI 兼容端点),返回组合后的转写/翻译/总结三个 provider。 // 转写=DashScope 录音文件转写(paraformer-v2,异步)、翻译=阿里翻译。
func newAliProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { // 总结(通义千问)走 OpenAI 兼容端点,见 provider_registry.go 的 openaiSummarizer。
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 语言码)。
// //
// 坑:core.go 的 bcp47ToDashScope 里还映射了 es/pt/it/vi/tr/pl/nl/uk/th/ar/id/ms/fil, // 语言支持过滤已上移到编排层:服务池 asrfile_alibaba 模板的 languages 字段 seed 了
// 但 paraformer-v2 并不支持它们——把这些语种的音频送进去,引擎识别不出任何内容, // paraformer-v2 实际支持的语种,选路时不支持的语言会跳过本服务(原 SupportsLanguage 白名单
// 转写查询会返回 SUCCESS_WITH_NO_VALID_FRAGMENT(看起来像"音频没声音",实则是模型不认识该语种)。 // 的职责由后台配置接管——把西语送进 paraformer-v2 会静默返回空转写,务必别放开 languages)。
// 若将来把 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,
}
func (this *aliTranscriber) SupportsLanguage(bcp47 string) bool { // 阿里云 DashScope 转写
if strings.TrimSpace(bcp47) == "" { type aliTranscriber struct {
return true // 空 = 不指定 hints,交给 DashScope 自动检测 sys filetrans.ISys
}
return dashScopeParaformerV2Langs[toDashScopeLang(bcp47)]
} }
func (this *aliTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { func (this *aliTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) {
aliLang := toDashScopeLang(req.Language) 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 { if err != nil {
return nil, fmt.Errorf("ali filetrans create task: %w", err) 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) { 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 { switch status {
case filetrans.StatusSuccess: case filetrans.StatusSuccess:
out := make([]*pb.ContextStruct, 0, len(contexts)) out := make([]*pb.ContextStruct, 0, len(contexts))
@ -88,39 +53,10 @@ func (this *aliTranscriber) Query(_ context.Context, taskID, _ string) (*QueryRe
} }
// 阿里翻译 // 阿里翻译
type aliTranslator struct{} type aliTranslator struct {
sys alitranslate.ISys
func (this *aliTranslator) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_Ali
} }
func (this *aliTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { func (this *aliTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) {
return translate.Translate(ctx, toAliTranslateLang(from), toAliTranslateLang(to), texts) return this.sys.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
} }

60
apps/services/modules/echomeet/provider_bytedance.go

@ -2,43 +2,21 @@ package echomeet
import ( import (
"context" "context"
"yunyan/lego/sys/log" "fmt"
"yunyan/pb" "yunyan/pb"
"yunyan/sys/bytedance/audomodel" "yunyan/sys/bytedance/audomodel"
"yunyan/sys/bytedance/translate" bdtranslate "yunyan/sys/bytedance/translate"
"yunyan/sys/doubao" "yunyan/sys/doubao"
"fmt"
) )
// newByteDanceProviders 初始化字节系单元服务(转写=audomodel、翻译=火山翻译、总结=豆包), // 字节系单元服务实例(由 provider_registry.go 按服务池字段构造):
// 返回组合后的转写/翻译/总结三个 provider。 // 转写=audomodel(短音频 flash 同步 + 长音频异步)、翻译=火山翻译、总结=豆包。
func newByteDanceProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { // 语言支持过滤已上移到编排层(服务池 languages 字段),此处只做语言码转换。
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}
}
// 字节跳动转写:短音频 RecognizeFlash(同步) + 长音频 CreateTask+QueryTask(异步) // 字节跳动转写:短音频 RecognizeFlash(同步) + 长音频 CreateTask+QueryTask(异步)
type byteDanceTranscriber struct{} type byteDanceTranscriber struct {
sys audomodel.ISys
func (this *byteDanceTranscriber) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_ByteDance
}
func (this *byteDanceTranscriber) SupportsLanguage(bcp47 string) bool {
_, ok := toByteDanceLang(bcp47)
return ok
} }
const ( const (
@ -50,7 +28,7 @@ func (this *byteDanceTranscriber) Submit(_ context.Context, req SubmitRequest) (
lang, _ := toByteDanceLang(req.Language) lang, _ := toByteDanceLang(req.Language)
// 短音频走 flash 同步通道 // 短音频走 flash 同步通道
if req.Seconds <= byteDanceFlashMaxSeconds && req.SizeBytes <= byteDanceFlashMaxBytes { 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 { if err != nil {
return nil, fmt.Errorf("bytedance flash err: status=%s %w", statusCode, err) 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 return &SubmitResult{Done: true, Contexts: out}, nil
} }
// 长音频走异步 submit // 长音频走异步 submit(CallbackURL 由编排层按部署对外地址拼好传入)
taskID, logID, err := audomodel.CreateTask(req.Uid, req.AudioURL, req.EnableSpeaker, lang, req.CallbackURL, req.CallbackData) taskID, logID, err := this.sys.CreateTask(req.Uid, req.AudioURL, req.EnableSpeaker, lang, req.CallbackURL, req.CallbackData)
if err != nil { if err != nil {
return nil, fmt.Errorf("bytedance create task: %w", err) 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) { 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 { switch code {
case "20000000": case "20000000":
if err != nil { if err != nil {
@ -97,15 +75,13 @@ func (this *byteDanceTranscriber) Query(_ context.Context, taskID, logID string)
} }
} }
// 字节翻译 // 火山翻译
type byteDanceTranslator struct{} type byteDanceTranslator struct {
sys bdtranslate.ISys
func (this *byteDanceTranslator) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_ByteDance
} }
func (this *byteDanceTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { 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 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) { func (this *doubaoSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) {
if this.sys == nil { if this.sys == nil {
return "", fmt.Errorf("doubao summarizer not initialized") return "", fmt.Errorf("doubao summarizer not initialized")

60
apps/services/modules/echomeet/provider_google.go

@ -2,48 +2,24 @@ package echomeet
import ( import (
"context" "context"
"yunyan/lego/sys/log" "fmt"
"yunyan/pb" "yunyan/pb"
"yunyan/sys/google/gemini" "yunyan/sys/google/gemini"
"yunyan/sys/google/speech" gspeech "yunyan/sys/google/speech"
"yunyan/sys/google/translate" gtranslate "yunyan/sys/google/translate"
"fmt"
) )
// newGoogleProviders 初始化 Google 系单元服务(转写=Cloud Speech、翻译=Cloud Translation、总结=Gemini), // Google 系单元服务实例(由 provider_registry.go 按服务池字段构造):
// 返回组合后的转写/翻译/总结三个 provider。 // 转写=Cloud Speech、翻译=Cloud Translation、总结=Gemini。
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 Cloud Speech-to-Text V1 LongRunningRecognize(要求 gs:// 音频) // Google Cloud Speech-to-Text V1 LongRunningRecognize(要求 gs:// 音频)
type googleTranscriber struct{} type googleTranscriber struct {
sys gspeech.ISys
func (this *googleTranscriber) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_Google
}
// Google Speech 支持的语种较广,不做白名单限制(gs:// 限制在 Submit 内)
func (this *googleTranscriber) SupportsLanguage(_ string) bool {
return true
} }
func (this *googleTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { 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 { if err != nil {
return nil, fmt.Errorf("google speech create task: %w", err) 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) { 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 { switch status {
case speech.StatusSuccess: case gspeech.StatusSuccess:
out := make([]*pb.ContextStruct, 0, len(contexts)) out := make([]*pb.ContextStruct, 0, len(contexts))
for _, c := range contexts { for _, c := range contexts {
out = append(out, &pb.ContextStruct{ 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 return &QueryResult{Status: StatusSuccess, Contexts: out}, nil
case speech.StatusRunning: case gspeech.StatusRunning:
return &QueryResult{Status: StatusRunning}, nil return &QueryResult{Status: StatusRunning}, nil
default: default:
return &QueryResult{Status: StatusFailed}, fmt.Errorf("google speech query failed: %w", err) 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 // Google Cloud Translation v3
type googleTranslator struct{} type googleTranslator struct {
sys gtranslate.ISys
func (this *googleTranslator) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_Google
} }
func (this *googleTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { 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 总结 // Gemini 总结
@ -87,10 +61,6 @@ type geminiSummarizer struct {
sys gemini.ISys 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) { func (this *geminiSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) {
if this.sys == nil { if this.sys == nil {
return "", fmt.Errorf("gemini summarizer not initialized") return "", fmt.Errorf("gemini summarizer not initialized")

73
apps/services/modules/echomeet/provider_microsoft.go

@ -2,50 +2,25 @@ package echomeet
import ( import (
"context" "context"
"yunyan/lego/sys/log" "fmt"
"yunyan/pb" "yunyan/pb"
"yunyan/sys/microsoft/speech" msspeech "yunyan/sys/microsoft/speech"
"yunyan/sys/microsoft/translate" mstranslate "yunyan/sys/microsoft/translate"
"yunyan/sys/openai" "yunyan/sys/openai"
"fmt"
) )
// newMicrosoftProviders 初始化微软系单元服务(转写=Azure Speech、翻译=Azure Translator、 // 微软系单元服务实例(由 provider_registry.go 按服务池字段构造):
// 总结=Azure OpenAI 兼容端点),返回组合后的转写/翻译/总结三个 provider。 // 转写=Azure Speech Batch、翻译=Azure Translator。
func newMicrosoftProviders(opt ProviderOptions) (Transcriber, Translator, Summarizer) { // 总结(Azure OpenAI)走 OpenAI 兼容端点(openaiSummarizer)。
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 &microsoftTranscriber{}, &microsoftTranslator{}, &microsoftSummarizer{sys: sys}
}
// Azure Speech Batch Transcription // Azure Speech Batch Transcription
type microsoftTranscriber struct{} type microsoftTranscriber struct {
sys msspeech.ISys
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
} }
func (this *microsoftTranscriber) Submit(_ context.Context, req SubmitRequest) (*SubmitResult, error) { 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 { if err != nil {
return nil, fmt.Errorf("microsoft speech create task: %w", err) 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) { 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 { switch status {
case speech.StatusSuccess: case msspeech.StatusSuccess:
out := make([]*pb.ContextStruct, 0, len(contexts)) out := make([]*pb.ContextStruct, 0, len(contexts))
for _, c := range contexts { for _, c := range contexts {
out = append(out, &pb.ContextStruct{ 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 return &QueryResult{Status: StatusSuccess, Contexts: out}, nil
case speech.StatusRunning: case msspeech.StatusRunning:
return &QueryResult{Status: StatusRunning}, nil return &QueryResult{Status: StatusRunning}, nil
default: default:
return &QueryResult{Status: StatusFailed}, fmt.Errorf("microsoft speech query failed: %w", err) 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 // Azure Translator
type microsoftTranslator struct{} type microsoftTranslator struct {
sys mstranslate.ISys
func (this *microsoftTranslator) Name() pb.TranscribeService {
return pb.TranscribeService_TranscribeService_Microsoft
} }
func (this *microsoftTranslator) Translate(ctx context.Context, from, to string, texts []string) ([]string, error) { 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 兼容端点) // openaiSummarizer OpenAI 兼容端点总结(openai/qwen/deepseek/azure openai…共用)。
type microsoftSummarizer struct { type openaiSummarizer struct {
sys openai.ISys sys openai.ISys
} }
func (this *microsoftSummarizer) Name() pb.TranscribeService { func (this *openaiSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) {
return pb.TranscribeService_TranscribeService_Microsoft
}
func (this *microsoftSummarizer) Chat(ctx context.Context, messages []ChatMessage) (string, error) {
if this.sys == nil { 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)) msgs := make([]openai.ChatReq, 0, len(messages))
for _, m := range messages { for _, m := range messages {
@ -106,7 +75,7 @@ func (this *microsoftSummarizer) Chat(ctx context.Context, messages []ChatMessag
return "", err return "", err
} }
if resp == nil { if resp == nil {
return "", fmt.Errorf("microsoft(azure openai) chat: empty response") return "", fmt.Errorf("openai-compatible chat: empty response")
} }
return resp.Content, nil return resp.Content, nil
} }

229
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 &microsoftTranscriber{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 &microsoftTranslator{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
}
}

414
apps/services/modules/echomeet/providers.go

@ -2,24 +2,47 @@ package echomeet
import ( import (
"context" "context"
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"yunyan/comm"
"yunyan/lego/core" "yunyan/lego/core"
"yunyan/lego/core/cbase" "yunyan/lego/core/cbase"
"yunyan/lego/sys/cron"
"yunyan/pb" "yunyan/pb"
"fmt" natssys "yunyan/sys/nats"
"strings"
gonats "github.com/nats-io/nats.go"
) )
// providersComp 服务聚合组件: // providersComp 会议记录服务编排消费组件。
// - Init 阶段按 echomeet 模块配置初始化字节/阿里/Google/微软四套单元服务(转写+翻译+总结), //
// 并把它们组合成「按服务商成套」的 provider 集合; // 不再是「yaml 四套凭据 + 一会一服务商」:三段(识别/翻译/总结)各自由后台编排
// - 运行期把转写/翻译/总结三类能力按 ServiceType 路由到同一服务商的 provider。 // (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 { type providersComp struct {
cbase.ModuleCompBase cbase.ModuleCompBase
module *Echomeet module *Echomeet
transcribers map[pb.TranscribeService]Transcriber // 部署身份(env 注入,与 api 服务 LoadOrSeedServiceConfig 同源)。
translators map[pb.TranscribeService]Translator appName string
summarizers map[pb.TranscribeService]Summarizer 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 包 // 任务状态常量,对齐底层 sys 包
@ -41,7 +64,7 @@ type SubmitRequest struct {
CallbackData string CallbackData string
} }
// SubmitResult 转写提交结果。Done=true 时同步完成(仅 ByteDance flash),直接取 Contexts; // SubmitResult 转写提交结果。Done=true 时同步完成(如字节短音频 flash),直接取 Contexts;
// 否则需要后续调用 Query 拉取异步结果。 // 否则需要后续调用 Query 拉取异步结果。
type SubmitResult struct { type SubmitResult struct {
Done bool Done bool
@ -57,8 +80,7 @@ type QueryResult struct {
} }
// ChatMessage 对齐 sys/doubao / sys/google/gemini 的统一消息结构。 // ChatMessage 对齐 sys/doubao / sys/google/gemini 的统一消息结构。
// Images 为可选的图片 url 列表(多模态总结用):非空时该条消息以「文本 + 图片」的多模态形式 // Images 为可选的图片 url 列表(多模态总结用)。
// 发给模型;为空时退化为纯文本,行为与以前一致。要求所配模型支持视觉输入。
type ChatMessage struct { type ChatMessage struct {
Role string Role string
Content string Content string
@ -67,21 +89,17 @@ type ChatMessage struct {
// Transcriber 转写 provider 接口 // Transcriber 转写 provider 接口
type Transcriber interface { type Transcriber interface {
Name() pb.TranscribeService
SupportsLanguage(bcp47 string) bool
Submit(ctx context.Context, req SubmitRequest) (*SubmitResult, error) Submit(ctx context.Context, req SubmitRequest) (*SubmitResult, error)
Query(ctx context.Context, taskID, logID string) (*QueryResult, error) Query(ctx context.Context, taskID, logID string) (*QueryResult, error)
} }
// Translator 翻译 provider 接口 // Translator 翻译 provider 接口
type Translator interface { type Translator interface {
Name() pb.TranscribeService
Translate(ctx context.Context, from, to string, texts []string) ([]string, error) Translate(ctx context.Context, from, to string, texts []string) ([]string, error)
} }
// Summarizer AI 总结 provider 接口 // Summarizer AI 总结 provider 接口
type Summarizer interface { type Summarizer interface {
Name() pb.TranscribeService
Chat(ctx context.Context, messages []ChatMessage) (string, error) 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) { 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.ModuleCompBase.Init(service, module, comp, options)
this.module = module.(*Echomeet) this.module = module.(*Echomeet)
opt := options.(*Options) // 部署身份由 .env 注入(APP_NAME/APP_REGION/FIELD_ENCRYPT_KEY),与 api 服务同源。
this.appName = os.Getenv("APP_NAME")
// 按服务商成套初始化:每套各自完成转写/翻译/总结三个单元服务的装配。 this.regionCode = os.Getenv("APP_REGION")
bdTranscriber, bdTranslator, bdSummarizer := newByteDanceProviders(opt.ByteDance) this.region = comm.RegionFromCode(this.regionCode)
aliTranscriber, aliTranslator, aliSummarizer := newAliProviders(opt.Ali) this.encKey = os.Getenv("FIELD_ENCRYPT_KEY")
ggTranscriber, ggTranslator, ggSummarizer := newGoogleProviders(opt.Google) this.snap = &orchSnapshot{Services: map[string]*resolvedSvc{}}
msTranscriber, msTranslator, msSummarizer := newMicrosoftProviders(opt.Microsoft) this.transcribers = map[string]Transcriber{}
this.translators = map[string]Translator{}
this.transcribers = map[pb.TranscribeService]Transcriber{ this.summarizers = map[string]Summarizer{}
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)
return 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。 // subscribeConfigChanged 订阅 console 配置变更广播:编排本身或服务池(凭据/区域覆盖)变了都重载。
func parseServiceType(s string) pb.TranscribeService { func (this *providersComp) subscribeConfigChanged() {
switch strings.ToLower(strings.TrimSpace(s)) { conn := natssys.Conn()
case "bytedance", "byte", "字节": if conn == nil {
return pb.TranscribeService_TranscribeService_ByteDance this.module.Warnf("echomeet 编排: NATS 未就绪,跳过订阅(cron 定时兜底)")
case "ali", "aliyun", "阿里", "阿里云": return
return pb.TranscribeService_TranscribeService_Ali }
case "google", "谷歌": _, err := conn.Subscribe(comm.Nats_ConfigChangedSubject, func(msg *gonats.Msg) {
return pb.TranscribeService_TranscribeService_Google var ev comm.ConfigChangedEvent
case "microsoft", "azure", "微软": if err := json.Unmarshal(msg.Data, &ev); err != nil {
return pb.TranscribeService_TranscribeService_Microsoft return
default: }
return pb.TranscribeService_TranscribeService_Auto 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 解析本次会议最终使用的服务商。优先级: // Reload 全量重载编排快照并重建 provider 实例。失败保留旧快照(好过清空导致全部会议失败)。
// 1. 请求显式指定 ServiceType → 直接采用(覆盖配置); func (this *providersComp) Reload() {
// 2. 模块配置 DefaultService 指定了服务商 → 采用配置; snap, err := loadOrchSnapshot(this.appName, this.region, this.regionCode, this.encKey)
// 3. 默认模式(以前的方案):按源语言自动路由——字节支持该语言走字节,否则走阿里。 if err != nil {
// this.module.Errorf("echomeet 编排: 加载失败,保留现有快照: %v", err)
// 解析结果一旦确定,会议撰写、翻译、总结三步都走同一服务商。 return
func (this *providersComp) ResolveServiceType(svcType pb.TranscribeService, language string) pb.TranscribeService {
if svcType != pb.TranscribeService_TranscribeService_Auto && svcType != pb.TranscribeService_TranscribeService_Unknown {
return svcType
} }
if d := parseServiceType(this.module.options.DefaultService); d != pb.TranscribeService_TranscribeService_Auto { transcribers := map[string]Transcriber{}
return d 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 { build(snap.ASR, comm.EchoKindASR)
return pb.TranscribeService_TranscribeService_ByteDance 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 有真实语言表,最可信,排首位。 // PickMT 翻译选路:按后台 Priority 升序(服务配了 languages 则过滤,未配不限)。
var transcribeFallbackOrder = []pb.TranscribeService{ func (this *providersComp) PickMT(language string) (svcId string, t Translator, err error) {
pb.TranscribeService_TranscribeService_ByteDance, this.mu.RLock()
pb.TranscribeService_TranscribeService_Ali, defer this.mu.RUnlock()
pb.TranscribeService_TranscribeService_Microsoft, for _, r := range this.snap.MT {
pb.TranscribeService_TranscribeService_Google, 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 // PickLLM 总结选路:clientSvcId 非空且后台开关允许客户端指定时,在 ClientVisible 且已构建
// 选第一个支持的服务商。返回 (选中的服务商, 是否改道, 选中的服务商是否真的支持该语言)。 // 的服务里精确匹配;否则默认模型(IsDefault);再否则按 Priority 第一个可用的。
func pickTranscribeService(transcribers map[pb.TranscribeService]Transcriber, resolved pb.TranscribeService, language string) (svc pb.TranscribeService, changed bool, supported bool) { func (this *providersComp) PickLLM(clientSvcId string) (svcId string, s Summarizer, err error) {
if t, ok := transcribers[resolved]; ok && t != nil && t.SupportsLanguage(language) { this.mu.RLock()
return resolved, false, true 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 { var first *comm.EchoOrchestration
if s == resolved { for _, r := range this.snap.LLM {
if this.summarizers[r.SvcId] == nil {
continue continue
} }
if t, ok := transcribers[s]; ok && t != nil && t.SupportsLanguage(language) { if r.IsDefault {
return s, true, true 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 之上叠加「所选服务商是否支持该源语言」的校验, // ==================== 按 svc_id 取实例(record 存量路由:轮询/翻译/总结) ====================
// 不支持时换到第一个支持的服务商。
// func (this *providersComp) GetTranscriber(svcId string) (Transcriber, error) {
// 为什么需要:ResolveServiceType 只看请求/配置/字节语言表,一旦 DefaultService 固定成某家, this.mu.RLock()
// 就会把引擎不认识的语种也硬塞进去。典型事故是西语被送进阿里 paraformer-v2(该模型不支持西语), defer this.mu.RUnlock()
// 引擎静默返回 SUCCESS_WITH_NO_VALID_FRAGMENT,表现得像「音频没声音」,极难排查。 if t, ok := this.transcribers[svcId]; ok && t != nil {
// 只作用于**转写**选路;翻译/总结仍沿用记录已定的 ServiceType(三段同源不变)。 return t, nil
func (this *providersComp) ResolveTranscribeService(svcType pb.TranscribeService, language string) pb.TranscribeService { }
resolved := this.ResolveServiceType(svcType, language) return nil, fmt.Errorf("识别服务 %s 已不在编排中(可能被后台移除)", svcId)
picked, changed, supported := pickTranscribeService(this.transcribers, resolved, language) }
if changed {
this.module.Warnf("转写服务 %s 不支持语言 %s,改用 %s", ServiceTypeName(resolved), language, ServiceTypeName(picked)) func (this *providersComp) GetTranslator(svcId string) (Translator, error) {
} else if !supported { this.mu.RLock()
this.module.Warnf("无转写服务支持语言 %s,维持 %s(转写可能失败)", language, ServiceTypeName(picked)) defer this.mu.RUnlock()
} if t, ok := this.translators[svcId]; ok && t != nil {
return picked return t, nil
} }
return nil, fmt.Errorf("翻译服务 %s 已不在编排中(可能被后台移除)", svcId)
// ==================== 工厂方法 ==================== }
// GetTranscriber 取转写 provider;svcType=Auto/Unknown 时先 ResolveServiceType。 func (this *providersComp) GetSummarizer(svcId string) (Summarizer, error) {
func (this *providersComp) GetTranscriber(svcType pb.TranscribeService, language string) (Transcriber, error) { this.mu.RLock()
resolved := this.ResolveServiceType(svcType, language) defer this.mu.RUnlock()
if p, ok := this.transcribers[resolved]; ok && p != nil { if s, ok := this.summarizers[svcId]; ok && s != nil {
return p, nil return s, 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))
} }
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
} }

162
apps/services/modules/echomeet/tasks.go

@ -10,8 +10,6 @@ import (
"yunyan/lego/sys/mysql" "yunyan/lego/sys/mysql"
redissys "yunyan/lego/sys/redis" redissys "yunyan/lego/sys/redis"
"yunyan/pb" "yunyan/pb"
"yunyan/sys/aliyun/filetrans"
"yunyan/sys/bytedance/audomodel"
"yunyan/utils" "yunyan/utils"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -169,33 +167,15 @@ func (this *tasksComp) SubmitShortAudioTask(task *pb.DBEchoMeetRecord) (err erro
return return
} }
// 提交阿里云音频转写任务(字节不支持的语言走此通道) // SubmitTranscribeTask 统一异步转写提交:按 record.AsrSvcId(后台编排选路结果)取实例,
func (this *tasksComp) SubmitAliAudioTask(task *pb.DBEchoMeetRecord) (err error) { // 单次远程提交(无需 Redis 速率队列),失败直接置为 TranscribeFail。
this.module.Infof("SubmitAliAudioTask id:%d uid:%s lang:%s → 调用阿里云转写", task.Id, task.Uid, task.Formlanguage) // 回调地址来自编排解析(部署对外地址 + 服务 callback_path),无回调的服务商靠轮询兜底。
aliLang := toDashScopeLang(task.Formlanguage) // 字节实例对短音频会同步完成(Done=true)——那种情况应走 SubmitShortAudioTask 限流队列,
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。
func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err error) { func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err error) {
this.module.Infof("SubmitTranscribeTask id:%d uid:%s service:%s lang:%s", this.module.Infof("SubmitTranscribeTask id:%d uid:%s svc:%s lang:%s",
task.Id, task.Uid, ServiceTypeName(task.ServiceType), task.Formlanguage) task.Id, task.Uid, task.AsrSvcId, task.Formlanguage)
transcriber, err := this.module.providers.GetTranscriber(task.ServiceType, task.Formlanguage) transcriber, err := this.module.providers.GetTranscriber(task.AsrSvcId)
if err != nil { if err != nil {
this.module.Errorf("SubmitTranscribeTask id:%d 路由失败 err:%v", task.Id, err) this.module.Errorf("SubmitTranscribeTask id:%d 路由失败 err:%v", task.Id, err)
task.State = pb.DBEchoMeetRecordState_TranscribeFail task.State = pb.DBEchoMeetRecordState_TranscribeFail
@ -209,6 +189,8 @@ func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err erro
EnableSpeaker: task.Isdistinguishspeaker, EnableSpeaker: task.Isdistinguishspeaker,
Seconds: task.Seconds, Seconds: task.Seconds,
SizeBytes: task.Size, SizeBytes: task.Size,
CallbackURL: this.module.providers.ASRCallbackURL(task.AsrSvcId),
CallbackData: fmt.Sprintf("%d", task.Id),
}) })
if err != nil { if err != nil {
this.module.Errorf("SubmitTranscribeTask id:%d 提交失败 err:%v", task.Id, err) 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) this.module.model.saverecord(task)
return err return err
} }
if result.Done { // 同步完成(字节 flash):直接走完成收尾
this.finishSyncTranscribe(task, result.Contexts)
return
}
task.Taskid = result.TaskID task.Taskid = result.TaskID
task.Logid = result.LogID task.Logid = result.LogID
task.State = pb.DBEchoMeetRecordState_Transcribing task.State = pb.DBEchoMeetRecordState_Transcribing
@ -224,25 +210,6 @@ func (this *tasksComp) SubmitTranscribeTask(task *pb.DBEchoMeetRecord) (err erro
return 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任务 // 提交AI任务
func (this *tasksComp) SubmitAITask(task *pb.DBEchoMeetRecord) (err error) { func (this *tasksComp) SubmitAITask(task *pb.DBEchoMeetRecord) (err error) {
this.module.Infof("SubmitAITask id:%d uid:%s → 进入AI等待队列", task.Id, task.Uid) 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 { if !ok || rec == nil {
return return
} }
this.module.Infof("ShortAudioProcess id:%d uid:%s lang:%s → 开始转写", rec.Id, rec.Uid, rec.Formlanguage) this.module.Infof("ShortAudioProcess id:%d uid:%s svc:%s lang:%s → 开始转写", rec.Id, rec.Uid, rec.AsrSvcId, rec.Formlanguage)
bdLang, _ := toByteDanceLang(rec.Formlanguage) var result *SubmitResult
statusCode, logID, contexts, err := audomodel.RecognizeFlash(rec.Uid, rec.Audiourl, rec.Isdistinguishspeaker, bdLang) 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 { 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 rec.State = pb.DBEchoMeetRecordState_TranscribeFail
this.module.model.saverecord(rec) this.module.model.saverecord(rec)
if user, err := this.module.model.getuser(rec.Uid); err == nil { 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() _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err()
return 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{}) personnels := make(map[string]struct{})
pbcontexts := make([]*pb.ContextStruct, 0, len(contexts)) for _, c := range contexts {
for _, ctx := range contexts { c.Meetingid = rec.Id
pbcontexts = append(pbcontexts, &pb.ContextStruct{ if c.Speaker != "" && !strings.HasPrefix(c.Speaker, "Speaker") {
Meetingid: rec.Id, c.Speaker = fmt.Sprintf("Speaker %s", c.Speaker)
Content: ctx.Content, }
Starttime: ctx.StartTime, personnels[c.Speaker] = struct{}{}
Endtime: ctx.EndTime,
Speaker: fmt.Sprintf("Speaker %s", ctx.Speaker),
})
personnels[ctx.Speaker] = struct{}{}
} }
rec.Personnel = "" rec.Personnel = ""
for k, _ := range personnels { for k := range personnels {
rec.Personnel += fmt.Sprintf("[%s] ", k) rec.Personnel += fmt.Sprintf("[%s] ", k)
} }
rec.Original = utils.ToString(contexts)
rec.Original = utils.ToString(pbcontexts)
rec.State = pb.DBEchoMeetRecordState_AwaitSummarizing rec.State = pb.DBEchoMeetRecordState_AwaitSummarizing
this.TranslateProcess(rec, pbcontexts) this.TranslateProcess(rec, contexts)
this.module.model.saverecord(rec) this.module.model.saverecord(rec)
// 最小实现:完成即移除在执行集合
procKey := this.keyShortProc()
_ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err()
_ = this.SubmitAITask(rec) _ = this.SubmitAITask(rec)
} }
@ -354,7 +335,17 @@ func (this *tasksComp) TranslateProcess(rec *pb.DBEchoMeetRecord, contexts []*pb
for _, item := range contexts { for _, item := range contexts {
tests = append(tests, item.Content) 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 { if err != nil {
this.module.Errorf("TranslateProcess id:%d 取 translator 失败 err:%v", rec.Id, err) this.module.Errorf("TranslateProcess id:%d 取 translator 失败 err:%v", rec.Id, err)
return err return err
@ -365,14 +356,14 @@ func (this *tasksComp) TranslateProcess(rec *pb.DBEchoMeetRecord, contexts []*pb
// 判定「结果是否可用」只认长度对齐,不能只看 results==nil: // 判定「结果是否可用」只认长度对齐,不能只看 results==nil:
// 有的 provider(如字节)出错时用具名返回值裸 return,会带回一个长度不足的非 nil 切片。 // 有的 provider(如字节)出错时用具名返回值裸 return,会带回一个长度不足的非 nil 切片。
if err != nil && len(results) != len(tests) { if err != nil && len(results) != len(tests) {
this.module.Errorf("TranslateProcess id:%d 翻译失败 service:%s got=%d want=%d err:%v", this.module.Errorf("TranslateProcess id:%d 翻译失败 svc:%s got=%d want=%d err:%v",
rec.Id, ServiceTypeName(translator.Name()), len(results), len(tests), err) rec.Id, rec.MtSvcId, len(results), len(tests), err)
return err return err
} }
if err != nil { if err != nil {
// 部分失败但结果完整(provider 已用原文占位):记日志后继续,不让整篇译文作废。 // 部分失败但结果完整(provider 已用原文占位):记日志后继续,不让整篇译文作废。
this.module.Warnf("TranslateProcess id:%d 部分句子翻译失败(已用原文占位) service:%s err:%v", this.module.Warnf("TranslateProcess id:%d 部分句子翻译失败(已用原文占位) svc:%s err:%v",
rec.Id, ServiceTypeName(translator.Name()), err) rec.Id, rec.MtSvcId, err)
} }
for i, item := range contexts { for i, item := range contexts {
if i >= len(results) { 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() _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err()
return 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 { if err != nil {
this.module.Errorf("AIProcess id:%d 取 summarizer 失败 err:%v", rec.Id, err) this.module.Errorf("AIProcess id:%d 取 summarizer 失败 err:%v", rec.Id, err)
rec.State = pb.DBEchoMeetRecordState_SummarizFail 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() _ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err()
return 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 数组字符串),解析后作为多模态内容随 // 注释图片:用户创建记录时填写的 imageurls(JSON 数组字符串),解析后作为多模态内容随
// user 消息发给模型,让总结结合图片(白板/PPT/截图等)。为空/解析失败时 Images 为 nil, // user 消息发给模型,让总结结合图片(白板/PPT/截图等)。为空/解析失败时 Images 为 nil,
// provider 退化为纯文本。 // provider 退化为纯文本。
@ -518,17 +522,17 @@ func (this *tasksComp) AIProcess(task interface{}) {
_ = redissys.Conn().LRem(context.Background(), procKey, 1, fmt.Sprintf("%d", rec.Id)).Err() _ = 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 // 客户端在 GetRecord/GetRecords 拿到状态为 Transcribing 的记录时驱动调用;TranscribeQueryMinInterval
// 限制了查询频率。 // 限制了查询频率。
func (this *tasksComp) PollTranscribe(rec *pb.DBEchoMeetRecord) { 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 { if err != nil {
this.module.Errorf("转写轮询 id:%d 路由失败 err:%v", rec.Id, err) this.module.Errorf("转写轮询 id:%d 路由失败 err:%v", rec.Id, err)
return return
} }
this.module.Infof("转写轮询[%s] id:%d taskID:%s logID:%s", 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) result, err := transcriber.Query(context.Background(), rec.Taskid, rec.Logid)
if err != nil { if err != nil {
this.module.Errorf("转写轮询 id:%d 查询失败 err:%v,状态置为失败", rec.Id, err) this.module.Errorf("转写轮询 id:%d 查询失败 err:%v,状态置为失败", rec.Id, err)

234
apps/services/pb/echomeet_db.pb.go

@ -21,65 +21,6 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = 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 type DBEchoMeetRecordState int32
const ( const (
@ -131,11 +72,11 @@ func (x DBEchoMeetRecordState) String() string {
} }
func (DBEchoMeetRecordState) Descriptor() protoreflect.EnumDescriptor { 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 { 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 { func (x DBEchoMeetRecordState) Number() protoreflect.EnumNumber {
@ -144,24 +85,24 @@ func (x DBEchoMeetRecordState) Number() protoreflect.EnumNumber {
// Deprecated: Use DBEchoMeetRecordState.Descriptor instead. // Deprecated: Use DBEchoMeetRecordState.Descriptor instead.
func (DBEchoMeetRecordState) EnumDescriptor() ([]byte, []int) { 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 { type DBEchoMeetTemplate struct {
state protoimpl.MessageState `protogen:"open.v1"` 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 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 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"`) 来源 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"`) 标题 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"`) 描述 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"`) 语言 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"`) 类型 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"`) 标签 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"`) 图标 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"`) 大纲 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"`) 模板 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"`) 显示优先级,数值越大越靠前 Sort int32 `protobuf:"varint,12,opt,name=sort,proto3" json:"sort,omitempty" gorm:"sort;index"` //@go_tags(`gorm:"sort;index"`) 显示优先级,数值越大越靠前
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -283,35 +224,37 @@ func (x *DBEchoMeetTemplate) GetSort() int32 {
// 会议记录数据结构 // 会议记录数据结构
type DBEchoMeetRecord struct { type DBEchoMeetRecord struct {
state protoimpl.MessageState `protogen:"open.v1"` 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 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 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"`) 状态 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"`) 時間 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"`) 类型 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"`) 本地音频文件路径 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 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"`) 标题 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"`) 地址 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"`) 成员 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"`) 任务类型 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 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 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"`) 是否区分说话人 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 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"`) 语言 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"`) 目标语言 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"`) 时长 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"`) 文件大小 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"`) 听写内容 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"`) 翻译 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"`) 短总结 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"`) 总结 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"`) 转写服务类型 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(后台编排选路结果)
Starttime int64 `protobuf:"varint,25,opt,name=starttime,proto3" json:"starttime" gorm:"starttime"` //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒 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(后台编排选路结果)
Processduration int64 `protobuf:"varint,26,opt,name=processduration,proto3" json:"processduration" gorm:"processduration"` //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒 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(默认或客户端指定)
Lastquerytime int64 `protobuf:"varint,27,opt,name=lastquerytime,proto3" json:"lastquerytime" gorm:"lastquerytime"` //@go_tags(`gorm:"lastquerytime" json:"lastquerytime"`) 上一次主动查询第三方服务的 unix 秒,用于限制查询频率 Starttime int64 `protobuf:"varint,25,opt,name=starttime,proto3" json:"starttime" gorm:"starttime"` //@go_tags(`gorm:"starttime" json:"starttime"`) 处理开始时间 unix 秒
Remark string `protobuf:"bytes,28,opt,name=remark,proto3" json:"remark" gorm:"remark"` //@go_tags(`gorm:"remark" json:"remark"`) 用户备注(创建记录时填写,参与AI总结) Processduration int64 `protobuf:"varint,26,opt,name=processduration,proto3" json:"processduration" gorm:"processduration"` //@go_tags(`gorm:"processduration" json:"processduration"`) 处理耗时 秒
Imageurls string `protobuf:"bytes,29,opt,name=imageurls,proto3" json:"imageurls" gorm:"imageurls"` //@go_tags(`gorm:"imageurls" json:"imageurls"`) 注释图片url列表(JSON数组字符串,编码方式同original),作为多模态内容参与AI总结 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -507,11 +450,25 @@ func (x *DBEchoMeetRecord) GetSummary() string {
return "" 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 { 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 { func (x *DBEchoMeetRecord) GetStarttime() int64 {
@ -993,7 +950,7 @@ const file_echomeet_echomeet_db_proto_rawDesc = "" +
"\aoutline\x18\n" + "\aoutline\x18\n" +
" \x01(\tR\aoutline\x12\x1a\n" + " \x01(\tR\aoutline\x12\x1a\n" +
"\btemplate\x18\v \x01(\tR\btemplate\x12\x12\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" + "\x10DBEchoMeetRecord\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x04R\x02id\x12\x10\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x10\n" +
"\x03uid\x18\x02 \x01(\tR\x03uid\x12,\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" + "\boriginal\x18\x14 \x01(\tR\boriginal\x12\x1c\n" +
"\ttranslate\x18\x15 \x01(\tR\ttranslate\x12\x1a\n" + "\ttranslate\x18\x15 \x01(\tR\ttranslate\x12\x1a\n" +
"\boverview\x18\x16 \x01(\tR\boverview\x12\x18\n" + "\boverview\x18\x16 \x01(\tR\boverview\x12\x18\n" +
"\asummary\x18\x17 \x01(\tR\asummary\x125\n" + "\asummary\x18\x17 \x01(\tR\asummary\x12\x1c\n" +
"\fservice_type\x18\x18 \x01(\x0e2\x12.TranscribeServiceR\vserviceType\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" + "\tstarttime\x18\x19 \x01(\x03R\tstarttime\x12(\n" +
"\x0fprocessduration\x18\x1a \x01(\x03R\x0fprocessduration\x12$\n" + "\x0fprocessduration\x18\x1a \x01(\x03R\x0fprocessduration\x12$\n" +
"\rlastquerytime\x18\x1b \x01(\x03R\rlastquerytime\x12\x16\n" + "\rlastquerytime\x18\x1b \x01(\x03R\rlastquerytime\x12\x16\n" +
@ -1064,14 +1025,7 @@ const file_echomeet_echomeet_db_proto_rawDesc = "" +
"utterances\x12.\n" + "utterances\x12.\n" +
"\tadditions\x18\x03 \x01(\v2\x10.ResultAdditionsR\tadditions\"'\n" + "\tadditions\x18\x03 \x01(\v2\x10.ResultAdditionsR\tadditions\"'\n" +
"\tAudioInfo\x12\x1a\n" + "\tAudioInfo\x12\x1a\n" +
"\bduration\x18\x01 \x01(\x03R\bduration*\xc9\x01\n" + "\bduration\x18\x01 \x01(\x03R\bduration*\xb6\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" +
"\x15DBEchoMeetRecordState\x12\n" + "\x15DBEchoMeetRecordState\x12\n" +
"\n" + "\n" +
"\x06Unknow\x10\x00\x12\x15\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 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_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_echomeet_echomeet_db_proto_goTypes = []any{ var file_echomeet_echomeet_db_proto_goTypes = []any{
(TranscribeService)(0), // 0: TranscribeService (DBEchoMeetRecordState)(0), // 0: DBEchoMeetRecordState
(DBEchoMeetRecordState)(0), // 1: DBEchoMeetRecordState (*DBEchoMeetTemplate)(nil), // 1: DBEchoMeetTemplate
(*DBEchoMeetTemplate)(nil), // 2: DBEchoMeetTemplate (*DBEchoMeetRecord)(nil), // 2: DBEchoMeetRecord
(*DBEchoMeetRecord)(nil), // 3: DBEchoMeetRecord (*ContextStruct)(nil), // 3: ContextStruct
(*ContextStruct)(nil), // 4: ContextStruct (*Word)(nil), // 4: Word
(*Word)(nil), // 5: Word (*UtteranceAdditions)(nil), // 5: UtteranceAdditions
(*UtteranceAdditions)(nil), // 6: UtteranceAdditions (*Utterance)(nil), // 6: Utterance
(*Utterance)(nil), // 7: Utterance (*ResultAdditions)(nil), // 7: ResultAdditions
(*ResultAdditions)(nil), // 8: ResultAdditions (*RecognitionResult)(nil), // 8: RecognitionResult
(*RecognitionResult)(nil), // 9: RecognitionResult (*AudioInfo)(nil), // 9: AudioInfo
(*AudioInfo)(nil), // 10: AudioInfo
} }
var file_echomeet_echomeet_db_proto_depIdxs = []int32{ var file_echomeet_echomeet_db_proto_depIdxs = []int32{
1, // 0: DBEchoMeetRecord.state:type_name -> DBEchoMeetRecordState 0, // 0: DBEchoMeetRecord.state:type_name -> DBEchoMeetRecordState
0, // 1: DBEchoMeetRecord.service_type:type_name -> TranscribeService 4, // 1: Utterance.words:type_name -> Word
5, // 2: Utterance.words:type_name -> Word 5, // 2: Utterance.additions:type_name -> UtteranceAdditions
6, // 3: Utterance.additions:type_name -> UtteranceAdditions 6, // 3: RecognitionResult.utterances:type_name -> Utterance
7, // 4: RecognitionResult.utterances:type_name -> Utterance 7, // 4: RecognitionResult.additions:type_name -> ResultAdditions
8, // 5: RecognitionResult.additions:type_name -> ResultAdditions 5, // [5:5] is the sub-list for method output_type
6, // [6:6] is the sub-list for method output_type 5, // [5:5] is the sub-list for method input_type
6, // [6:6] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee
6, // [6:6] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name
0, // [0:6] is the sub-list for field type_name
} }
func init() { file_echomeet_echomeet_db_proto_init() } func init() { file_echomeet_echomeet_db_proto_init() }
@ -1136,7 +1088,7 @@ func file_echomeet_echomeet_db_proto_init() {
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_db_proto_rawDesc), len(file_echomeet_echomeet_db_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_db_proto_rawDesc), len(file_echomeet_echomeet_db_proto_rawDesc)),
NumEnums: 2, NumEnums: 1,
NumMessages: 9, NumMessages: 9,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,

422
apps/services/pb/echomeet_msg.pb.go

@ -727,14 +727,14 @@ func (*EchomeetPutRecordsResp) Descriptor() ([]byte, []int) {
type EchomeetAddRecordReq struct { type EchomeetAddRecordReq struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title"` //@go_tags(`json:"title"`) //标题 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"`) //类型 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"`) //音频文件路径 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"`) 听写内容 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"`) //时长 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"`) //文件大小 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总结) 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总结) Imageurls []string `protobuf:"bytes,8,rep,name=imageurls,proto3" json:"imageurls"` //@go_tags(`json:"imageurls"`) //注释图片url列表(作为多模态内容参与AI总结)
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -969,13 +969,13 @@ func (x *EchomeetUpRecordResp) GetRecord() *DBEchoMeetRecord {
type EchomeetStartTaskReq struct { type EchomeetStartTaskReq struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id 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"`) //语言 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"`) //目标语言 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"`) //是否区分说话人 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 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 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 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -1052,11 +1052,11 @@ func (x *EchomeetStartTaskReq) GetTid() string {
return "" return ""
} }
func (x *EchomeetStartTaskReq) GetServiceType() TranscribeService { func (x *EchomeetStartTaskReq) GetSummarySvcId() string {
if x != nil { if x != nil {
return x.ServiceType return x.SummarySvcId
} }
return TranscribeService_TranscribeService_Unknown return ""
} }
type EchomeetStartTaskResp struct { type EchomeetStartTaskResp struct {
@ -1219,11 +1219,11 @@ func (*EchomeetCallbackResp) Descriptor() ([]byte, []int) {
// 重新总结请求 // 重新总结请求
type EchomeetSummaryReq struct { type EchomeetSummaryReq struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id"` //@go_tags(`json:"id"`) //会议记录id 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 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 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"`) 语言 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"`) //转写服务类型(用于翻译/总结路由),留空沿用记录已有 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 unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -1286,11 +1286,11 @@ func (x *EchomeetSummaryReq) GetTolanguage() string {
return "" return ""
} }
func (x *EchomeetSummaryReq) GetServiceType() TranscribeService { func (x *EchomeetSummaryReq) GetSummarySvcId() string {
if x != nil { if x != nil {
return x.ServiceType return x.SummarySvcId
} }
return TranscribeService_TranscribeService_Unknown return ""
} }
type EchomeetSummaryResp struct { type EchomeetSummaryResp struct {
@ -1329,6 +1329,156 @@ func (*EchomeetSummaryResp) Descriptor() ([]byte, []int) {
return file_echomeet_echomeet_msg_proto_rawDescGZIP(), []int{25} 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 { type EchomeetReadRecordReq struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
@ -1339,7 +1489,7 @@ type EchomeetReadRecordReq struct {
func (x *EchomeetReadRecordReq) Reset() { func (x *EchomeetReadRecordReq) Reset() {
*x = EchomeetReadRecordReq{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1351,7 +1501,7 @@ func (x *EchomeetReadRecordReq) String() string {
func (*EchomeetReadRecordReq) ProtoMessage() {} func (*EchomeetReadRecordReq) ProtoMessage() {}
func (x *EchomeetReadRecordReq) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1364,7 +1514,7 @@ func (x *EchomeetReadRecordReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetReadRecordReq.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetReadRecordReq.ProtoReflect.Descriptor instead.
func (*EchomeetReadRecordReq) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetReadRecordReq) GetId() uint64 {
@ -1382,7 +1532,7 @@ type EchomeetReadRecordResp struct {
func (x *EchomeetReadRecordResp) Reset() { func (x *EchomeetReadRecordResp) Reset() {
*x = EchomeetReadRecordResp{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1394,7 +1544,7 @@ func (x *EchomeetReadRecordResp) String() string {
func (*EchomeetReadRecordResp) ProtoMessage() {} func (*EchomeetReadRecordResp) ProtoMessage() {}
func (x *EchomeetReadRecordResp) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1407,7 +1557,7 @@ func (x *EchomeetReadRecordResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetReadRecordResp.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetReadRecordResp.ProtoReflect.Descriptor instead.
func (*EchomeetReadRecordResp) Descriptor() ([]byte, []int) { 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() { func (x *EchomeetModifyRecordsReq) Reset() {
*x = EchomeetModifyRecordsReq{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1437,7 +1587,7 @@ func (x *EchomeetModifyRecordsReq) String() string {
func (*EchomeetModifyRecordsReq) ProtoMessage() {} func (*EchomeetModifyRecordsReq) ProtoMessage() {}
func (x *EchomeetModifyRecordsReq) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1450,7 +1600,7 @@ func (x *EchomeetModifyRecordsReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetModifyRecordsReq.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetModifyRecordsReq.ProtoReflect.Descriptor instead.
func (*EchomeetModifyRecordsReq) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetModifyRecordsReq) GetId() uint64 {
@ -1504,7 +1654,7 @@ type EchomeetModifyRecordsResp struct {
func (x *EchomeetModifyRecordsResp) Reset() { func (x *EchomeetModifyRecordsResp) Reset() {
*x = EchomeetModifyRecordsResp{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1516,7 +1666,7 @@ func (x *EchomeetModifyRecordsResp) String() string {
func (*EchomeetModifyRecordsResp) ProtoMessage() {} func (*EchomeetModifyRecordsResp) ProtoMessage() {}
func (x *EchomeetModifyRecordsResp) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1529,7 +1679,7 @@ func (x *EchomeetModifyRecordsResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetModifyRecordsResp.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetModifyRecordsResp.ProtoReflect.Descriptor instead.
func (*EchomeetModifyRecordsResp) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetModifyRecordsResp) GetRecord() *DBEchoMeetRecord {
@ -1549,7 +1699,7 @@ type EchomeetDelRecordsReq struct {
func (x *EchomeetDelRecordsReq) Reset() { func (x *EchomeetDelRecordsReq) Reset() {
*x = EchomeetDelRecordsReq{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1561,7 +1711,7 @@ func (x *EchomeetDelRecordsReq) String() string {
func (*EchomeetDelRecordsReq) ProtoMessage() {} func (*EchomeetDelRecordsReq) ProtoMessage() {}
func (x *EchomeetDelRecordsReq) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1574,7 +1724,7 @@ func (x *EchomeetDelRecordsReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetDelRecordsReq.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetDelRecordsReq.ProtoReflect.Descriptor instead.
func (*EchomeetDelRecordsReq) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetDelRecordsReq) GetIds() []uint64 {
@ -1593,7 +1743,7 @@ type EchomeetDelRecordsResp struct {
func (x *EchomeetDelRecordsResp) Reset() { func (x *EchomeetDelRecordsResp) Reset() {
*x = EchomeetDelRecordsResp{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1605,7 +1755,7 @@ func (x *EchomeetDelRecordsResp) String() string {
func (*EchomeetDelRecordsResp) ProtoMessage() {} func (*EchomeetDelRecordsResp) ProtoMessage() {}
func (x *EchomeetDelRecordsResp) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1618,7 +1768,7 @@ func (x *EchomeetDelRecordsResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetDelRecordsResp.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetDelRecordsResp.ProtoReflect.Descriptor instead.
func (*EchomeetDelRecordsResp) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetDelRecordsResp) GetIds() []uint64 {
@ -1642,7 +1792,7 @@ type EchomeetAliCallbackReq struct {
func (x *EchomeetAliCallbackReq) Reset() { func (x *EchomeetAliCallbackReq) Reset() {
*x = EchomeetAliCallbackReq{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1654,7 +1804,7 @@ func (x *EchomeetAliCallbackReq) String() string {
func (*EchomeetAliCallbackReq) ProtoMessage() {} func (*EchomeetAliCallbackReq) ProtoMessage() {}
func (x *EchomeetAliCallbackReq) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1667,7 +1817,7 @@ func (x *EchomeetAliCallbackReq) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetAliCallbackReq.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetAliCallbackReq.ProtoReflect.Descriptor instead.
func (*EchomeetAliCallbackReq) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetAliCallbackReq) GetStatusCode() int32 {
@ -1714,7 +1864,7 @@ type EchomeetAliCallbackResult struct {
func (x *EchomeetAliCallbackResult) Reset() { func (x *EchomeetAliCallbackResult) Reset() {
*x = EchomeetAliCallbackResult{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1726,7 +1876,7 @@ func (x *EchomeetAliCallbackResult) String() string {
func (*EchomeetAliCallbackResult) ProtoMessage() {} func (*EchomeetAliCallbackResult) ProtoMessage() {}
func (x *EchomeetAliCallbackResult) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1739,7 +1889,7 @@ func (x *EchomeetAliCallbackResult) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetAliCallbackResult.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetAliCallbackResult.ProtoReflect.Descriptor instead.
func (*EchomeetAliCallbackResult) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetAliCallbackResult) GetSentences() []*EchomeetAliSentence {
@ -1762,7 +1912,7 @@ type EchomeetAliSentence struct {
func (x *EchomeetAliSentence) Reset() { func (x *EchomeetAliSentence) Reset() {
*x = EchomeetAliSentence{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1774,7 +1924,7 @@ func (x *EchomeetAliSentence) String() string {
func (*EchomeetAliSentence) ProtoMessage() {} func (*EchomeetAliSentence) ProtoMessage() {}
func (x *EchomeetAliSentence) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1787,7 +1937,7 @@ func (x *EchomeetAliSentence) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetAliSentence.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetAliSentence.ProtoReflect.Descriptor instead.
func (*EchomeetAliSentence) Descriptor() ([]byte, []int) { 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 { func (x *EchomeetAliSentence) GetText() string {
@ -1833,7 +1983,7 @@ type EchomeetAliCallbackResp struct {
func (x *EchomeetAliCallbackResp) Reset() { func (x *EchomeetAliCallbackResp) Reset() {
*x = EchomeetAliCallbackResp{} *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 := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1845,7 +1995,7 @@ func (x *EchomeetAliCallbackResp) String() string {
func (*EchomeetAliCallbackResp) ProtoMessage() {} func (*EchomeetAliCallbackResp) ProtoMessage() {}
func (x *EchomeetAliCallbackResp) ProtoReflect() protoreflect.Message { 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 { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1858,7 +2008,7 @@ func (x *EchomeetAliCallbackResp) ProtoReflect() protoreflect.Message {
// Deprecated: Use EchomeetAliCallbackResp.ProtoReflect.Descriptor instead. // Deprecated: Use EchomeetAliCallbackResp.ProtoReflect.Descriptor instead.
func (*EchomeetAliCallbackResp) Descriptor() ([]byte, []int) { 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 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" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x1a\n" +
"\baudiourl\x18\x02 \x01(\tR\baudiourl\"A\n" + "\baudiourl\x18\x02 \x01(\tR\baudiourl\"A\n" +
"\x14EchomeetUpRecordResp\x12)\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" + "\x14EchomeetStartTaskReq\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x04R\x02id\x12\"\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\"\n" +
"\fformlanguage\x18\x02 \x01(\tR\fformlanguage\x12\x1e\n" + "\fformlanguage\x18\x02 \x01(\tR\fformlanguage\x12\x1e\n" +
@ -1923,8 +2073,8 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" +
"\n" + "\n" +
"templateid\x18\x05 \x01(\x04R\n" + "templateid\x18\x05 \x01(\x04R\n" +
"templateid\x12\x10\n" + "templateid\x12\x10\n" +
"\x03tid\x18\x06 \x01(\tR\x03tid\x125\n" + "\x03tid\x18\x06 \x01(\tR\x03tid\x12$\n" +
"\fservice_type\x18\a \x01(\x0e2\x12.TranscribeServiceR\vserviceType\"\x17\n" + "\x0esummary_svc_id\x18\b \x01(\tR\fsummarySvcId\"\x17\n" +
"\x15EchomeetStartTaskResp\"\xcf\x01\n" + "\x15EchomeetStartTaskResp\"\xcf\x01\n" +
"\x13EchomeetCallbackReq\x12\x12\n" + "\x13EchomeetCallbackReq\x12\x12\n" +
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\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" + "audio_info\x18\x05 \x01(\v2\n" +
".AudioInfoR\taudioInfo\x12#\n" + ".AudioInfoR\taudioInfo\x12#\n" +
"\rcallback_data\x18\x06 \x01(\tR\fcallbackData\"\x16\n" + "\rcallback_data\x18\x06 \x01(\tR\fcallbackData\"\x16\n" +
"\x14EchomeetCallbackResp\"\xad\x01\n" + "\x14EchomeetCallbackResp\"\x9c\x01\n" +
"\x12EchomeetSummaryReq\x12\x0e\n" + "\x12EchomeetSummaryReq\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x04R\x02id\x12\x1e\n" + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x1e\n" +
"\n" + "\n" +
@ -1944,9 +2094,18 @@ const file_echomeet_echomeet_msg_proto_rawDesc = "" +
"\x03tid\x18\x06 \x01(\tR\x03tid\x12\x1e\n" + "\x03tid\x18\x06 \x01(\tR\x03tid\x12\x1e\n" +
"\n" + "\n" +
"tolanguage\x18\a \x01(\tR\n" + "tolanguage\x18\a \x01(\tR\n" +
"tolanguage\x125\n" + "tolanguage\x12$\n" +
"\fservice_type\x18\b \x01(\x0e2\x12.TranscribeServiceR\vserviceType\"\x15\n" + "\x0esummary_svc_id\x18\t \x01(\tR\fsummarySvcId\"\x15\n" +
"\x13EchomeetSummaryResp\"'\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" + "\x15EchomeetReadRecordReq\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x04R\x02id\"\x18\n" + "\x02id\x18\x01 \x01(\x04R\x02id\"\x18\n" +
"\x16EchomeetReadRecordResp\"\xb0\x01\n" + "\x16EchomeetReadRecordResp\"\xb0\x01\n" +
@ -1995,76 +2154,77 @@ func file_echomeet_echomeet_msg_proto_rawDescGZIP() []byte {
return file_echomeet_echomeet_msg_proto_rawDescData 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{ var file_echomeet_echomeet_msg_proto_goTypes = []any{
(*EchomeetGetTemplatesReq)(nil), // 0: EchomeetGetTemplatesReq (*EchomeetGetTemplatesReq)(nil), // 0: EchomeetGetTemplatesReq
(*EchomeetGetTemplatesResp)(nil), // 1: EchomeetGetTemplatesResp (*EchomeetGetTemplatesResp)(nil), // 1: EchomeetGetTemplatesResp
(*EchomeetAddTemplateReq)(nil), // 2: EchomeetAddTemplateReq (*EchomeetAddTemplateReq)(nil), // 2: EchomeetAddTemplateReq
(*EchomeetAddTemplateResp)(nil), // 3: EchomeetAddTemplateResp (*EchomeetAddTemplateResp)(nil), // 3: EchomeetAddTemplateResp
(*EchomeetUpdateTemplateReq)(nil), // 4: EchomeetUpdateTemplateReq (*EchomeetUpdateTemplateReq)(nil), // 4: EchomeetUpdateTemplateReq
(*EchomeetUpdateTemplateResp)(nil), // 5: EchomeetUpdateTemplateResp (*EchomeetUpdateTemplateResp)(nil), // 5: EchomeetUpdateTemplateResp
(*EchomeetDeleteTemplateReq)(nil), // 6: EchomeetDeleteTemplateReq (*EchomeetDeleteTemplateReq)(nil), // 6: EchomeetDeleteTemplateReq
(*EchomeetDeleteTemplateResp)(nil), // 7: EchomeetDeleteTemplateResp (*EchomeetDeleteTemplateResp)(nil), // 7: EchomeetDeleteTemplateResp
(*EchomeetGetAllRecordsReq)(nil), // 8: EchomeetGetAllRecordsReq (*EchomeetGetAllRecordsReq)(nil), // 8: EchomeetGetAllRecordsReq
(*EchomeetGetAllRecordsResp)(nil), // 9: EchomeetGetAllRecordsResp (*EchomeetGetAllRecordsResp)(nil), // 9: EchomeetGetAllRecordsResp
(*EchomeetGetRecordsReq)(nil), // 10: EchomeetGetRecordsReq (*EchomeetGetRecordsReq)(nil), // 10: EchomeetGetRecordsReq
(*EchomeetGetRecordsResp)(nil), // 11: EchomeetGetRecordsResp (*EchomeetGetRecordsResp)(nil), // 11: EchomeetGetRecordsResp
(*EchomeetGetRecordReq)(nil), // 12: EchomeetGetRecordReq (*EchomeetGetRecordReq)(nil), // 12: EchomeetGetRecordReq
(*EchomeetGetRecordResp)(nil), // 13: EchomeetGetRecordResp (*EchomeetGetRecordResp)(nil), // 13: EchomeetGetRecordResp
(*EchomeetPutRecordsReq)(nil), // 14: EchomeetPutRecordsReq (*EchomeetPutRecordsReq)(nil), // 14: EchomeetPutRecordsReq
(*EchomeetPutRecordsResp)(nil), // 15: EchomeetPutRecordsResp (*EchomeetPutRecordsResp)(nil), // 15: EchomeetPutRecordsResp
(*EchomeetAddRecordReq)(nil), // 16: EchomeetAddRecordReq (*EchomeetAddRecordReq)(nil), // 16: EchomeetAddRecordReq
(*EchomeetAddRecordResp)(nil), // 17: EchomeetAddRecordResp (*EchomeetAddRecordResp)(nil), // 17: EchomeetAddRecordResp
(*EchomeetUpRecordReq)(nil), // 18: EchomeetUpRecordReq (*EchomeetUpRecordReq)(nil), // 18: EchomeetUpRecordReq
(*EchomeetUpRecordResp)(nil), // 19: EchomeetUpRecordResp (*EchomeetUpRecordResp)(nil), // 19: EchomeetUpRecordResp
(*EchomeetStartTaskReq)(nil), // 20: EchomeetStartTaskReq (*EchomeetStartTaskReq)(nil), // 20: EchomeetStartTaskReq
(*EchomeetStartTaskResp)(nil), // 21: EchomeetStartTaskResp (*EchomeetStartTaskResp)(nil), // 21: EchomeetStartTaskResp
(*EchomeetCallbackReq)(nil), // 22: EchomeetCallbackReq (*EchomeetCallbackReq)(nil), // 22: EchomeetCallbackReq
(*EchomeetCallbackResp)(nil), // 23: EchomeetCallbackResp (*EchomeetCallbackResp)(nil), // 23: EchomeetCallbackResp
(*EchomeetSummaryReq)(nil), // 24: EchomeetSummaryReq (*EchomeetSummaryReq)(nil), // 24: EchomeetSummaryReq
(*EchomeetSummaryResp)(nil), // 25: EchomeetSummaryResp (*EchomeetSummaryResp)(nil), // 25: EchomeetSummaryResp
(*EchomeetReadRecordReq)(nil), // 26: EchomeetReadRecordReq (*EchomeetGetSummaryServicesReq)(nil), // 26: EchomeetGetSummaryServicesReq
(*EchomeetReadRecordResp)(nil), // 27: EchomeetReadRecordResp (*EchomeetSummaryServiceItem)(nil), // 27: EchomeetSummaryServiceItem
(*EchomeetModifyRecordsReq)(nil), // 28: EchomeetModifyRecordsReq (*EchomeetGetSummaryServicesResp)(nil), // 28: EchomeetGetSummaryServicesResp
(*EchomeetModifyRecordsResp)(nil), // 29: EchomeetModifyRecordsResp (*EchomeetReadRecordReq)(nil), // 29: EchomeetReadRecordReq
(*EchomeetDelRecordsReq)(nil), // 30: EchomeetDelRecordsReq (*EchomeetReadRecordResp)(nil), // 30: EchomeetReadRecordResp
(*EchomeetDelRecordsResp)(nil), // 31: EchomeetDelRecordsResp (*EchomeetModifyRecordsReq)(nil), // 31: EchomeetModifyRecordsReq
(*EchomeetAliCallbackReq)(nil), // 32: EchomeetAliCallbackReq (*EchomeetModifyRecordsResp)(nil), // 32: EchomeetModifyRecordsResp
(*EchomeetAliCallbackResult)(nil), // 33: EchomeetAliCallbackResult (*EchomeetDelRecordsReq)(nil), // 33: EchomeetDelRecordsReq
(*EchomeetAliSentence)(nil), // 34: EchomeetAliSentence (*EchomeetDelRecordsResp)(nil), // 34: EchomeetDelRecordsResp
(*EchomeetAliCallbackResp)(nil), // 35: EchomeetAliCallbackResp (*EchomeetAliCallbackReq)(nil), // 35: EchomeetAliCallbackReq
(*DBEchoMeetTemplate)(nil), // 36: DBEchoMeetTemplate (*EchomeetAliCallbackResult)(nil), // 36: EchomeetAliCallbackResult
(*DBEchoMeetRecord)(nil), // 37: DBEchoMeetRecord (*EchomeetAliSentence)(nil), // 37: EchomeetAliSentence
(*ContextStruct)(nil), // 38: ContextStruct (*EchomeetAliCallbackResp)(nil), // 38: EchomeetAliCallbackResp
(TranscribeService)(0), // 39: TranscribeService (*DBEchoMeetTemplate)(nil), // 39: DBEchoMeetTemplate
(*RecognitionResult)(nil), // 40: RecognitionResult (*DBEchoMeetRecord)(nil), // 40: DBEchoMeetRecord
(*AudioInfo)(nil), // 41: AudioInfo (*ContextStruct)(nil), // 41: ContextStruct
(*RecognitionResult)(nil), // 42: RecognitionResult
(*AudioInfo)(nil), // 43: AudioInfo
} }
var file_echomeet_echomeet_msg_proto_depIdxs = []int32{ var file_echomeet_echomeet_msg_proto_depIdxs = []int32{
36, // 0: EchomeetGetTemplatesResp.templates:type_name -> DBEchoMeetTemplate 39, // 0: EchomeetGetTemplatesResp.templates:type_name -> DBEchoMeetTemplate
36, // 1: EchomeetAddTemplateReq.template:type_name -> DBEchoMeetTemplate 39, // 1: EchomeetAddTemplateReq.template:type_name -> DBEchoMeetTemplate
36, // 2: EchomeetAddTemplateResp.template:type_name -> DBEchoMeetTemplate 39, // 2: EchomeetAddTemplateResp.template:type_name -> DBEchoMeetTemplate
36, // 3: EchomeetUpdateTemplateReq.template:type_name -> DBEchoMeetTemplate 39, // 3: EchomeetUpdateTemplateReq.template:type_name -> DBEchoMeetTemplate
36, // 4: EchomeetUpdateTemplateResp.template:type_name -> DBEchoMeetTemplate 39, // 4: EchomeetUpdateTemplateResp.template:type_name -> DBEchoMeetTemplate
37, // 5: EchomeetGetAllRecordsResp.records:type_name -> DBEchoMeetRecord 40, // 5: EchomeetGetAllRecordsResp.records:type_name -> DBEchoMeetRecord
37, // 6: EchomeetGetRecordsResp.records:type_name -> DBEchoMeetRecord 40, // 6: EchomeetGetRecordsResp.records:type_name -> DBEchoMeetRecord
37, // 7: EchomeetGetRecordResp.record:type_name -> DBEchoMeetRecord 40, // 7: EchomeetGetRecordResp.record:type_name -> DBEchoMeetRecord
37, // 8: EchomeetPutRecordsReq.records:type_name -> DBEchoMeetRecord 40, // 8: EchomeetPutRecordsReq.records:type_name -> DBEchoMeetRecord
38, // 9: EchomeetAddRecordReq.original:type_name -> ContextStruct 41, // 9: EchomeetAddRecordReq.original:type_name -> ContextStruct
37, // 10: EchomeetAddRecordResp.record:type_name -> DBEchoMeetRecord 40, // 10: EchomeetAddRecordResp.record:type_name -> DBEchoMeetRecord
37, // 11: EchomeetUpRecordResp.record:type_name -> DBEchoMeetRecord 40, // 11: EchomeetUpRecordResp.record:type_name -> DBEchoMeetRecord
39, // 12: EchomeetStartTaskReq.service_type:type_name -> TranscribeService 42, // 12: EchomeetCallbackReq.result:type_name -> RecognitionResult
40, // 13: EchomeetCallbackReq.result:type_name -> RecognitionResult 43, // 13: EchomeetCallbackReq.audio_info:type_name -> AudioInfo
41, // 14: EchomeetCallbackReq.audio_info:type_name -> AudioInfo 27, // 14: EchomeetGetSummaryServicesResp.services:type_name -> EchomeetSummaryServiceItem
39, // 15: EchomeetSummaryReq.service_type:type_name -> TranscribeService 40, // 15: EchomeetModifyRecordsResp.record:type_name -> DBEchoMeetRecord
37, // 16: EchomeetModifyRecordsResp.record:type_name -> DBEchoMeetRecord 36, // 16: EchomeetAliCallbackReq.Result:type_name -> EchomeetAliCallbackResult
33, // 17: EchomeetAliCallbackReq.Result:type_name -> EchomeetAliCallbackResult 37, // 17: EchomeetAliCallbackResult.Sentences:type_name -> EchomeetAliSentence
34, // 18: EchomeetAliCallbackResult.Sentences:type_name -> EchomeetAliSentence 18, // [18:18] is the sub-list for method output_type
19, // [19:19] is the sub-list for method output_type 18, // [18:18] is the sub-list for method input_type
19, // [19:19] is the sub-list for method input_type 18, // [18:18] is the sub-list for extension type_name
19, // [19:19] is the sub-list for extension type_name 18, // [18:18] is the sub-list for extension extendee
19, // [19:19] is the sub-list for extension extendee 0, // [0:18] is the sub-list for field type_name
0, // [0:19] is the sub-list for field type_name
} }
func init() { file_echomeet_echomeet_msg_proto_init() } func init() { file_echomeet_echomeet_msg_proto_init() }
@ -2079,7 +2239,7 @@ func file_echomeet_echomeet_msg_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_msg_proto_rawDesc), len(file_echomeet_echomeet_msg_proto_rawDesc)), RawDescriptor: unsafe.Slice(unsafe.StringData(file_echomeet_echomeet_msg_proto_rawDesc), len(file_echomeet_echomeet_msg_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 36, NumMessages: 39,
NumExtensions: 0, NumExtensions: 0,
NumServices: 0, NumServices: 0,
}, },

49
deploy/voitrans/confs/home.yaml.example

@ -134,53 +134,10 @@ modules:
CBCKey: "" CBCKey: ""
allhelp: allhelp:
max_summary_process: 5 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} 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: pay:
Debug: ${LOG_DEBUG:-false} Debug: ${LOG_DEBUG:-false}
WxAppId: "wx5bbb7be9324f3e2d" WxAppId: "wx5bbb7be9324f3e2d"

7
deploy/voitrans/env/env.example

@ -31,6 +31,13 @@ NATS_URL=nats://nats:4222
# 发出去,表现为 401 "API key is invalid",而后台却显示配置已填好,极难排查。 # 发出去,表现为 401 "API key is invalid",而后台却显示配置已填好,极难排查。
FIELD_ENCRYPT_KEY= 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 注册表中的应用名/区域一致)── # ── 统计上报(须与 console 注册表中的应用名/区域一致)──
ANALYZE_APP_NAME=voitrans-a11-dev ANALYZE_APP_NAME=voitrans-a11-dev
ANALYZE_REGION=1 ANALYZE_REGION=1

Loading…
Cancel
Save