更新代码

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

+111 -27
View File
@@ -30,6 +30,7 @@ type settingsWindow struct {
// 模型接入:左边是接入点列表,右边是当前接入点的接口详情。
profileList *widget.List
emptyHint *widget.Label
selIndex int
loading bool // 回填表单时抑制回写
@@ -53,6 +54,7 @@ type settingsWindow struct {
maxEdge *widget.Entry
quality *widget.Entry
diff *widget.Entry
sens *widget.Entry
cooldown *widget.Entry
stable *widget.Entry
turns *widget.Entry
@@ -72,6 +74,9 @@ func newSettingsWindow(app *App) *settingsWindow {
s.cfg = app.eng.Config().Clone()
s.win = app.fyne.NewWindow(settingsTitle)
s.win.Resize(fyne.NewSize(900, 700))
// 点关闭只是隐藏窗口:Fyne 的窗口一旦 Close 就被销毁,
// 工具栏的设置按钮会再也打不开它。
s.win.SetCloseIntercept(func() { s.win.Hide() })
s.tabs = container.NewAppTabs(
container.NewTabItem("模型", s.buildModelTab()),
container.NewTabItem("提示词", s.buildTemplateTab()),
@@ -103,16 +108,10 @@ func (s *settingsWindow) reload() {
s.cfg = s.app.eng.Config().Clone()
s.loading = true
s.profileList.Refresh()
idx := s.cfg.ProfileIndex(s.cfg.Active)
if idx < 0 {
idx = 0
}
s.selIndex = idx
s.profileList.Select(idx)
// 选中的项没变化时 List 不会再回调,这里显式回填一次表单。
s.loadProfileToForm()
s.selIndex = -1
s.syncSelection()
s.loading = false
s.refreshList()
keys := make([]string, 0, len(s.cfg.Templates))
for _, t := range s.cfg.Templates {
@@ -127,6 +126,7 @@ func (s *settingsWindow) reload() {
s.maxEdge.SetText(strconv.Itoa(c.MaxEdge))
s.quality.SetText(strconv.Itoa(c.JPEGQuality))
s.diff.SetText(strconv.Itoa(c.DiffThreshold))
s.sens.SetText(fmt.Sprintf("%g", c.DiffSensitivity))
s.cooldown.SetText(strconv.Itoa(c.CooldownMs))
s.stable.SetText(strconv.Itoa(c.StableFrames))
s.turns.SetText(strconv.Itoa(c.HistoryTurns))
@@ -202,12 +202,16 @@ func (s *settingsWindow) buildProfilePane() fyne.CanvasObject {
)
s.profileList.OnSelected = func(id widget.ListItemID) { s.selectProfile(int(id)) }
s.emptyHint = widget.NewLabel("还没有接入点,点「新增」添加一个")
s.emptyHint.Importance = widget.LowImportance
s.emptyHint.Hide()
addBtn := widget.NewButtonWithIcon("新增", theme.ContentAddIcon(), func() { s.askNewFormat() })
dupBtn := widget.NewButtonWithIcon("复制", theme.ContentCopyIcon(), func() { s.duplicateProfile() })
delBtn := widget.NewButtonWithIcon("删除", theme.DeleteIcon(), func() { s.deleteProfile() })
useBtn := widget.NewButton("设为生效", func() { s.activateProfile() })
top := container.NewVBox(widget.NewLabel("接入点"), widget.NewSeparator())
top := container.NewVBox(widget.NewLabel("接入点"), widget.NewSeparator(), s.emptyHint)
bottom := container.NewVBox(
widget.NewSeparator(),
container.NewHBox(addBtn, dupBtn, delBtn),
@@ -413,8 +417,8 @@ func (s *settingsWindow) askNewFormat() {
// addProfile 按选定格式新增一份接入点。
func (s *settingsWindow) addProfile(format string) {
p := s.cfg.AddProfile(config.NewProfile("接入点", format))
s.profileList.Refresh()
s.profileList.Select(s.cfg.ProfileIndex(p.Name))
s.selectInList(s.cfg.ProfileIndex(p.Name))
s.applyNow("已新增接入点 " + p.Name)
}
// duplicateProfile 复制当前接入点。
@@ -427,34 +431,40 @@ func (s *settingsWindow) duplicateProfile() {
cp.Name = s.cfg.UniqueName(p.Name + " 副本")
cp.ExtraHeaders = copyHeaders(p.ExtraHeaders)
s.cfg.Models = append(s.cfg.Models, cp)
s.profileList.Refresh()
s.profileList.Select(s.cfg.ProfileIndex(cp.Name))
s.selectInList(s.cfg.ProfileIndex(cp.Name))
s.applyNow("已复制为接入点 " + cp.Name)
}
// selectInList 选中列表里的第 i 项,并把右边的表单切过去。
func (s *settingsWindow) selectInList(i int) {
if i < 0 || i >= len(s.cfg.Models) {
return
}
s.selIndex = i
// 先取消再选,保证 OnSelected 一定回调,表单随之刷新。
s.profileList.UnselectAll()
s.profileList.Select(i)
}
// deleteProfile 删除当前接入点。
// 删除立即生效:直接应用到引擎并落盘,关掉设置窗口也不会"复活"。
func (s *settingsWindow) deleteProfile() {
p, ok := s.profileAt(s.selIndex)
if !ok {
return
}
dialog.NewConfirm("删除接入点", "确定删除「"+p.Name+"」?", func(confirmed bool) {
// 注意用 ShowConfirm:NewConfirm 只创建不显示,点「删除」会毫无反应。
dialog.ShowConfirm("删除接入点", "确定删除「"+p.Name+"」?", func(confirmed bool) {
if !confirmed {
return
}
s.cfg.RemoveModel(p.Name)
s.profileList.Refresh()
if !s.cfg.RemoveModel(p.Name) {
return
}
if s.cfg.ProfileIndex(s.cfg.Active) < 0 {
s.cfg.Active = ""
}
if len(s.cfg.Models) == 0 {
s.selIndex = -1
return
}
idx := s.selIndex
if idx >= len(s.cfg.Models) {
idx = len(s.cfg.Models) - 1
}
s.profileList.Select(idx)
s.applyNow("已删除接入点 " + p.Name)
}, s.win)
}
@@ -465,7 +475,78 @@ func (s *settingsWindow) activateProfile() {
return
}
s.cfg.Active = p.Name
s.applyNow("已切换到接入点 " + p.Name)
}
// applyNow 把当前改动立即应用到引擎并落盘。
//
// 接入点列表的增删改属于结构变化,必须当场生效:只改设置页里的副本的话,
// 关掉窗口就丢了,用户会以为"删不掉"。
func (s *settingsWindow) applyNow(notice string) {
// 配置里至少要保留一个接入点,删光后 Normalize 会补一个空白项。
emptied := len(s.cfg.Models) == 0
if err := s.app.applyAndSave(s.cfg); err != nil {
fyne.Do(func() { s.app.chat.addNotice("操作失败:" + err.Error()) })
return
}
s.syncSelection()
s.refreshList()
if emptied {
notice += "(接入点已清空,点「新增」可以再加一个)"
}
if notice != "" {
fyne.Do(func() { s.app.chat.addNotice(notice) })
}
}
// syncSelection 让列表选中项、右边表单和配置保持一致。
//
// 不能只靠 List.Select 的回调:Fyne 在 id 没变时会直接返回,
// 不会触发 OnSelected,表单就会继续显示已经删掉的接入点。
func (s *settingsWindow) syncSelection() {
if len(s.cfg.Models) == 0 {
s.selIndex = -1
s.profileList.UnselectAll()
s.clearProfileForm()
return
}
idx := s.selIndex
if idx < 0 || idx >= len(s.cfg.Models) {
idx = s.cfg.ProfileIndex(s.cfg.Active)
}
if idx < 0 || idx >= len(s.cfg.Models) {
idx = 0
}
s.selectInList(idx)
}
// refreshList 刷新列表,并按接入点数量显示/隐藏空列表提示。
func (s *settingsWindow) refreshList() {
s.profileList.Refresh()
if s.emptyHint == nil {
return
}
if len(s.cfg.Models) == 0 {
s.emptyHint.Show()
} else {
s.emptyHint.Hide()
}
}
// clearProfileForm 清空右边的表单(一个接入点都没有时用)。
func (s *settingsWindow) clearProfileForm() {
prev := s.loading
s.loading = true
s.nameEntry.SetText("")
s.baseEntry.SetText("")
s.modelEntry.SetText("")
s.keyEntry.SetText("")
s.maxEntry.SetText("")
s.tempEntry.SetText("")
s.headerEntry.SetText("")
s.loading = prev
}
// testConnection 用当前表单的值测试一次连通性。
@@ -523,6 +604,7 @@ func (s *settingsWindow) buildCaptureTab() fyne.CanvasObject {
s.maxEdge = widget.NewEntry()
s.quality = widget.NewEntry()
s.diff = widget.NewEntry()
s.sens = widget.NewEntry()
s.cooldown = widget.NewEntry()
s.stable = widget.NewEntry()
s.turns = widget.NewEntry()
@@ -532,10 +614,11 @@ func (s *settingsWindow) buildCaptureTab() fyne.CanvasObject {
entry("图片长边上限", s.maxEdge),
entry("JPEG 质量", s.quality),
entry("变化阈值", s.diff),
entry("变化灵敏度", s.sens),
entry("冷却时间 (ms)", s.cooldown),
entry("稳定帧数", s.stable),
entry("历史轮数", s.turns),
widget.NewLabel("说明:变化阈值越小越灵敏(默认 5);冷却时间决定两次自动提问的最小间隔。"),
widget.NewLabel("说明:变化阈值越小越灵敏(默认 5,基于感知哈希);变化灵敏度是细节网格的归一化平均差阈值(0~1,越小越灵敏,默认 0.01),专门用来捕捉「布局没变、只是文字/题目变了」这类更新。两者任一超阈值即触发。"),
)
saveBtn := widget.NewButton("保存并应用", func() {
@@ -634,6 +717,7 @@ func (s *settingsWindow) commitCapture() {
s.cfg.Capture.MaxEdge = atoiDefault(s.maxEdge.Text, 1280)
s.cfg.Capture.JPEGQuality = atoiDefault(s.quality.Text, 75)
s.cfg.Capture.DiffThreshold = atoiDefault(s.diff.Text, 5)
s.cfg.Capture.DiffSensitivity = atofDefault(s.sens.Text, 0.01)
s.cfg.Capture.CooldownMs = atoiDefault(s.cooldown.Text, 3000)
s.cfg.Capture.StableFrames = atoiDefault(s.stable.Text, 2)
s.cfg.Capture.HistoryTurns = atoiDefault(s.turns.Text, 6)