You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
9.8 KiB
218 lines
9.8 KiB
package comm
|
|
|
|
import (
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/lego/sys/mysql"
|
|
)
|
|
|
|
// 会议记录(echomeet)服务配置托管:把「识别 / 翻译 / 总结」三类服务配置全部搬进管理后台。
|
|
//
|
|
// 与旧的「一会一服务商,三段同源」不同:识别/翻译/总结各自维护一份**按优先级排序的服务清单**,
|
|
// - 识别:按优先级 + 各服务「支持语言」自动选择(跳过不支持源语言的服务);
|
|
// - 翻译:按优先级自动选择;
|
|
// - 总结:每条 = 一个具体文本大模型条目,客户端按 id 选择。
|
|
//
|
|
// 存储沿用 [[app_module_config]] 的路子:存**应用自己的业务库**(mysql/pg,随部署)、**全局一份**(无 app_name)。
|
|
// console 后台经 getServiceDB 写、home 的 echomeet 启动时 LoadOrSeed 读(改配置需重启生效)。
|
|
// 密钥字段以 AES-256-GCM 密文落库(comm.Encrypt,${FIELD_ENCRYPT_KEY},console 与业务侧同一把 key),
|
|
// 返回前端脱敏为 EncMask。字段清单由 EchoSvcCatalog 数据驱动(哪些字段、类型、是否密钥)。
|
|
|
|
// 服务类别。
|
|
const (
|
|
EchoSvcCatASR = 1 // 识别(转写)
|
|
EchoSvcCatMT = 2 // 翻译
|
|
EchoSvcCatLLM = 3 // 总结(文本大模型)
|
|
)
|
|
|
|
// EchoSvcConfig 一行 = 某类别下一条服务/模型条目。存应用业务库,全局一份。
|
|
type EchoSvcConfig struct {
|
|
Id string `gorm:"primaryKey;column:id;size:64" json:"id"` // 稳定键:asr_ali / mt_bytedance / llm_qwen_plus
|
|
Category int32 `gorm:"column:category;index:idx_echosvc_cat" json:"category"` // 1 识别 / 2 翻译 / 3 总结
|
|
Provider string `gorm:"column:provider;size:32" json:"provider"` // bytedance/ali/google/microsoft(识别/翻译);doubao/openai/gemini(总结)
|
|
Name string `gorm:"column:name;size:128" json:"name"` // 展示名(总结类即模型友好名,客户端可见)
|
|
Priority int32 `gorm:"column:priority" json:"priority"` // 同类内升序,小=优先
|
|
Enabled bool `gorm:"column:enabled" json:"enabled"` // 是否启用
|
|
SupportLangs string `gorm:"column:support_langs;type:text" json:"support_langs"` // 识别用:逗号分隔 BCP-47;空=按代码默认支持集
|
|
Values map[string]string `gorm:"column:values;type:text;serializer:json" json:"values"` // 参数/凭据值;密钥字段存密文
|
|
Createtime int64 `gorm:"column:createtime" json:"createtime"`
|
|
Updatetime int64 `gorm:"column:updatetime" json:"updatetime"`
|
|
}
|
|
|
|
func (EchoSvcConfig) TableName() string { return TableEchomeetSvcConfig }
|
|
|
|
// EchoSvcField 一个配置字段的元数据(驱动前端表单 + 后端密钥判定)。
|
|
type EchoSvcField struct {
|
|
Key string `json:"key"`
|
|
Label string `json:"label"`
|
|
Type string `json:"type"` // text|password|textarea|url
|
|
Secret bool `json:"secret"`
|
|
Required bool `json:"required"`
|
|
Placeholder string `json:"placeholder,omitempty"`
|
|
Desc string `json:"desc,omitempty"`
|
|
}
|
|
|
|
// EchoSvcProviderDef 某类别下某服务商/模型实现的字段模板。
|
|
type EchoSvcProviderDef struct {
|
|
Category int32 `json:"category"` // 所属类别
|
|
Provider string `json:"provider"` // 存库 Provider 值
|
|
Name string `json:"name"` // 展示名
|
|
HasLangs bool `json:"has_langs"` // 是否展示「支持语言」(识别类为 true)
|
|
Multi bool `json:"multi"` // 是否允许同 provider 多条(总结类为 true:一个服务商多个模型)
|
|
Fields []EchoSvcField `json:"fields"`
|
|
}
|
|
|
|
// 常用字段构造助手。
|
|
func esfText(key, label string, required bool) EchoSvcField {
|
|
return EchoSvcField{Key: key, Label: label, Type: "text", Required: required}
|
|
}
|
|
func esfSecret(key, label string, required bool) EchoSvcField {
|
|
return EchoSvcField{Key: key, Label: label, Type: "password", Secret: true, Required: required}
|
|
}
|
|
|
|
// EchoSvcCatalog 会议记录服务字段目录:识别/翻译/总结 × 各服务商,字段依据 home.yaml 的 echomeet 配置结构。
|
|
var EchoSvcCatalog = []EchoSvcProviderDef{
|
|
// ===== 识别(转写)=====
|
|
{Category: EchoSvcCatASR, Provider: "bytedance", Name: "字节跳动", HasLangs: true, Fields: []EchoSvcField{
|
|
esfText("AppID", "AppID", true),
|
|
esfSecret("Token", "Token", false),
|
|
esfText("Callback", "回调地址", false),
|
|
}},
|
|
{Category: EchoSvcCatASR, Provider: "ali", Name: "阿里云 DashScope", HasLangs: true, Fields: []EchoSvcField{
|
|
esfSecret("ApiKey", "ApiKey", true),
|
|
esfText("Callback", "回调地址", false),
|
|
}},
|
|
{Category: EchoSvcCatASR, Provider: "google", Name: "Google Cloud", HasLangs: true, Fields: []EchoSvcField{
|
|
{Key: "JsonPath", Label: "服务账号 JSON", Type: "textarea", Secret: true, Required: true,
|
|
Desc: "服务账号 JSON 内容或服务器上的文件路径"},
|
|
}},
|
|
{Category: EchoSvcCatASR, Provider: "microsoft", Name: "Azure 语音", HasLangs: true, Fields: []EchoSvcField{
|
|
esfSecret("Key", "Key", true),
|
|
esfText("Region", "Region", true),
|
|
}},
|
|
|
|
// ===== 翻译 =====
|
|
{Category: EchoSvcCatMT, Provider: "bytedance", Name: "火山翻译", Fields: []EchoSvcField{
|
|
esfText("AccessKey", "AccessKey", true),
|
|
esfSecret("SecretKey", "SecretKey", true),
|
|
esfText("Region", "Region", false),
|
|
}},
|
|
{Category: EchoSvcCatMT, Provider: "ali", Name: "阿里机器翻译", Fields: []EchoSvcField{
|
|
esfText("AccessKeyId", "AccessKeyId", true),
|
|
esfSecret("AccessKeySecret", "AccessKeySecret", true),
|
|
esfText("Region", "Region", false),
|
|
}},
|
|
{Category: EchoSvcCatMT, Provider: "google", Name: "Google 翻译", Fields: []EchoSvcField{
|
|
{Key: "JsonPath", Label: "服务账号 JSON", Type: "textarea", Secret: true, Required: true,
|
|
Desc: "服务账号 JSON 内容或服务器上的文件路径"},
|
|
}},
|
|
{Category: EchoSvcCatMT, Provider: "microsoft", Name: "Azure 翻译", Fields: []EchoSvcField{
|
|
esfSecret("Key", "Key", true),
|
|
esfText("Region", "Region", false),
|
|
esfText("Endpoint", "Endpoint", false),
|
|
}},
|
|
|
|
// ===== 总结(文本大模型;一个服务商可配多条不同模型)=====
|
|
{Category: EchoSvcCatLLM, Provider: "doubao", Name: "豆包(字节)", Multi: true, Fields: []EchoSvcField{
|
|
esfSecret("Apikey", "ApiKey", true),
|
|
esfText("Model", "模型 ID", true),
|
|
}},
|
|
{Category: EchoSvcCatLLM, Provider: "openai", Name: "OpenAI 兼容端点(通义/Azure OpenAI 等)", Multi: true, Fields: []EchoSvcField{
|
|
esfText("BaseURL", "BaseURL", true),
|
|
esfSecret("Token", "Token", true),
|
|
esfText("Model", "模型 ID", true),
|
|
}},
|
|
{Category: EchoSvcCatLLM, Provider: "gemini", Name: "Gemini(Google)", Multi: true, Fields: []EchoSvcField{
|
|
esfSecret("Apikey", "ApiKey", true),
|
|
esfText("Model", "模型 ID", true),
|
|
esfText("Endpoint", "自定义 Endpoint(可选)", false),
|
|
}},
|
|
}
|
|
|
|
// FindEchoSvcDef 按 (类别, provider) 取字段模板;找不到返回 nil。
|
|
func FindEchoSvcDef(category int32, provider string) *EchoSvcProviderDef {
|
|
for i := range EchoSvcCatalog {
|
|
if EchoSvcCatalog[i].Category == category && EchoSvcCatalog[i].Provider == provider {
|
|
return &EchoSvcCatalog[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EchoSvcFieldIsSecret 判断某 (类别,provider,字段) 是否密钥字段。目录优先,回退通用命名规则。
|
|
func EchoSvcFieldIsSecret(category int32, provider, key string) bool {
|
|
if def := FindEchoSvcDef(category, provider); def != nil {
|
|
for _, f := range def.Fields {
|
|
if f.Key == key {
|
|
return f.Secret
|
|
}
|
|
}
|
|
}
|
|
return IsSecretField(key)
|
|
}
|
|
|
|
// EncryptEchoValues 返回 values 的副本,把密钥字段加密(空值/已是 EncMask 的不动,交由调用方决定)。
|
|
func EncryptEchoValues(encKey string, category int32, provider string, values map[string]string) map[string]string {
|
|
out := make(map[string]string, len(values))
|
|
for k, v := range values {
|
|
if EchoSvcFieldIsSecret(category, provider, k) && v != "" && v != EncMask {
|
|
if ct, err := Encrypt(encKey, v); err == nil {
|
|
out[k] = ct
|
|
continue
|
|
}
|
|
}
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// DecryptEchoValues 返回 values 的副本,把密钥字段解密(供运行时消费)。
|
|
func DecryptEchoValues(encKey string, category int32, provider string, values map[string]string) map[string]string {
|
|
out := make(map[string]string, len(values))
|
|
for k, v := range values {
|
|
if EchoSvcFieldIsSecret(category, provider, k) && v != "" {
|
|
plain, err := Decrypt(encKey, v)
|
|
if err == nil {
|
|
out[k] = plain
|
|
continue
|
|
}
|
|
// 仍沿用密文(保持既有行为),但必须留痕:否则密文会被当明文用作第三方 API key,
|
|
// 表现为鉴权失败,而后台显示"已配置",无从排查。
|
|
log.Errorf("[EchoSvcConfig] 密钥字段解密失败 category=%d provider=%s key=%s err=%v —— 检查本服务 ${FIELD_ENCRYPT_KEY} 是否与 console 完全一致", category, provider, k, err)
|
|
}
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// MaskEchoValues 返回 values 的副本,把密钥字段脱敏为 EncMask(返回前端用)。
|
|
func MaskEchoValues(category int32, provider string, values map[string]string) map[string]string {
|
|
out := make(map[string]string, len(values))
|
|
for k, v := range values {
|
|
if EchoSvcFieldIsSecret(category, provider, k) && v != "" {
|
|
out[k] = EncMask
|
|
continue
|
|
}
|
|
out[k] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// EnsureEchoSvcTable 建表(幂等)。db 为应用业务库。
|
|
func EnsureEchoSvcTable(db mysql.ISys) error {
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
return db.CreateTable(TableEchomeetSvcConfig, &EchoSvcConfig{})
|
|
}
|
|
|
|
// LoadEchoSvcConfigs 读全部会议记录服务配置行(未解密)。
|
|
func LoadEchoSvcConfigs(db mysql.ISys) ([]*EchoSvcConfig, error) {
|
|
if db == nil {
|
|
return nil, nil
|
|
}
|
|
rows := make([]*EchoSvcConfig, 0)
|
|
if err := db.Find(TableEchomeetSvcConfig, &rows, ""); err != nil {
|
|
return nil, err
|
|
}
|
|
return rows, nil
|
|
}
|
|
|