thread-master/apps/backend/internal/logic/settings/ai_helpers.go

113 lines
3.5 KiB
Go
Raw Permalink Normal View History

2026-07-13 01:15:30 +00:00
package settings
import (
"context"
"strings"
"apps/backend/internal/module/ai"
"apps/backend/internal/module/member/domain"
"apps/backend/internal/svc"
"apps/backend/internal/types"
)
func resolveAPIKey(svcCtx *svc.ServiceContext, st *domain.UserSettings, provider string) (key string, platformAvailable bool) {
provider = ai.NormalizeProvider(provider)
// 1) user BYOK for this provider
if st != nil {
if k := st.KeyForProvider(provider); k != "" {
return k, platformKeyAvailable(svcCtx, provider)
}
}
// 2) platform key
return platformKey(svcCtx, provider), platformKeyAvailable(svcCtx, provider)
}
func platformKey(svcCtx *svc.ServiceContext, provider string) string {
if svcCtx == nil {
return ""
}
switch ai.NormalizeProvider(provider) {
case ai.ProviderOpenCodeGo:
return strings.TrimSpace(svcCtx.Config.Platform.OpenCodeKey)
default:
k := strings.TrimSpace(svcCtx.Config.Platform.XAIKey)
if k == "" {
k = strings.TrimSpace(svcCtx.Config.Platform.AIKey)
}
return k
}
}
func platformKeyAvailable(svcCtx *svc.ServiceContext, provider string) bool {
return platformKey(svcCtx, provider) != ""
}
// loadModelsForProvider — 真拉 + 5 分鐘快取;失敗用 fallback不回空 list
func loadModelsForProvider(ctx context.Context, svcCtx *svc.ServiceContext, st *domain.UserSettings, provider string) (list []string, fromCache bool, modelsErr string) {
provider = ai.NormalizeProvider(provider)
key, _ := resolveAPIKey(svcCtx, st, provider)
if key == "" {
return ai.FallbackModels(provider), false, "尚未設定 API key會員 BYOK 或平台 key"
}
if svcCtx.AIRegistry == nil {
return ai.FallbackModels(provider), false, "AI registry not configured"
}
list, fromCache, err := svcCtx.AIRegistry.ListModelsCached(ctx, provider, key)
if err != nil {
return ai.FallbackModels(provider), false, err.Error()
}
return list, fromCache, ""
}
// pickModel優先用會員已存模型即使暫時不在列表也保留不寫死改成別台
func pickModel(preferred string, list []string) string {
preferred = strings.TrimSpace(preferred)
if preferred != "" {
return preferred
}
if len(list) > 0 {
return list[0]
}
return ""
}
func buildAiSettingsData(ctx context.Context, svcCtx *svc.ServiceContext, st *domain.UserSettings, modelsProvider string) *types.AiSettingsData {
if st == nil {
st = domain.DefaultSettings(0)
}
savedProvider := ai.NormalizeProvider(st.Provider)
if savedProvider == "" {
savedProvider = ai.ProviderXAI
}
modelsProvider = ai.NormalizeProvider(modelsProvider)
if modelsProvider == "" {
modelsProvider = savedProvider
}
list, fromCache, modelsErr := loadModelsForProvider(ctx, svcCtx, st, modelsProvider)
// 顯示/回傳都以會員存檔為準
selected := pickModel(st.Model, list)
savedModel := strings.TrimSpace(st.Model)
if modelsProvider == savedProvider && selected != "" {
savedModel = selected
}
keyConfigured := st.HasKeyForProvider(savedProvider)
platformAvail := platformKeyAvailable(svcCtx, savedProvider)
return &types.AiSettingsData{
Provider: savedProvider,
Model: savedModel,
ResearchProvider: savedProvider,
ResearchModel: savedModel,
ApiKeyConfigured: keyConfigured,
ResearchApiKeyConfigured: keyConfigured || st.ResearchAPIKeyConfigured,
PlatformKeyAvailable: platformAvail,
Models: list,
ModelsProvider: modelsProvider,
SelectedModel: selected,
ModelsFromCache: fromCache,
ModelsError: modelsErr,
}
}