更新代码

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

+91 -5
View File
@@ -25,6 +25,7 @@ import (
"viewsagent/internal/capture"
"viewsagent/internal/config"
"viewsagent/internal/llm"
"viewsagent/internal/logging"
"viewsagent/internal/win32"
)
@@ -44,10 +45,12 @@ type App struct {
modelSel *widget.Select
regionLbl *widget.Label
statusLbl *widget.Label
frameBtn *widget.Button
settings *settingsWindow
card *floatCard
overlay *overlay
regionFrm *regionFrame
logs *logWindow
hotkeyStop func()
// 流式文本合批,避免每个 delta 都触发一次布局
@@ -62,10 +65,13 @@ type App struct {
lastCardSync time.Time
quietModelSel bool
// frameBtnShown 记录取景框按钮当前的图标状态,避免重复刷新。
frameBtnShown bool
}
// Run 启动整个桌面应用。
func Run() error {
// Run 启动整个桌面应用。logs 是程序自己的日志缓冲,
// 界面上的「日志」按钮直接读它展示。
func Run(logs *logging.Buffer) error {
store, err := config.NewStore()
if err != nil {
return err
@@ -79,7 +85,11 @@ func Run() error {
eng := agent.New(cfg, store)
a := &App{fyne: fa, eng: eng}
a.logs = newLogWindow(fa, logs, store.Dir())
a.build()
// 取景框上的「确定 / 取消」直接改监视区域。
a.regionFrm.onApply = a.onFrameApply
a.regionFrm.onCancel = a.onFrameCancel
eng.SetSink(a)
a.startHotkeys()
a.maybeAutoShot()
@@ -88,6 +98,9 @@ func Run() error {
if a.hotkeyStop != nil {
a.hotkeyStop()
}
a.logs.close()
// 取景框是原生窗口,不随 Fyne 窗口一起消失,
// 必须在这里显式销毁,否则屏幕上会留下一个关不掉的红色线框。
a.regionFrm.close()
eng.Close()
}()
@@ -159,6 +172,10 @@ func (a *App) build() {
a.win = a.fyne.NewWindow(mainTitle)
a.win.Resize(fyne.NewSize(520, 780))
a.win.CenterOnScreen()
// 主窗体是 master:点它的关闭按钮就直接退出整个程序。
// 否则 Fyne 只会在最后一个窗口关闭时才退出,卡片/设置/日志窗口
// 还活着,程序会变成后台僵尸,取景框的红色线框也留在屏幕上。
a.win.SetMaster()
a.chat = newChatView()
a.card = newFloatCard(a.fyne)
@@ -175,12 +192,14 @@ func (a *App) build() {
cardBtn := widget.NewButtonWithIcon("", theme.InfoIcon(), a.toggleCard)
cardBtn.Importance = widget.LowImportance
logBtn := widget.NewButtonWithIcon("", theme.DocumentIcon(), a.showLogs)
logBtn.Importance = widget.LowImportance
settingsBtn := widget.NewButtonWithIcon("", theme.SettingsIcon(), a.openSettings)
settingsBtn.Importance = widget.LowImportance
toolbar := container.NewHBox(
widget.NewLabel("接入点"), a.modelSel,
layout.NewSpacer(), cardBtn, settingsBtn,
layout.NewSpacer(), cardBtn, logBtn, settingsBtn,
)
a.regionLbl = widget.NewLabel("未框选区域")
@@ -188,10 +207,12 @@ func (a *App) build() {
selectBtn := widget.NewButtonWithIcon("框选", theme.VisibilityOffIcon(), a.startSelection)
previewBtn := widget.NewButtonWithIcon("预览", theme.SearchIcon(), a.showPreview)
previewBtn.Importance = widget.LowImportance
a.frameBtn = widget.NewButtonWithIcon("取景框", theme.VisibilityIcon(), a.toggleFrame)
a.frameBtn.Importance = widget.LowImportance
a.watchBtn = widget.NewButtonWithIcon("开始监听", theme.MediaPlayIcon(), a.toggleWatch)
a.watchBtn.Importance = widget.HighImportance
regionRow := container.NewHBox(a.regionLbl, layout.NewSpacer(), previewBtn, selectBtn, a.watchBtn)
regionRow := container.NewHBox(a.regionLbl, layout.NewSpacer(), previewBtn, selectBtn, a.frameBtn, a.watchBtn)
a.entry = widget.NewMultiLineEntry()
a.entry.SetPlaceHolder("输入你的要求,例如:请帮我做练习题,实时给出答案")
@@ -289,6 +310,61 @@ func (a *App) openSettings() {
a.settings.show()
}
// showLogs 打开运行日志窗口。
func (a *App) showLogs() {
if a.logs == nil {
return
}
a.logs.show()
}
// toggleFrame 显示或隐藏屏幕上的取景框。
func (a *App) toggleFrame() {
if a.regionFrm == nil {
return
}
if a.regionFrm.toggle() {
a.chat.addNotice("已显示取景框:拖动边框可调整范围,右上角有确定/取消")
} else {
a.chat.addNotice("已隐藏取景框")
}
a.updateFrameBtn()
}
// updateFrameBtn 同步取景框按钮的图标与可用状态。
func (a *App) updateFrameBtn() {
if a.frameBtn == nil {
return
}
visible := a.regionFrm != nil && a.regionFrm.visible()
if visible == a.frameBtnShown {
return
}
a.frameBtnShown = visible
if visible {
a.frameBtn.SetIcon(theme.VisibilityIcon())
} else {
a.frameBtn.SetIcon(theme.VisibilityOffIcon())
}
}
// onFrameApply 取景框上点了确定:把拖出来的范围应用成监视区域。
func (a *App) onFrameApply(r capture.Region) {
if err := a.eng.SetRegion(r); err != nil {
fyne.Do(func() { a.chat.addNotice(err.Error()) })
return
}
fyne.Do(func() { a.chat.addNotice(fmt.Sprintf("已应用监视区域 %s", r)) })
}
// onFrameCancel 取景框上点了取消:红框已经隐藏,这里只提示一句。
func (a *App) onFrameCancel() {
fyne.Do(func() {
a.chat.addNotice("已取消取景框调整,监视区域保持不变")
a.updateFrameBtn()
})
}
func (a *App) clearChat() {
a.eng.ResetHistory()
a.chat.clear()
@@ -358,7 +434,8 @@ func (a *App) startSelection() {
return
}
vd := win32.VirtualScreen()
a.overlay = newOverlay(a.fyne, shot, vd, a.onRegionSelected, a.onRegionCancel)
// 把上一次的选区带进去,打开就能直接移动或调整边界。
a.overlay = newOverlay(a.fyne, shot, vd, a.eng.Region(), a.onRegionSelected, a.onRegionCancel)
a.overlay.show()
}
@@ -372,6 +449,8 @@ func (a *App) onRegionSelected(r capture.Region) {
}
a.card.setRegion(r)
a.regionFrm.resume(r)
// 重新框选后把红框显示出来(之前点过取消的话,这里恢复)。
a.regionFrm.show()
a.chat.addNotice(fmt.Sprintf("已框选监视区域 %s", r))
}
@@ -541,12 +620,19 @@ func (a *App) applyState(st agent.State) {
if st.Region.Valid() {
a.regionLbl.SetText(fmt.Sprintf("区域 %d × %d", st.Region.W, st.Region.H))
if a.frameBtn != nil {
a.frameBtn.Enable()
}
} else {
a.regionLbl.SetText("未框选区域")
if a.frameBtn != nil {
a.frameBtn.Disable()
}
}
a.card.setRegion(st.Region)
// 屏幕上的红框跟着选区走,始终提示当前盯的是哪一块。
a.regionFrm.apply(st.Region)
a.updateFrameBtn()
if st.Busy {
a.sendBtn.Disable()