refactor(ui): вынести панель «Состояние» (StatePanel) из монолита окна
Some checks failed
CI / test (pull_request) Failing after 1m17s
CI / build-and-package (amd64, linux) (pull_request) Failing after 1m2s
CI / build-and-package (amd64, windows) (pull_request) Successful in 28s

Спеца 12.13: интерфейс + Nil + чистые хелперы (StateText/TraceLine/
ActivityLine/TaskStatusText/StatusBadge/Itoa) в internal/ui/state_panel.go,
Fyne-реализация в internal/ui/desktop/state_panel.go. window.go — чистая
компоновка: все 5 панелей вынесены как контракты.
This commit is contained in:
ki.sagidullin
2026-08-20 23:54:27 +05:00
parent 5c93642cf8
commit 8ba1a8aa00
5 changed files with 221 additions and 74 deletions

View File

@@ -19,12 +19,10 @@ import (
"github.com/kamelion/ratatoskr-go/internal/chat"
"github.com/kamelion/ratatoskr-go/internal/events"
"github.com/kamelion/ratatoskr-go/internal/model"
"github.com/kamelion/ratatoskr-go/internal/ui"
)
const (
bufLimit = 200_000 // обрезка буфера панели «Состояние», чтобы не расти бесконечно
selectedPref = "task.selected"
splitHPref = "split.h"
splitVPref = "split.v"
@@ -46,7 +44,7 @@ type Window struct {
taskList *TaskListPanel
detail *TaskDetailPanel
chat *ChatPanel
stateLbl *widget.Label
state *StatePanel
logs *LogPanel
hsplit *container.Split
@@ -85,10 +83,9 @@ func (w *Window) build() {
w.chat = NewChatPanel()
w.chat.SetCommands(w.commands)
w.logs = NewLogPanel()
w.state = NewStatePanel()
w.stateLbl = widget.NewLabel("")
w.stateLbl.Wrapping = fyne.TextWrapWord
w.logs = NewLogPanel()
// Список задач (слева)
w.taskList = NewTaskListPanel()
@@ -113,7 +110,7 @@ func (w *Window) build() {
// Нижняя панель: вкладки «Логи» + «Состояние».
bottomTabs := container.NewAppTabs(
container.NewTabItem("Логи", w.logs.Widget()),
container.NewTabItem("Состояние", container.NewScroll(w.stateLbl)),
container.NewTabItem("Состояние", w.state.Widget()),
)
bottom := container.NewBorder(nil, w.chat.Composer(), nil, nil, bottomTabs)
@@ -187,18 +184,7 @@ func (w *Window) refreshState(ctx context.Context, id int64) {
if err != nil {
return
}
var b strings.Builder
for _, tr := range traces {
b.WriteString(strings.ToUpper(tr.Agent) + ": " + string(tr.Status))
if tr.SessionID != "" {
b.WriteString(" (session " + tr.SessionID + ")")
}
b.WriteString("\n")
}
if b.Len() == 0 {
b.WriteString("Нет активных агентов.")
}
w.stateLbl.SetText(b.String())
w.state.SetText(ui.StateText(traces))
}
// refreshList перечитывает список задач из Store (снимок).
@@ -218,15 +204,6 @@ func (w *Window) appendLog(text string) {
w.logs.Append(text)
}
// appendState добавляет строку в панель «Состояние».
func (w *Window) appendState(text string) {
s := w.stateLbl.Text + text + "\n"
if len(s) > bufLimit {
s = s[len(s)-bufLimit:]
}
w.stateLbl.SetText(s)
}
// submitText отправляет ввод пользователя в Router.
func (w *Window) submitText(text string) {
if w.handler != nil {
@@ -325,7 +302,7 @@ func (w *Window) OnTaskStatusChanged(e events.TaskStatusChanged) {
if w.selected == e.ID {
w.selectTask(e.ID)
}
w.appendState(taskStatusLine(e))
w.state.Append(ui.TaskStatusText(e))
})
}
@@ -339,59 +316,16 @@ func (w *Window) OnHistoryAppended(e events.HistoryAppended) {
func (w *Window) OnTraceAppended(e events.TraceAppended) {
fyne.Do(func() {
w.appendState(strings.ToUpper(e.Agent) + ": " + string(e.Status))
w.state.Append(ui.TraceLine(e.Agent, e.Status))
})
}
func (w *Window) OnAgentActivity(e events.AgentActivity) {
fyne.Do(func() {
w.appendState(strings.ToUpper(e.Agent) + " → " + e.Stage)
w.state.Append(ui.ActivityLine(e.Agent, e.Stage))
})
}
func (w *Window) OnLog(e events.LogLine) {
w.appendLog(e.Text)
}
func taskStatusLine(e events.TaskStatusChanged) string {
return statusBadge(e.To) + " #" + itoa(e.ID) + ": " + string(e.From) + " → " + string(e.To)
}
func statusBadge(s model.Status) string {
switch s {
case model.StatusSuccess:
return "✅"
case model.StatusFailed, model.StatusAborted:
return "❌"
case model.StatusRunning, model.StatusCollecting:
return "⏳"
case model.StatusReady, model.StatusApproved:
return "🟡"
case model.StatusCancelled:
return "🚫"
default:
return "•"
}
}
func itoa(v int64) string {
if v == 0 {
return "0"
}
neg := v < 0
if neg {
v = -v
}
var b [24]byte
i := len(b)
for v > 0 {
i--
b[i] = byte('0' + v%10)
v /= 10
}
if neg {
i--
b[i] = '-'
}
return string(b[i:])
}