refactor(ui): вынести панель «Логи» (LogPanel) из монолита окна

Спеца 12.8-12.9: интерфейс + Nil в internal/ui/log_panel.go, Fyne-реализация
в internal/ui/desktop/log_panel.go, окно использует контракт вместо logsLbl.
This commit is contained in:
ki.sagidullin
2026-08-20 23:26:18 +05:00
parent 60a6966a95
commit fdd73cab61
5 changed files with 122 additions and 16 deletions

View File

@@ -0,0 +1,49 @@
//go:build cgo
package desktop
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/kamelion/ratatoskr-go/internal/ui"
)
// LogPanel — Fyne-реализация ui.LogPanel (спец 12.8, 12.9): поток системных логов.
//
// Внутри — widget.Label с ограниченным буфером (maxLogLen); Append обрезает
// строку до лимита. Вызовы Append приходят из горутин Hub, поэтому обновление
// виджета выполняется через fyne.Do (на потоке Fyne).
type LogPanel struct {
label *widget.Label
}
// maxLogLen — обрезка буфера панели «Логи», чтобы не расти бесконечно.
const maxLogLen = 200_000
// NewLogPanel создаёт LogPanel.
func NewLogPanel() *LogPanel {
l := widget.NewLabel("")
l.Wrapping = fyne.TextWrapWord
return &LogPanel{label: l}
}
// Widget возвращает контейнер панели для встраивания в окно (таб «Логи»).
func (p *LogPanel) Widget() fyne.CanvasObject {
return container.NewScroll(p.label)
}
// Append добавляет строку лога (спец 12.9), с ограничением буфера.
func (p *LogPanel) Append(text string) {
fyne.Do(func() {
s := p.label.Text + text + "\n"
if len(s) > maxLogLen {
s = s[len(s)-maxLogLen:]
}
p.label.SetText(s)
})
}
// compile-time проверка реализации контракта.
var _ ui.LogPanel = (*LogPanel)(nil)

View File

@@ -26,7 +26,7 @@ import (
)
const (
maxLogLen = 200_000 // обрезка буферов панелей, чтобы не расти бесконечно
bufLimit = 200_000 // обрезка буферов панелей окна (conv/state), чтобы не расти бесконечно
selectedPref = "task.selected"
splitHPref = "split.h"
splitVPref = "split.v"
@@ -52,8 +52,8 @@ type Window struct {
statusLbl *widget.Label
detailLbl *widget.Label
convLbl *widget.Label
logsLbl *widget.Label
stateLbl *widget.Label
logs *LogPanel
input *widget.Entry
hsplit *container.Split
@@ -97,8 +97,7 @@ func (w *Window) build() {
w.convLbl = widget.NewLabel("Выберите задачу.")
w.convLbl.Wrapping = fyne.TextWrapWord
w.logsLbl = widget.NewLabel("")
w.logsLbl.Wrapping = fyne.TextWrapWord
w.logs = NewLogPanel()
w.stateLbl = widget.NewLabel("")
w.stateLbl.Wrapping = fyne.TextWrapWord
@@ -149,7 +148,7 @@ func (w *Window) build() {
// Нижняя панель: вкладки «Логи» + «Состояние».
bottomTabs := container.NewAppTabs(
container.NewTabItem("Логи", container.NewScroll(w.logsLbl)),
container.NewTabItem("Логи", w.logs.Widget()),
container.NewTabItem("Состояние", container.NewScroll(w.stateLbl)),
)
@@ -303,26 +302,22 @@ func (w *Window) refreshList() {
// appendConv добавляет строку в диалог (буфер ограничен).
func (w *Window) appendConv(text string) {
s := w.convLbl.Text + text + "\n\n"
if len(s) > maxLogLen {
s = s[len(s)-maxLogLen:]
if len(s) > bufLimit {
s = s[len(s)-bufLimit:]
}
w.convLbl.SetText(s)
}
// appendLog добавляет строку в панель «Логи».
// appendLog добавляет строку в панель «Логи» (делегирует в LogPanel).
func (w *Window) appendLog(text string) {
s := w.logsLbl.Text + text + "\n"
if len(s) > maxLogLen {
s = s[len(s)-maxLogLen:]
}
w.logsLbl.SetText(s)
w.logs.Append(text)
}
// appendState добавляет строку в панель «Состояние».
func (w *Window) appendState(text string) {
s := w.stateLbl.Text + text + "\n"
if len(s) > maxLogLen {
s = s[len(s)-maxLogLen:]
if len(s) > bufLimit {
s = s[len(s)-bufLimit:]
}
w.stateLbl.SetText(s)
}
@@ -450,7 +445,7 @@ func (w *Window) OnAgentActivity(e events.AgentActivity) {
}
func (w *Window) OnLog(e events.LogLine) {
fyne.Do(func() { w.appendLog(e.Text) })
w.appendLog(e.Text)
}
func taskStatusLine(e events.TaskStatusChanged) string {