209 lines
5.9 KiB
Go
209 lines
5.9 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"infinite-canvas/backend/internal/model"
|
|
)
|
|
|
|
type ChannelModelRequest struct {
|
|
ModelKey string `json:"modelKey"`
|
|
DisplayName string `json:"displayName"`
|
|
Capability string `json:"capability"`
|
|
BillingMode string `json:"billingMode"`
|
|
UnitPriceMicrocredits int64 `json:"unitPriceMicrocredits"`
|
|
PriceConfigured bool `json:"priceConfigured"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
|
|
func (s *Service) EnsureSystemChannelModels() error {
|
|
channels, err := s.repo.SystemChannels(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for index := range channels {
|
|
items, err := s.repo.ChannelModels(channels[index].ID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(items) == 0 {
|
|
if err := s.syncInitialChannelModels(&channels[index], channelModelNames(channels[index])); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) AdminChannelModels(actor *model.User, channelID string) ([]model.ChannelModel, error) {
|
|
if err := s.RequireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := s.repo.AdminSystemChannel(channelID); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ensureChannelModels(channelID, true)
|
|
}
|
|
|
|
func (s *Service) SaveAdminChannelModel(actor *model.User, channelID string, id string, req ChannelModelRequest) (*model.ChannelModel, error) {
|
|
if err := s.RequireAdmin(actor); err != nil {
|
|
return nil, err
|
|
}
|
|
channel, err := s.repo.AdminSystemChannel(channelID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
modelKey := strings.TrimPrefix(strings.TrimSpace(req.ModelKey), "models/")
|
|
if modelKey == "" {
|
|
return nil, BadAuthRequest("请填写模型标识")
|
|
}
|
|
capability := normalizeCapability(req.Capability)
|
|
if capability == "" {
|
|
capability = capabilityForChannel(*channel)
|
|
}
|
|
if capability == "" {
|
|
return nil, BadAuthRequest("请选择模型能力")
|
|
}
|
|
billingMode := strings.TrimSpace(req.BillingMode)
|
|
if billingMode == "" {
|
|
billingMode = "fixed_request"
|
|
}
|
|
if billingMode != "fixed_request" {
|
|
return nil, BadAuthRequest("当前版本只支持按次固定计费")
|
|
}
|
|
if req.UnitPriceMicrocredits < 0 {
|
|
return nil, BadAuthRequest("模型积分价格不能小于 0")
|
|
}
|
|
item := &model.ChannelModel{ID: newID(), ChannelID: channelID, Enabled: true, PriceVersion: 1}
|
|
if id != "" {
|
|
item, err = s.repo.ChannelModelByID(channelID, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.PriceVersion++
|
|
}
|
|
item.ModelKey = modelKey
|
|
item.DisplayName = strings.TrimSpace(req.DisplayName)
|
|
if item.DisplayName == "" {
|
|
item.DisplayName = modelKey
|
|
}
|
|
item.Capability = capability
|
|
item.BillingMode = billingMode
|
|
item.UnitPriceMicrocredits = req.UnitPriceMicrocredits
|
|
item.PriceConfigured = req.PriceConfigured
|
|
if req.Enabled != nil {
|
|
item.Enabled = *req.Enabled
|
|
}
|
|
if err := s.repo.SaveChannelModel(item); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.syncChannelModelNames(channel); err != nil {
|
|
return nil, err
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
func (s *Service) DisableAdminChannelModel(actor *model.User, channelID string, id string) error {
|
|
if err := s.RequireAdmin(actor); err != nil {
|
|
return err
|
|
}
|
|
item, err := s.repo.ChannelModelByID(channelID, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
item.Enabled = false
|
|
item.PriceVersion++
|
|
if err := s.repo.SaveChannelModel(item); err != nil {
|
|
return err
|
|
}
|
|
channel, err := s.repo.AdminSystemChannel(channelID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.syncChannelModelNames(channel)
|
|
}
|
|
|
|
func (s *Service) syncInitialChannelModels(channel *model.ModelChannel, names []string) error {
|
|
existing, err := s.repo.ChannelModels(channel.ID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
byKey := make(map[string]*model.ChannelModel, len(existing))
|
|
for index := range existing {
|
|
byKey[existing[index].ModelKey] = &existing[index]
|
|
}
|
|
desired := make(map[string]bool, len(names))
|
|
for _, name := range uniqueNonEmpty(names) {
|
|
name = strings.TrimPrefix(name, "models/")
|
|
desired[name] = true
|
|
if item := byKey[name]; item != nil {
|
|
if !item.Enabled {
|
|
item.Enabled = true
|
|
item.PriceVersion++
|
|
if err := s.repo.SaveChannelModel(item); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
item := model.ChannelModel{ID: newID(), ChannelID: channel.ID, ModelKey: name, DisplayName: name, Capability: capabilityForChannel(*channel), BillingMode: "fixed_request", Enabled: true, PriceVersion: 1}
|
|
if err := s.repo.SaveChannelModel(&item); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for index := range existing {
|
|
if existing[index].Enabled && !desired[existing[index].ModelKey] {
|
|
existing[index].Enabled = false
|
|
existing[index].PriceVersion++
|
|
if err := s.repo.SaveChannelModel(&existing[index]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) ensureChannelModels(channelID string, includeDisabled bool) ([]model.ChannelModel, error) {
|
|
items, err := s.repo.ChannelModels(channelID, includeDisabled)
|
|
if err != nil || len(items) > 0 {
|
|
return items, err
|
|
}
|
|
channel, err := s.repo.AdminSystemChannel(channelID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.syncInitialChannelModels(channel, channelModelNames(*channel)); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.ChannelModels(channelID, includeDisabled)
|
|
}
|
|
|
|
func (s *Service) syncChannelModelNames(channel *model.ModelChannel) error {
|
|
items, err := s.repo.ChannelModels(channel.ID, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
names := make([]string, 0, len(items))
|
|
for _, item := range items {
|
|
names = append(names, item.ModelKey)
|
|
}
|
|
encoded, err := json.Marshal(names)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
channel.ModelsJSON = string(encoded)
|
|
return s.repo.Save(channel)
|
|
}
|
|
|
|
func capabilityForChannel(channel model.ModelChannel) string {
|
|
switch channel.InterfaceType {
|
|
case model.ChannelInterfaceOpenAIImage:
|
|
return "image"
|
|
case model.ChannelInterfaceNewAPIVideo, model.ChannelInterfaceNewAPIChannel1, model.ChannelInterfaceNewAPIChannel2, model.ChannelInterfaceXAIVideo:
|
|
return "video"
|
|
default:
|
|
return "text"
|
|
}
|
|
}
|