init: initial commit

This commit is contained in:
Blizzard
2026-04-01 14:09:33 +08:00
commit aef2e152dc
66 changed files with 6540 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
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}
}