优化代码
This commit is contained in:
1 parent
3553790475
commit
eda7e8bc96
23 files changed
+3113
-342
No files matched your search
+186
-63
@@ -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") {
|
||||
|
||||
Reference in new issue
Block a user