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