优化代码

This commit is contained in:
hero920103 committed 2026-10-28 08:47:09 +08:00
1 parent 3553790475
commit eda7e8bc96
23 files changed
+3113 -342

No files matched your search

+186 -63
View File
@@ -9,11 +9,14 @@ import (
"strings"
"viewsagent/internal/capture"
"viewsagent/internal/llm"
)
// Profile 是一个 OpenAI 兼容协议的模型接入点。
// Profile 描述一份模型接入配置。Name 是用户自己起的名字,
// 既是左侧列表的显示文本,也是 Config.Active 引用的键。
type Profile struct {
Name string `json:"name"`
Format string `json:"format"`
BaseURL string `json:"baseURL"`
APIKey string `json:"apiKey"`
Model string `json:"model"`
@@ -22,6 +25,20 @@ type Profile struct {
Temperature float64 `json:"temperature"`
}
// NewProfile 按接口格式生成一份空白模型配置,只带好该格式必需的采样默认值。
// BaseURL 与模型名留空,由用户按自己用的服务商填写。
func NewProfile(name, format string) Profile {
if format != llm.FormatAnthropic {
format = llm.FormatOpenAI
}
return Profile{
Name: name,
Format: format,
MaxTokens: 1024,
Temperature: 0.3,
}
}
// Capture 是抓屏与触发策略参数。
type Capture struct {
FPS float64 `json:"fps"` // 监听帧率,1~5
@@ -48,8 +65,11 @@ type Template struct {
// Config 是完整的应用配置。
type Config struct {
ActiveProfile string `json:"activeProfile"`
Profiles []Profile `json:"profiles"`
// Models 是全部已保存的模型配置,Active 指向其中生效的那一条。
Models []Profile `json:"models"`
Active string `json:"active"`
// Theme 是界面主题:system 跟随系统、contrast 黑白高对比、light 浅色高对比。
Theme string `json:"theme"`
Capture Capture `json:"capture"`
Hotkeys Hotkeys `json:"hotkeys"`
PromptTemplate string `json:"promptTemplate"`
@@ -113,32 +133,11 @@ func DefaultTemplates() []Template {
}
}
// Default 返回一份可直接使用的默认配置。
// Default 返回一份默认配置。
// Models 故意留空:由 Normalize 补一个空白条目,这样旧配置文件反序列化后
// 仍能通过 len(Models)==0 判断出走迁移分支。
func Default() *Config {
return &Config{
ActiveProfile: "qwen",
Profiles: []Profile{
{
Name: "qwen", BaseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
Model: "qwen3-vl-plus", MaxTokens: 1024, Temperature: 0.3,
},
{
Name: "doubao", BaseURL: "https://ark.cn-beijing.volces.com/api/v3",
Model: "doubao-seed-1-6-vision", MaxTokens: 1024, Temperature: 0.3,
},
{
Name: "zhipu", BaseURL: "https://open.bigmodel.cn/api/paas/v4",
Model: "glm-4.6v", MaxTokens: 1024, Temperature: 0.3,
},
{
Name: "kimi", BaseURL: "https://api.moonshot.cn/v1",
Model: "kimi-latest", MaxTokens: 1024, Temperature: 0.3,
},
{
Name: "ollama", BaseURL: "http://localhost:11434/v1",
Model: "qwen2.5vl:7b", MaxTokens: 1024, Temperature: 0.3,
},
},
Capture: Capture{
FPS: 2, MaxEdge: 1280, JPEGQuality: 75,
DiffThreshold: 5, CooldownMs: 3000, StableFrames: 2, HistoryTurns: 6,
@@ -153,25 +152,85 @@ func Default() *Config {
}
}
// Profile 按名称查找接入点。
func (c *Config) Profile(name string) (Profile, bool) {
for _, p := range c.Profiles {
if p.Name == name {
return p, true
// ActiveModel 返回当前生效的模型配置。
func (c *Config) ActiveModel() (Profile, bool) {
for i := range c.Models {
if c.Models[i].Name == c.Active {
return c.Models[i], true
}
}
return Profile{}, false
}
// Active 返回当前启用的接入点,找不到时回退到第一个。
func (c *Config) Active() Profile {
if p, ok := c.Profile(c.ActiveProfile); ok {
return p
// ModelNames 返回全部模型名字,顺序即列表顺序。
func (c *Config) ModelNames() []string {
names := make([]string, 0, len(c.Models))
for _, m := range c.Models {
names = append(names, m.Name)
}
if len(c.Profiles) > 0 {
return c.Profiles[0]
return names
}
// ProfileIndex 按名字返回接入点在列表里的下标,找不到返回 -1。
func (c *Config) ProfileIndex(name string) int {
for i := range c.Models {
if c.Models[i].Name == name {
return i
}
}
return Profile{}
return -1
}
// SetActive 把指定接入点设为生效项;名字不存在时返回 false。
func (c *Config) SetActive(name string) bool {
if c.ProfileIndex(name) < 0 {
return false
}
c.Active = name
return true
}
// AddProfile 追加一份接入点,名字为空或冲突时自动改名,并返回真正落库的副本。
func (c *Config) AddProfile(p Profile) Profile {
p.Name = strings.TrimSpace(p.Name)
if p.Name == "" {
p.Name = "接入点"
}
p.Name = c.UniqueName(p.Name)
c.Models = append(c.Models, p)
return p
}
// UniqueName 基于前缀生成一个在现有模型里不重复的名字。
func (c *Config) UniqueName(prefix string) string {
used := make(map[string]bool, len(c.Models))
for _, m := range c.Models {
used[m.Name] = true
}
if !used[prefix] {
return prefix
}
for n := 2; ; n++ {
name := fmt.Sprintf("%s (%d)", prefix, n)
if !used[name] {
return name
}
}
}
// RemoveModel 删除指定名字的模型;删掉生效那条时把 Active 置空,由 Normalize 重新兜底。
func (c *Config) RemoveModel(name string) bool {
for i := range c.Models {
if c.Models[i].Name != name {
continue
}
c.Models = append(c.Models[:i], c.Models[i+1:]...)
if c.Active == name {
c.Active = ""
}
return true
}
return false
}
// Template 按 key 查找提示词模板。
@@ -190,44 +249,66 @@ func (c *Config) Template(key string) Template {
// Clone 返回一份深拷贝,避免设置窗口与引擎共享同一份可变配置。
func (c *Config) Clone() *Config {
if c == nil {
return Default()
cfg := Default()
cfg.Normalize()
return cfg
}
out := *c
out.Profiles = append([]Profile(nil), c.Profiles...)
for i := range out.Profiles {
if c.Profiles[i].ExtraHeaders != nil {
h := make(map[string]string, len(c.Profiles[i].ExtraHeaders))
for k, v := range c.Profiles[i].ExtraHeaders {
out.Templates = append([]Template(nil), c.Templates...)
out.Models = make([]Profile, len(c.Models))
for i, m := range c.Models {
out.Models[i] = m
if m.ExtraHeaders != nil {
h := make(map[string]string, len(m.ExtraHeaders))
for k, v := range m.ExtraHeaders {
h[k] = v
}
out.Profiles[i].ExtraHeaders = h
out.Models[i].ExtraHeaders = h
}
}
out.Templates = append([]Template(nil), c.Templates...)
return &out
}
// Normalize 补全缺失字段,保证运行期不会出现零值。
func (c *Config) Normalize() {
def := Default()
if len(c.Profiles) == 0 {
c.Profiles = def.Profiles
if len(c.Models) == 0 {
c.Models = []Profile{NewProfile("接入点 1", llm.FormatOpenAI)}
}
if c.ActiveProfile == "" {
c.ActiveProfile = c.Profiles[0].Name
}
for i := range c.Profiles {
p := &c.Profiles[i]
if p.MaxTokens <= 0 {
p.MaxTokens = 1024
for i := range c.Models {
m := &c.Models[i]
m.Name = strings.TrimSpace(m.Name)
if m.Name == "" {
m.Name = fmt.Sprintf("接入点 %d", i+1)
}
if p.Temperature <= 0 {
p.Temperature = 0.3
if m.Format != llm.FormatAnthropic {
m.Format = llm.FormatOpenAI
}
m.BaseURL = strings.TrimRight(strings.TrimSpace(m.BaseURL), "/")
m.Model = strings.TrimSpace(m.Model)
if m.MaxTokens <= 0 {
m.MaxTokens = 1024
}
if m.Temperature <= 0 {
m.Temperature = 0.3
}
p.BaseURL = strings.TrimRight(strings.TrimSpace(p.BaseURL), "/")
}
// 名字必须唯一,否则 Active 无法定位到确定的那一条。
used := make(map[string]bool, len(c.Models))
for i := range c.Models {
base := c.Models[i].Name
name := base
for n := 2; used[name]; n++ {
name = fmt.Sprintf("%s (%d)", base, n)
}
c.Models[i].Name = name
used[name] = true
}
if _, ok := c.ActiveModel(); !ok {
c.Active = c.Models[0].Name
}
if len(c.Templates) == 0 {
c.Templates = def.Templates
c.Templates = DefaultTemplates()
}
if c.Capture.FPS <= 0 {
c.Capture.FPS = 2
@@ -251,10 +332,15 @@ func (c *Config) Normalize() {
c.Capture.HistoryTurns = 6
}
if c.Hotkeys.SelectRegion == "" {
c.Hotkeys.SelectRegion = def.Hotkeys.SelectRegion
c.Hotkeys.SelectRegion = "Ctrl+Alt+X"
}
switch c.Theme {
case "contrast", "light":
default:
c.Theme = "system"
}
if c.Hotkeys.ToggleWatch == "" {
c.Hotkeys.ToggleWatch = def.Hotkeys.ToggleWatch
c.Hotkeys.ToggleWatch = "Ctrl+Alt+Space"
}
if c.PromptTemplate == "" {
c.PromptTemplate = c.Templates[0].Key
@@ -300,11 +386,48 @@ func (s *Store) Load() *Config {
fresh.Normalize()
return fresh
}
var legacy legacyConfig
_ = json.Unmarshal(raw, &legacy)
cfg.migrateLegacy(legacy)
cfg.Normalize()
cfg.migrate()
return cfg
}
// legacyConfig 是历史版本的两种配置形状,仅用于迁移。
type legacyConfig struct {
// 上一版:单份模型 + 厂商 key。
Vendor string `json:"vendor"`
Model Profile `json:"model"`
// 更早版本:多接入点。
ActiveProfile string `json:"activeProfile"`
Profiles []Profile `json:"profiles"`
}
// migrateLegacy 把旧版配置折叠成 Models 列表。
// 旧格式一律是 OpenAI 兼容,Anthropic 格式是这次才引入的。
func (c *Config) migrateLegacy(l legacyConfig) {
if len(c.Models) > 0 {
return
}
switch {
case len(l.Profiles) > 0:
c.Models = append([]Profile(nil), l.Profiles...)
c.Active = l.ActiveProfile
case l.Model.BaseURL != "" || l.Model.Model != "":
c.Models = []Profile{l.Model}
c.Active = l.Model.Name
if c.Active == "" {
c.Active = l.Vendor
}
}
for i := range c.Models {
if c.Models[i].Format == "" {
c.Models[i].Format = llm.FormatOpenAI
}
}
}
// migrate 把旧版本的默认值升级到当前默认值,避免老配置文件一直沿用已知冲突的热键。
func (c *Config) migrate() {
if strings.EqualFold(strings.TrimSpace(c.Hotkeys.SelectRegion), "Ctrl+Alt+Q") {