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.
318 lines
11 KiB
318 lines
11 KiB
package console
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/postgres"
|
|
"yunyan/pb"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// ============================ 会议记录服务编排 (echomeet orch) ============================
|
|
//
|
|
// admin「会议记录服务」页的后端。编排**不存凭据**,只引用第三方服务配置(svc_config)里配好的
|
|
// 服务 id,按作用域 (app_name, region) 分层:app_name='' 且 region=0 = 全局默认兜底。
|
|
// 数据存 console 共享库(postgres),与 svc_config 同库。
|
|
//
|
|
// 前端流程与「通话翻译配置」一致:先 getsvcconfigs 拿服务池、再 getechoorch 拿编排,前端组装。
|
|
// web api(handleWeb 分发),前端统一用 webApi。
|
|
|
|
// echoOrchScope 解析请求里的作用域参数。region<0 归一为 0(全区域)。
|
|
type echoOrchScope struct {
|
|
AppName string `json:"app_name"`
|
|
Region int32 `json:"region"`
|
|
}
|
|
|
|
func normScope(s *echoOrchScope) {
|
|
s.AppName = strings.TrimSpace(s.AppName)
|
|
if s.Region < 0 {
|
|
s.Region = 0
|
|
}
|
|
}
|
|
|
|
// getEchoOrch 列出某作用域下的编排 + 作用域开关。app_name='' 且 region=0 为全局默认。
|
|
func (this *serverComp) getEchoOrch(c *gin.Context) {
|
|
var req echoOrchScope
|
|
_ = c.ShouldBindJSON(&req)
|
|
normScope(&req)
|
|
|
|
items := make([]*EchoOrchestration, 0)
|
|
if err := postgres.Find(comm.TableEchomeetOrch, &items, "app_name=? AND region=?", req.AppName, req.Region); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
if items[i].Kind != items[j].Kind {
|
|
return items[i].Kind < items[j].Kind
|
|
}
|
|
return items[i].Priority < items[j].Priority
|
|
})
|
|
|
|
setting := &EchoOrchSetting{}
|
|
if err := postgres.FindOne(comm.TableEchomeetOrchSetting, setting, "app_name=? AND region=?", req.AppName, req.Region); err != nil {
|
|
setting = &EchoOrchSetting{AppName: req.AppName, Region: req.Region} // 未设置 → 零值
|
|
}
|
|
|
|
writeOK(c, gin.H{
|
|
"items": items,
|
|
"setting": setting,
|
|
"available": this.echoOrchAvailableSvcs(req.AppName),
|
|
// 各编排类别对应的服务池类别,前端据此从「可选服务」里筛出每段能选的服务。
|
|
"kind_cats": gin.H{
|
|
"1": echoKindSvcCats[echoKindASR],
|
|
"2": echoKindSvcCats[echoKindMT],
|
|
"3": echoKindSvcCats[echoKindLLM],
|
|
},
|
|
})
|
|
}
|
|
|
|
// echoOrchSvcView 供前端选服务/显示名/识别语言的精简服务视图(不含凭据)。
|
|
type echoOrchSvcView struct {
|
|
Id string `json:"id"`
|
|
Name string `json:"name"`
|
|
Provider string `json:"provider"`
|
|
Categories string `json:"categories"` // 逗号分隔类别,前端按 kind_cats 筛
|
|
Languages string `json:"languages"` // 识别服务的支持语言(逗号分隔 BCP-47),用于选路预览
|
|
}
|
|
|
|
// echoOrchAvailableSvcs 返回该作用域可见的启用服务(本作用域 + 全局兜底,同 id 应用覆盖全局)。
|
|
func (this *serverComp) echoOrchAvailableSvcs(appName string) []echoOrchSvcView {
|
|
rows := make([]*ThirdSvcConfig, 0)
|
|
if err := postgres.Find(comm.TableSvcConfig, &rows, "(app_name=? OR app_name='') AND enable=true", appName); err != nil {
|
|
return nil
|
|
}
|
|
// 去重:同业务 id,应用作用域优先于全局兜底。
|
|
pick := make(map[string]*ThirdSvcConfig, len(rows))
|
|
for _, r := range rows {
|
|
if cur, ok := pick[r.Id]; !ok || (r.AppName != "" && cur.AppName == "") {
|
|
pick[r.Id] = r
|
|
}
|
|
}
|
|
out := make([]echoOrchSvcView, 0, len(pick))
|
|
for _, r := range pick {
|
|
langs := ""
|
|
for _, f := range r.Fields {
|
|
if f.Key == "languages" {
|
|
langs = f.DefValue
|
|
break
|
|
}
|
|
}
|
|
out = append(out, echoOrchSvcView{
|
|
Id: r.Id, Name: r.Name, Provider: r.Provider, Categories: r.Categories, Languages: langs,
|
|
})
|
|
}
|
|
sort.SliceStable(out, func(i, j int) bool { return out[i].Id < out[j].Id })
|
|
return out
|
|
}
|
|
|
|
// echoOrchSvcAllowed 校验 svc_id 在该作用域可见(本作用域或全局兜底)且类别与 kind 匹配。
|
|
// 返回 (ok, 提示)。服务池按 app_name 分作用域,应用作用域看不到别的应用、但能继承全局。
|
|
func (this *serverComp) echoOrchSvcAllowed(appName, svcId string, kind int32) (bool, string) {
|
|
wantCats := echoKindSvcCats[kind]
|
|
if len(wantCats) == 0 {
|
|
return false, "未知的编排类别"
|
|
}
|
|
// 该作用域可见的服务 = app_name 自己的 + 全局默认的。
|
|
rows := make([]*ThirdSvcConfig, 0)
|
|
if err := postgres.Find(comm.TableSvcConfig, &rows, "(app_name=? OR app_name='') AND id=?", appName, svcId); err != nil {
|
|
return false, err.Error()
|
|
}
|
|
for _, r := range rows {
|
|
if !r.Enable {
|
|
continue
|
|
}
|
|
for _, cat := range strings.Split(r.Categories, ",") {
|
|
for _, want := range wantCats {
|
|
if strings.TrimSpace(cat) == strconv.Itoa(int(want)) {
|
|
return true, ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false, "所选服务不存在、已停用,或类别与该段不匹配"
|
|
}
|
|
|
|
// saveEchoOrch 新增/更新一条编排(按 row_id;0 为新增)。
|
|
// 总结类默认模型同作用域唯一;非总结类强制清零 is_default/client_visible。
|
|
func (this *serverComp) saveEchoOrch(c *gin.Context) {
|
|
var req struct {
|
|
echoOrchScope
|
|
Orch EchoOrchestration `json:"orch"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
normScope(&req.echoOrchScope)
|
|
o := req.Orch
|
|
o.AppName = req.AppName
|
|
o.Region = req.Region
|
|
o.SvcId = strings.TrimSpace(o.SvcId)
|
|
if o.SvcId == "" {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "svc_id 必填")
|
|
return
|
|
}
|
|
if _, ok := echoKindSvcCats[o.Kind]; !ok {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "未知的编排类别 kind")
|
|
return
|
|
}
|
|
if ok, msg := this.echoOrchSvcAllowed(req.AppName, o.SvcId, o.Kind); !ok {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, msg)
|
|
return
|
|
}
|
|
// 只有总结类有默认/客户端可见;其余强制清零。停用的不能当默认。
|
|
if o.Kind != echoKindLLM {
|
|
o.IsDefault = false
|
|
o.ClientVisible = false
|
|
}
|
|
if !o.Enabled {
|
|
o.IsDefault = false
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
o.Updatetime = now
|
|
if o.RowId != 0 {
|
|
old := &EchoOrchestration{}
|
|
if err := postgres.FindOne(comm.TableEchomeetOrch, old, "row_id=?", o.RowId); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, "编排不存在: "+err.Error())
|
|
return
|
|
}
|
|
o.AppName, o.Region, o.Createtime = old.AppName, old.Region, old.Createtime
|
|
if err := postgres.Save(comm.TableEchomeetOrch, &o); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
// 同作用域同类同服务不可重复。
|
|
dup := &EchoOrchestration{}
|
|
if postgres.FindOne(comm.TableEchomeetOrch, dup, "app_name=? AND region=? AND kind=? AND svc_id=?",
|
|
o.AppName, o.Region, o.Kind, o.SvcId) == nil && dup.RowId != 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "该服务已在此段添加")
|
|
return
|
|
}
|
|
o.Createtime = now
|
|
if err := postgres.Insert(comm.TableEchomeetOrch, &o); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
// 默认模型同作用域唯一:本行置默认后,抹掉同作用域同类其余行。先写后清,避免中途失败留空默认。
|
|
if o.IsDefault {
|
|
if res := postgres.Exec(
|
|
"UPDATE "+comm.TableEchomeetOrch+" SET is_default=false WHERE app_name=? AND region=? AND kind=? AND row_id<>?",
|
|
o.AppName, o.Region, o.Kind, o.RowId); res.Error != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, "清理旧默认模型失败: "+res.Error.Error())
|
|
return
|
|
}
|
|
}
|
|
writeOK(c, gin.H{"row_id": o.RowId})
|
|
}
|
|
|
|
// delEchoOrch 删除一条编排。删掉的若是默认模型,顺延同作用域同类一个可用的。
|
|
func (this *serverComp) delEchoOrch(c *gin.Context) {
|
|
var req struct {
|
|
RowId uint64 `json:"row_id"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.RowId == 0 {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, "row_id 必填")
|
|
return
|
|
}
|
|
old := &EchoOrchestration{}
|
|
had := postgres.FindOne(comm.TableEchomeetOrch, old, "row_id=?", req.RowId) == nil && old.RowId != 0
|
|
if err := postgres.Delete(comm.TableEchomeetOrch, "row_id=?", req.RowId); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
if had && old.IsDefault && old.Kind == echoKindLLM {
|
|
this.promoteEchoOrchDefault(old.AppName, old.Region)
|
|
}
|
|
writeOK(c, gin.H{"deleted": req.RowId})
|
|
}
|
|
|
|
// promoteEchoOrchDefault 该作用域总结类无默认时,把优先级最高的启用条目提为默认。best-effort。
|
|
func (this *serverComp) promoteEchoOrchDefault(appName string, region int32) {
|
|
rows := make([]*EchoOrchestration, 0)
|
|
if err := postgres.Find(comm.TableEchomeetOrch, &rows, "app_name=? AND region=? AND kind=?", appName, region, echoKindLLM); err != nil {
|
|
return
|
|
}
|
|
var best *EchoOrchestration
|
|
for _, r := range rows {
|
|
if r.IsDefault {
|
|
return // 还有默认
|
|
}
|
|
if !r.Enabled {
|
|
continue
|
|
}
|
|
if best == nil || r.Priority < best.Priority {
|
|
best = r
|
|
}
|
|
}
|
|
if best == nil {
|
|
return
|
|
}
|
|
postgres.Exec("UPDATE "+comm.TableEchomeetOrch+" SET is_default=true WHERE row_id=?", best.RowId)
|
|
}
|
|
|
|
// reorderEchoOrch 批量调整同作用域内的优先级(拖拽/上下移后提交)。
|
|
func (this *serverComp) reorderEchoOrch(c *gin.Context) {
|
|
var req struct {
|
|
Items []struct {
|
|
RowId uint64 `json:"row_id"`
|
|
Priority int32 `json:"priority"`
|
|
} `json:"items"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
now := time.Now().Unix()
|
|
updated := 0
|
|
for _, it := range req.Items {
|
|
if it.RowId == 0 {
|
|
continue
|
|
}
|
|
if res := postgres.Exec("UPDATE "+comm.TableEchomeetOrch+" SET priority=?, updatetime=? WHERE row_id=?",
|
|
it.Priority, now, it.RowId); res.Error != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, res.Error.Error())
|
|
return
|
|
}
|
|
updated++
|
|
}
|
|
writeOK(c, gin.H{"updated": updated})
|
|
}
|
|
|
|
// saveEchoOrchSetting 保存作用域级开关(客户端可否指定总结模型),upsert 单行。
|
|
func (this *serverComp) saveEchoOrchSetting(c *gin.Context) {
|
|
var req struct {
|
|
echoOrchScope
|
|
LLMClientChoice bool `json:"llm_client_choice"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
writeErr(c, pb.ErrorCode_ReqParameterError, err.Error())
|
|
return
|
|
}
|
|
normScope(&req.echoOrchScope)
|
|
now := time.Now().Unix()
|
|
old := &EchoOrchSetting{}
|
|
if postgres.FindOne(comm.TableEchomeetOrchSetting, old, "app_name=? AND region=?", req.AppName, req.Region) == nil && old.RowId != 0 {
|
|
old.LLMClientChoice = req.LLMClientChoice
|
|
old.Updatetime = now
|
|
if err := postgres.Save(comm.TableEchomeetOrchSetting, old); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
row := &EchoOrchSetting{AppName: req.AppName, Region: req.Region, LLMClientChoice: req.LLMClientChoice, Updatetime: now}
|
|
if err := postgres.Insert(comm.TableEchomeetOrchSetting, row); err != nil {
|
|
writeErr(c, pb.ErrorCode_DBError, err.Error())
|
|
return
|
|
}
|
|
}
|
|
writeOK(c, gin.H{"llm_client_choice": req.LLMClientChoice})
|
|
}
|
|
|