132 lines
3.4 KiB
Go
132 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"AI-Expert-Sidebar/internal/config"
|
|
"AI-Expert-Sidebar/internal/crypto"
|
|
"AI-Expert-Sidebar/internal/database"
|
|
"AI-Expert-Sidebar/internal/models"
|
|
)
|
|
|
|
// SettingsDTO is what the frontend reads and writes.
|
|
type SettingsDTO struct {
|
|
AIProvider string `json:"ai_provider"`
|
|
BaseURL string `json:"base_url"`
|
|
APIKey string `json:"api_key"`
|
|
Model string `json:"model"`
|
|
SystemPrompt string `json:"system_prompt"`
|
|
MaxTokens int `json:"max_tokens"`
|
|
UsePublicKey bool `json:"use_public_key"`
|
|
}
|
|
|
|
// GetSettings reads AI config from settings.db key-value store.
|
|
func GetSettings() *SettingsDTO {
|
|
sdb := database.GetSettings()
|
|
if sdb == nil {
|
|
return defaultDTO()
|
|
}
|
|
var rows []models.AppSetting
|
|
sdb.Find(&rows)
|
|
m := make(map[string]string, len(rows))
|
|
for _, r := range rows {
|
|
m[r.Key] = r.Value
|
|
}
|
|
|
|
apiKey, _ := crypto.DecryptAPIKey(m["api_key_encrypted"])
|
|
maxTokens := 1024
|
|
fmt.Sscanf(m["max_tokens"], "%d", &maxTokens)
|
|
if maxTokens <= 0 {
|
|
maxTokens = 1024
|
|
}
|
|
return &SettingsDTO{
|
|
AIProvider: strOr(m["ai_provider"], "deepseek"),
|
|
BaseURL: m["base_url"],
|
|
APIKey: apiKey,
|
|
Model: strOr(m["model"], "deepseek-chat"),
|
|
SystemPrompt: m["system_prompt"],
|
|
MaxTokens: maxTokens,
|
|
UsePublicKey: m["use_public_key"] != "false",
|
|
}
|
|
}
|
|
|
|
// SaveSettings persists AI config into settings.db.
|
|
func SaveSettings(dto SettingsDTO) error {
|
|
sdb := database.GetSettings()
|
|
if sdb == nil {
|
|
return fmt.Errorf("settings DB not ready")
|
|
}
|
|
upsert := func(k, v string) {
|
|
sdb.Exec("INSERT INTO app_settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", k, v)
|
|
}
|
|
upsert("ai_provider", dto.AIProvider)
|
|
upsert("base_url", dto.BaseURL)
|
|
upsert("model", dto.Model)
|
|
upsert("system_prompt", dto.SystemPrompt)
|
|
upsert("max_tokens", fmt.Sprintf("%d", dto.MaxTokens))
|
|
usePublic := "true"
|
|
if !dto.UsePublicKey {
|
|
usePublic = "false"
|
|
}
|
|
upsert("use_public_key", usePublic)
|
|
if !dto.UsePublicKey && dto.APIKey != "" {
|
|
enc, err := crypto.EncryptAPIKey(dto.APIKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
upsert("api_key_encrypted", enc)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResolveAIConfig returns the effective AI call config (local settings or global fallback).
|
|
func ResolveAIConfig() AICallConfig {
|
|
base := AICallConfig{
|
|
BaseURL: "https://api.deepseek.com/chat/completions",
|
|
APIKey: config.Global.DeepSeek.APIKey,
|
|
Model: config.Global.DeepSeek.Model,
|
|
MaxTokens: config.Global.DeepSeek.MaxTokens,
|
|
}
|
|
dto := GetSettings()
|
|
if dto == nil {
|
|
return base
|
|
}
|
|
base.SystemPrompt = dto.SystemPrompt
|
|
if dto.UsePublicKey || dto.APIKey == "" {
|
|
return base
|
|
}
|
|
providerURL := dto.BaseURL
|
|
if providerURL == "" {
|
|
switch dto.AIProvider {
|
|
case "deepseek":
|
|
providerURL = "https://api.deepseek.com/chat/completions"
|
|
case "openai":
|
|
providerURL = "https://api.openai.com/v1/chat/completions"
|
|
case "grok":
|
|
providerURL = "https://api.x.ai/v1/chat/completions"
|
|
}
|
|
}
|
|
maxTok := dto.MaxTokens
|
|
if maxTok <= 0 {
|
|
maxTok = 1024
|
|
}
|
|
return AICallConfig{
|
|
BaseURL: strOr(providerURL, base.BaseURL),
|
|
APIKey: dto.APIKey,
|
|
Model: strOr(dto.Model, base.Model),
|
|
MaxTokens: maxTok,
|
|
SystemPrompt: dto.SystemPrompt,
|
|
}
|
|
}
|
|
|
|
func strOr(v, def string) string {
|
|
if v == "" {
|
|
return def
|
|
}
|
|
return v
|
|
}
|
|
|
|
func defaultDTO() *SettingsDTO {
|
|
return &SettingsDTO{AIProvider: "deepseek", Model: "deepseek-chat", MaxTokens: 1024, UsePublicKey: true}
|
|
}
|