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.
 
 
 
 
 
 

249 lines
8.3 KiB

package console
import (
"strings"
"time"
"yunyan/comm"
"yunyan/lego/sys/postgres"
"yunyan/pb"
"github.com/gin-gonic/gin"
)
// ============================ 通话翻译配置域 (calltranslate) ============================
//
// 数据存 console 主库(postgres.GetSys()),模型见 comm/calltranslate.go。
//
// 规则化模型(取代旧的 call_translate_pair 语言对表):
// 一条规则 = 某应用作用域 + 某区域下的「多源语言 × 多目标语言 → 一个 agent」映射,带优先级。
// 运行时按 comm.MatchCallTranslateRule 选路:优先级大者先,同优先级精确规则压过 * 通配规则。
// 规则不再直接拼 asr/mt/tts —— 服务编排收敛在 agent 里,规则只决定「什么语言走哪个 agent」。
// 规则可引用的 agent 类型:三段式翻译 与 端到端翻译。chat 类不能用于通话翻译选路。
var callTranslateAgentTypes = []string{comm.AgentTypeTranslate, comm.AgentTypeAstTranslate}
// findAgentForRule 在规则的应用作用域内找 agent,未命中回退全局(与 validateAgentSvcRefs 的解析口径一致)。
func findAgentForRule(appName, agentId string) (*AgentConfig, error) {
a := &AgentConfig{}
err := postgres.FindOne(comm.TableAgentConfig, a, "app_name=? AND id=?", appName, agentId)
if err != nil && appName != "" {
err = postgres.FindOne(comm.TableAgentConfig, a, "app_name=? AND id=?", "", agentId)
}
return a, err
}
// validateRule 校验规则自身与其引用的 agent,返回错误说明(空串=通过)。
func validateRule(m *comm.CallTranslateRule) string {
if len(comm.SplitLangs(m.SrcLangs)) == 0 {
return "源语言 必选(或选「任意语言」)"
}
if len(comm.SplitLangs(m.DstLangs)) == 0 {
return "目标语言 必选(或选「任意语言」)"
}
if strings.TrimSpace(m.AgentId) == "" {
return "翻译 Agent 必选"
}
a, err := findAgentForRule(m.AppName, m.AgentId)
if err != nil {
return "引用的 Agent 不存在: " + m.AgentId
}
ok := false
for _, t := range callTranslateAgentTypes {
if a.Type == t {
ok = true
break
}
}
if !ok {
return "Agent「" + a.Name + "」类型为 " + a.Type + ",通话翻译只能引用 translate(三段式)或 ast-translate(端到端)"
}
return ""
}
// getCallTranslateRules 列出某作用域下的规则;region>0 只取该区域 + 不限区域(0)的规则,region<=0 取全量。
// 返回已按生效顺序排好序(优先级降序 → id 升序),前端直接按序展示即可。
func (this *serverComp) getCallTranslateRules(c *gin.Context) {
var req struct {
AppName string `json:"app_name"`
Region int32 `json:"region"`
}
_ = c.ShouldBindJSON(&req)
items := make([]*comm.CallTranslateRule, 0)
var err error
if req.Region > 0 {
err = postgres.Find(comm.TableCallTranslateRule, &items, "app_name=? AND (region=? OR region=?)",
req.AppName, req.Region, comm.RegionAny)
} else {
err = postgres.Find(comm.TableCallTranslateRule, &items, "app_name=?", req.AppName)
}
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
sortRulesByEffect(items)
writeOK(c, gin.H{"items": items})
}
// sortRulesByEffect 按运行时生效顺序排序:优先级降序 → id 升序(插入排序,规则量级很小)。
func sortRulesByEffect(items []*comm.CallTranslateRule) {
for i := 1; i < len(items); i++ {
cur := items[i]
j := i - 1
for j >= 0 && (items[j].Priority < cur.Priority || (items[j].Priority == cur.Priority && items[j].Id > cur.Id)) {
items[j+1] = items[j]
j--
}
items[j+1] = cur
}
}
// saveCallTranslateRule 新增或更新一条规则(id=0 新增,否则按 id 更新)。
func (this *serverComp) saveCallTranslateRule(c *gin.Context) {
var m comm.CallTranslateRule
if err := c.ShouldBindJSON(&m); err != nil {
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
return
}
if msg := validateRule(&m); msg != "" {
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
return
}
now := time.Now().UnixMilli()
m.Updatetime = now
var err error
if m.Id == 0 {
m.Createtime = now
err = postgres.Insert(comm.TableCallTranslateRule, &m)
} else {
old := &comm.CallTranslateRule{}
if e := postgres.FindOne(comm.TableCallTranslateRule, old, "id=?", m.Id); e != nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "规则不存在")
return
}
m.Createtime = old.Createtime
err = postgres.Save(comm.TableCallTranslateRule, &m)
}
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
broadcastConfigChanged(comm.ConfigKindCallTranslate, "update", m.Region)
writeOK(c, &m)
}
// delCallTranslateRules 按 id 批量删除规则。
func (this *serverComp) delCallTranslateRules(c *gin.Context) {
var req struct {
Ids []uint64 `json:"ids"`
}
_ = c.ShouldBindJSON(&req)
if len(req.Ids) == 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "ids 必填")
return
}
if err := postgres.Delete(comm.TableCallTranslateRule, "id IN ?", req.Ids); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
broadcastConfigChanged(comm.ConfigKindCallTranslate, "delete", 0)
writeOK(c, gin.H{"deleted": len(req.Ids)})
}
// syncCallTranslateRules 把指定规则复制到多个目标区域。
// 目标区域已有「同 agent + 同源语言集 + 同目标语言集」的规则则覆盖,否则新建;源区域自身跳过。
func (this *serverComp) syncCallTranslateRules(c *gin.Context) {
var req struct {
RuleIds []uint64 `json:"rule_ids"`
TargetRegions []int32 `json:"target_regions"`
}
if err := c.ShouldBindJSON(&req); err != nil || len(req.RuleIds) == 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "rule_ids 必填")
return
}
if len(req.TargetRegions) == 0 {
writeOK(c, gin.H{"synced": 0})
return
}
srcRules := make([]*comm.CallTranslateRule, 0, len(req.RuleIds))
if err := postgres.Find(comm.TableCallTranslateRule, &srcRules, "id IN ?", req.RuleIds); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
now := time.Now().UnixMilli()
synced := 0
for _, r := range srcRules {
for _, tgt := range req.TargetRegions {
if r.Region == tgt {
continue
}
old := &comm.CallTranslateRule{}
_ = postgres.FindOne(comm.TableCallTranslateRule, old,
"app_name=? AND region=? AND agent_id=? AND src_langs=? AND dst_langs=?",
r.AppName, tgt, r.AgentId, r.SrcLangs, r.DstLangs)
m := &comm.CallTranslateRule{
Id: old.Id,
AppName: r.AppName,
Region: tgt,
Name: r.Name,
SrcLangs: r.SrcLangs,
DstLangs: r.DstLangs,
Bidir: r.Bidir,
AgentId: r.AgentId,
Priority: r.Priority,
Enable: r.Enable,
Remark: r.Remark,
Updatetime: now,
}
if old.Id == 0 {
m.Createtime = now
if err := postgres.Insert(comm.TableCallTranslateRule, m); err == nil {
synced++
}
} else {
m.Createtime = old.Createtime
if err := postgres.Save(comm.TableCallTranslateRule, m); err == nil {
synced++
}
}
}
}
if synced > 0 {
for _, tgt := range req.TargetRegions {
broadcastConfigChanged(comm.ConfigKindCallTranslate, "update", tgt)
}
}
writeOK(c, gin.H{"synced": synced})
}
// testCallTranslateRule 选路试算:给定区域与源/目标语言,返回实际会命中的规则及其 agent。
// 规则一多就很难靠肉眼确认「zh-CN→ja-JP 到底走哪条」,故给后台一个所见即所得的验证入口,
// 走的是与运行时完全相同的 comm.MatchCallTranslateRule。
func (this *serverComp) testCallTranslateRule(c *gin.Context) {
var req struct {
AppName string `json:"app_name"`
Region int32 `json:"region"`
SrcLang string `json:"src_lang"`
DstLang string `json:"dst_lang"`
}
_ = c.ShouldBindJSON(&req)
if strings.TrimSpace(req.SrcLang) == "" || strings.TrimSpace(req.DstLang) == "" {
writeErr(c, pb.ErrorCode_ReqParameterError, "src_lang / dst_lang 必填")
return
}
items := make([]*comm.CallTranslateRule, 0)
if err := postgres.Find(comm.TableCallTranslateRule, &items, "app_name=?", req.AppName); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
hit := comm.MatchCallTranslateRule(items, req.Region, req.SrcLang, req.DstLang)
if hit == nil {
writeOK(c, gin.H{"matched": false})
return
}
resp := gin.H{"matched": true, "rule": hit}
if a, err := findAgentForRule(hit.AppName, hit.AgentId); err == nil {
resp["agent"] = a
}
writeOK(c, resp)
}