更新代码

This commit is contained in:
hero920103 committed 2026-10-28 17:39:33 +08:00
1 parent eda7e8bc96
commit 6ff632e0b0
19 files changed
+2033 -197

No files matched your search

+38 -13
View File
@@ -41,13 +41,14 @@ func NewProfile(name, format string) Profile {
// Capture 是抓屏与触发策略参数。
type Capture struct {
FPS float64 `json:"fps"` // 监听帧率,1~5
MaxEdge int `json:"maxEdge"` // 发给模型的图片长边上限
JPEGQuality int `json:"jpegQuality"` // 1~100
DiffThreshold int `json:"diffThreshold"` // 感知哈希汉明距离阈值
CooldownMs int `json:"cooldownMs"` // 两次自动提问的最小间隔
StableFrames int `json:"stableFrames"` // 连续稳定帧数(去抖)
HistoryTurns int `json:"historyTurns"` // 带上的历史文本轮数
FPS float64 `json:"fps"` // 监听帧率,1~5
MaxEdge int `json:"maxEdge"` // 发给模型的图片长边上限
JPEGQuality int `json:"jpegQuality"` // 1~100
DiffThreshold int `json:"diffThreshold"` // 感知哈希汉明距离阈值
DiffSensitivity float64 `json:"diffSensitivity"` // 细节网格归一化平均差阈值(越小越灵敏)
CooldownMs int `json:"cooldownMs"` // 两次自动提问的最小间隔
StableFrames int `json:"stableFrames"` // 连续稳定帧数(去抖)
HistoryTurns int `json:"historyTurns"` // 带上的历史文本轮数
}
// Hotkeys 保存全局热键描述,语法如 "Ctrl+Alt+Q"。
@@ -140,7 +141,7 @@ func Default() *Config {
return &Config{
Capture: Capture{
FPS: 2, MaxEdge: 1280, JPEGQuality: 75,
DiffThreshold: 5, CooldownMs: 3000, StableFrames: 2, HistoryTurns: 6,
DiffThreshold: 5, DiffSensitivity: 0.01, CooldownMs: 3000, StableFrames: 2, HistoryTurns: 6,
},
Hotkeys: Hotkeys{
SelectRegion: "Ctrl+Alt+X",
@@ -246,11 +247,24 @@ func (c *Config) Template(key string) Template {
return Template{Key: "chat", Name: "自由对话", System: "你是桌面视觉助手。"}
}
// ensureModel 在没有任何接入点时补一个空白项。
//
// 只用于首次运行(还没有配置文件)时给用户一个可以填的入口;
// 用户主动删光之后不该被自动塞回来。
func (c *Config) ensureModel() {
if len(c.Models) > 0 {
return
}
c.Models = []Profile{NewProfile("接入点 1", llm.FormatOpenAI)}
c.Active = c.Models[0].Name
}
// Clone 返回一份深拷贝,避免设置窗口与引擎共享同一份可变配置。
func (c *Config) Clone() *Config {
if c == nil {
cfg := Default()
cfg.Normalize()
cfg.ensureModel()
return cfg
}
out := *c
@@ -270,10 +284,10 @@ func (c *Config) Clone() *Config {
}
// Normalize 补全缺失字段,保证运行期不会出现零值。
//
// 注意这里**不再**自动补接入点:用户主动删光之后不该又被塞回来,
// 否则"删除"看起来像没生效。首次运行的空白接入点由 ensureModel 负责。
func (c *Config) Normalize() {
if len(c.Models) == 0 {
c.Models = []Profile{NewProfile("接入点 1", llm.FormatOpenAI)}
}
for i := range c.Models {
m := &c.Models[i]
m.Name = strings.TrimSpace(m.Name)
@@ -303,8 +317,13 @@ func (c *Config) Normalize() {
c.Models[i].Name = name
used[name] = true
}
if _, ok := c.ActiveModel(); !ok {
c.Active = c.Models[0].Name
// 一个接入点都没有时,Active 留空即可。
if len(c.Models) > 0 {
if _, ok := c.ActiveModel(); !ok {
c.Active = c.Models[0].Name
}
} else {
c.Active = ""
}
if len(c.Templates) == 0 {
@@ -322,6 +341,9 @@ func (c *Config) Normalize() {
if c.Capture.DiffThreshold <= 0 {
c.Capture.DiffThreshold = 5
}
if c.Capture.DiffSensitivity <= 0 {
c.Capture.DiffSensitivity = 0.01
}
if c.Capture.CooldownMs <= 0 {
c.Capture.CooldownMs = 3000
}
@@ -377,6 +399,8 @@ func (s *Store) Load() *Config {
if err != nil {
cfg := Default()
cfg.Normalize()
// 首次运行:给一个空白接入点作为填写入口。
cfg.ensureModel()
return cfg
}
cfg := Default()
@@ -384,6 +408,7 @@ func (s *Store) Load() *Config {
_ = os.WriteFile(s.path+".broken", raw, 0o644)
fresh := Default()
fresh.Normalize()
fresh.ensureModel()
return fresh
}
var legacy legacyConfig
+52 -2
View File
@@ -12,9 +12,10 @@ import (
func TestDefaultIsUsable(t *testing.T) {
cfg := Default()
cfg.Normalize()
cfg.ensureModel()
if len(cfg.Models) == 0 {
t.Fatal("Normalize 后应至少有一份接入点")
t.Fatal("首次运行应有一份空白接入点")
}
if _, ok := cfg.ActiveModel(); !ok {
t.Fatal("Normalize 后应有生效接入点")
@@ -33,9 +34,10 @@ func TestDefaultIsUsable(t *testing.T) {
func TestNormalizeFillsZeroValues(t *testing.T) {
cfg := &Config{}
cfg.Normalize()
cfg.ensureModel()
if len(cfg.Models) == 0 {
t.Fatal("Normalize 应补全接入点列表")
t.Fatal("首次运行应补上一份接入点")
}
if cfg.Models[0].Format != llm.FormatOpenAI {
t.Fatalf("默认接入点应为 OpenAI 兼容格式,实际 %s", cfg.Models[0].Format)
@@ -105,6 +107,7 @@ func TestNormalizeTheme(t *testing.T) {
func TestCloneIsIndependent(t *testing.T) {
cfg := Default()
cfg.Normalize()
cfg.ensureModel()
cp := cfg.Clone()
cp.Models[0].Model = "changed"
@@ -173,6 +176,53 @@ func TestRemoveModelClearsActive(t *testing.T) {
}
}
func TestRemoveModelStaysRemoved(t *testing.T) {
cfg := Default()
cfg.Models = []Profile{
{Name: "a", Model: "m-a"},
{Name: "b", Model: "m-b"},
{Name: "c", Model: "m-c"},
}
cfg.Active = "b"
cfg.Normalize()
if !cfg.RemoveModel("b") {
t.Fatal("应能删除接入点")
}
// 再归一化一次,模拟落盘后重新加载:被删的接入点不该复活。
cfg.Normalize()
if len(cfg.Models) != 2 {
t.Fatalf("删除后应只剩 2 个接入点,实际 %d: %+v", len(cfg.Models), cfg.Models)
}
for _, m := range cfg.Models {
if m.Name == "b" {
t.Fatalf("被删的接入点不该被补回来: %+v", cfg.Models)
}
}
active, ok := cfg.ActiveModel()
if !ok {
t.Fatal("应自动选中一个剩余接入点")
}
if active.Name != "a" && active.Name != "c" {
t.Fatalf("生效项应指向剩余的接入点,实际 %s", active.Name)
}
}
func TestNormalizeKeepsEmptyModelList(t *testing.T) {
// 用户把接入点删光后,Normalize 不该再偷偷补一个回来,
// 否则"删除"看起来完全没生效。
cfg := Default()
cfg.Normalize()
if len(cfg.Models) != 0 {
t.Fatalf("Normalize 不应凭空补接入点: %+v", cfg.Models)
}
if cfg.Active != "" {
t.Fatalf("没有接入点时 Active 应为空,实际 %s", cfg.Active)
}
}
func TestMigrateLegacyProfiles(t *testing.T) {
cfg := Default()
cfg.migrateLegacy(legacyConfig{