Files
ttstd-infinite-canvas/backend/internal/service/response_interception.go

158 lines
5.1 KiB
Go

package service
import (
"encoding/json"
"errors"
"fmt"
"strings"
"infinite-canvas/backend/internal/model"
"gorm.io/gorm"
)
const responseInterceptionSettingKey = "response_interception"
const (
maxResponseInterceptionRules = 100
maxResponseInterceptionTextRunes = 200
maxResponseInterceptionReplaceRunes = 500
)
type ResponseInterceptionRule struct {
Contains string `json:"contains"`
Replace string `json:"replace"`
}
type ResponseInterceptionSetting struct {
Enabled bool `json:"enabled"`
Rules []ResponseInterceptionRule `json:"rules"`
}
func defaultResponseInterceptionSetting() ResponseInterceptionSetting {
return ResponseInterceptionSetting{Rules: []ResponseInterceptionRule{}}
}
func (s *Service) AdminResponseInterceptionSetting(actor *model.User) (*ResponseInterceptionSetting, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
value, err := s.responseInterceptionSetting()
if err != nil {
return nil, err
}
return &value, nil
}
func (s *Service) UpdateResponseInterceptionSetting(actor *model.User, value ResponseInterceptionSetting) (*ResponseInterceptionSetting, error) {
if err := s.RequireAdmin(actor); err != nil {
return nil, err
}
value = normalizeResponseInterceptionSetting(value)
if err := validateResponseInterceptionSetting(value); err != nil {
return nil, err
}
before, err := s.responseInterceptionSetting()
if err != nil {
return nil, err
}
encoded, err := json.Marshal(value)
if err != nil {
return nil, err
}
current, err := s.repo.SystemSetting(responseInterceptionSettingKey)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
setting := &model.SystemSetting{Key: responseInterceptionSettingKey, ValueJSON: string(encoded), UpdatedBy: actor.ID}
if current != nil {
setting.CreatedAt = current.CreatedAt
}
if err := s.repo.SaveSystemSetting(setting); err != nil {
return nil, err
}
if err := s.appendAdminAudit(actor, "response_interception.update", "system_setting", responseInterceptionSettingKey, "更新模型响应拦截", map[string]any{"before": before, "after": value}); err != nil {
return nil, err
}
return &value, nil
}
// InterceptResponseText 只投影用户可见文案,不修改上游响应或请求明细中的原始内容。
func (s *Service) InterceptResponseText(raw string) string {
raw = strings.TrimSpace(raw)
setting, readErr := s.responseInterceptionSetting()
if readErr != nil || !setting.Enabled {
return raw
}
return interceptResponseMessage(setting, raw)
}
// UserFacingErrorMessage 只投影用户可见错误,不修改上游响应或请求明细中的原始内容。
func (s *Service) UserFacingErrorMessage(err error) string {
return s.InterceptResponseText(taskFailureMessage(err))
}
func interceptResponseMessage(setting ResponseInterceptionSetting, raw string) string {
lower := strings.ToLower(raw)
for _, rule := range setting.Rules {
if strings.Contains(lower, strings.ToLower(rule.Contains)) {
return rule.Replace
}
}
return raw
}
func (s *Service) responseInterceptionSetting() (ResponseInterceptionSetting, error) {
setting, err := s.repo.SystemSetting(responseInterceptionSettingKey)
if errors.Is(err, gorm.ErrRecordNotFound) {
return defaultResponseInterceptionSetting(), nil
}
if err != nil {
return ResponseInterceptionSetting{}, err
}
value := defaultResponseInterceptionSetting()
if strings.TrimSpace(setting.ValueJSON) == "" {
return value, nil
}
if err := json.Unmarshal([]byte(setting.ValueJSON), &value); err != nil {
return ResponseInterceptionSetting{}, fmt.Errorf("模型响应拦截配置格式错误: %w", err)
}
value = normalizeResponseInterceptionSetting(value)
if err := validateResponseInterceptionSetting(value); err != nil {
return ResponseInterceptionSetting{}, err
}
return value, nil
}
func normalizeResponseInterceptionSetting(value ResponseInterceptionSetting) ResponseInterceptionSetting {
if value.Rules == nil {
value.Rules = []ResponseInterceptionRule{}
}
for index := range value.Rules {
value.Rules[index].Contains = strings.TrimSpace(value.Rules[index].Contains)
value.Rules[index].Replace = strings.TrimSpace(value.Rules[index].Replace)
}
return value
}
func validateResponseInterceptionSetting(value ResponseInterceptionSetting) error {
if len(value.Rules) > maxResponseInterceptionRules {
return fmt.Errorf("模型响应拦截规则不能超过 %d 条", maxResponseInterceptionRules)
}
for index, rule := range value.Rules {
if rule.Contains == "" {
return fmt.Errorf("第 %d 条拦截规则的匹配文案不能为空", index+1)
}
if len([]rune(rule.Contains)) > maxResponseInterceptionTextRunes {
return fmt.Errorf("第 %d 条拦截规则的匹配文案不能超过 %d 个字符", index+1, maxResponseInterceptionTextRunes)
}
if rule.Replace == "" {
return fmt.Errorf("第 %d 条拦截规则的替换文案不能为空", index+1)
}
if len([]rune(rule.Replace)) > maxResponseInterceptionReplaceRunes {
return fmt.Errorf("第 %d 条拦截规则的替换文案不能超过 %d 个字符", index+1, maxResponseInterceptionReplaceRunes)
}
}
return nil
}