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

113 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}