113 lines
3.5 KiB
Go
113 lines
3.5 KiB
Go
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,
|
||
}
|
||
}
|