refactor(ui): вынести панель «Диалог» (ChatPanel) из монолита окна
Спеца 12.12: интерфейс + Nil + FormatRole в internal/ui/chat_panel.go, Fyne-реализация в internal/ui/desktop/chat_panel.go (транскрипт + композитор). Окно как chat.Channel рендерит Send/Ask/историю через Append; state_panel не трогаем.
This commit is contained in:
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
bufLimit = 200_000 // обрезка буферов панелей окна (conv/state), чтобы не расти бесконечно
|
||||
bufLimit = 200_000 // обрезка буфера панели «Состояние», чтобы не расти бесконечно
|
||||
selectedPref = "task.selected"
|
||||
splitHPref = "split.h"
|
||||
splitVPref = "split.v"
|
||||
@@ -45,10 +45,9 @@ type Window struct {
|
||||
// виджеты
|
||||
taskList *TaskListPanel
|
||||
detail *TaskDetailPanel
|
||||
convLbl *widget.Label
|
||||
chat *ChatPanel
|
||||
stateLbl *widget.Label
|
||||
logs *LogPanel
|
||||
input *widget.Entry
|
||||
|
||||
hsplit *container.Split
|
||||
vsplit *container.Split
|
||||
@@ -83,8 +82,8 @@ func (w *Window) SetOnQuit(fn func()) { w.onQuit = fn }
|
||||
func (w *Window) build() {
|
||||
w.detail = NewTaskDetailPanel()
|
||||
|
||||
w.convLbl = widget.NewLabel("Выберите задачу.")
|
||||
w.convLbl.Wrapping = fyne.TextWrapWord
|
||||
w.chat = NewChatPanel()
|
||||
w.chat.SetCommands(w.commands)
|
||||
|
||||
w.logs = NewLogPanel()
|
||||
|
||||
@@ -105,10 +104,9 @@ func (w *Window) build() {
|
||||
|
||||
// Рабочая область: детали сверху, диалог снизу (сплит 2×2 в плане).
|
||||
details := w.detail.Widget()
|
||||
convScroll := container.NewScroll(w.convLbl)
|
||||
right := container.NewVSplit(
|
||||
container.NewScroll(details),
|
||||
convScroll,
|
||||
w.chat.Transcript(),
|
||||
)
|
||||
w.vsplit = right
|
||||
|
||||
@@ -118,25 +116,7 @@ func (w *Window) build() {
|
||||
container.NewTabItem("Состояние", container.NewScroll(w.stateLbl)),
|
||||
)
|
||||
|
||||
// Ввод + кнопки команд
|
||||
w.input = widget.NewEntry()
|
||||
w.input.SetPlaceHolder("Сообщение… (Enter — отправить)")
|
||||
w.input.OnSubmitted = func(s string) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
w.input.SetText("")
|
||||
w.commands.SendText(s)
|
||||
}
|
||||
cmdBar := container.NewHBox(
|
||||
widget.NewButton("Новая", func() { w.commands.Start() }),
|
||||
widget.NewButton("Создавай", func() { w.commands.Approve() }),
|
||||
widget.NewButton("Пропустить", func() { w.commands.Skip() }),
|
||||
widget.NewButton("Отмена", func() { w.commands.Cancel() }),
|
||||
)
|
||||
inputRow := container.NewBorder(nil, nil, cmdBar, w.input, nil)
|
||||
bottom := container.NewBorder(nil, inputRow, nil, nil, bottomTabs)
|
||||
bottom := container.NewBorder(nil, w.chat.Composer(), nil, nil, bottomTabs)
|
||||
|
||||
w.hsplit = container.NewHSplit(left, right)
|
||||
|
||||
@@ -170,7 +150,7 @@ func (w *Window) selectTask(id int64) {
|
||||
t, err := w.store.GetTask(ctx, id)
|
||||
if err != nil {
|
||||
w.detail.ShowEmpty()
|
||||
w.convLbl.SetText("")
|
||||
w.chat.Clear()
|
||||
return
|
||||
}
|
||||
w.detail.ShowTask(t)
|
||||
@@ -182,7 +162,7 @@ func (w *Window) selectTask(id int64) {
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, h := range hist {
|
||||
b.WriteString(formatRole(h.Role))
|
||||
b.WriteString(ui.FormatRole(h.Role))
|
||||
b.WriteString(h.Content)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
@@ -195,21 +175,12 @@ func (w *Window) selectTask(id int64) {
|
||||
if b.Len() == 0 {
|
||||
b.WriteString("Нет сообщений. /start — начать задачу.")
|
||||
}
|
||||
w.convLbl.SetText(b.String())
|
||||
w.chat.SetTranscript(b.String())
|
||||
|
||||
// Состояние: сброс к снимку задач.
|
||||
w.refreshState(ctx, id)
|
||||
}
|
||||
|
||||
func formatRole(role string) string {
|
||||
switch role {
|
||||
case "user":
|
||||
return "👤 "
|
||||
default:
|
||||
return "🤖 "
|
||||
}
|
||||
}
|
||||
|
||||
// refreshState — снимок «Состояния» выбранной задачи (агенты + трейсы).
|
||||
func (w *Window) refreshState(ctx context.Context, id int64) {
|
||||
traces, err := w.store.GetTraces(ctx, id)
|
||||
@@ -242,15 +213,6 @@ func (w *Window) refreshList() {
|
||||
w.taskList.Select(w.selected)
|
||||
}
|
||||
|
||||
// appendConv добавляет строку в диалог (буфер ограничен).
|
||||
func (w *Window) appendConv(text string) {
|
||||
s := w.convLbl.Text + text + "\n\n"
|
||||
if len(s) > bufLimit {
|
||||
s = s[len(s)-bufLimit:]
|
||||
}
|
||||
w.convLbl.SetText(s)
|
||||
}
|
||||
|
||||
// appendLog добавляет строку в панель «Логи» (делегирует в LogPanel).
|
||||
func (w *Window) appendLog(text string) {
|
||||
w.logs.Append(text)
|
||||
@@ -306,7 +268,7 @@ func (w *Window) Send(_ context.Context, _ chat.Address, m chat.Message) error {
|
||||
}
|
||||
text = sb.String()
|
||||
}
|
||||
fyne.Do(func() { w.appendConv("🤖 " + text) })
|
||||
fyne.Do(func() { w.chat.Append("🤖 " + text) })
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -316,7 +278,7 @@ func (w *Window) Ask(_ context.Context, _ chat.Address, m chat.Message) error {
|
||||
for _, opt := range m.Options {
|
||||
text += "\n" + opt.Label
|
||||
}
|
||||
fyne.Do(func() { w.appendConv("❓ " + text) })
|
||||
fyne.Do(func() { w.chat.Append("❓ " + text) })
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -370,7 +332,7 @@ func (w *Window) OnTaskStatusChanged(e events.TaskStatusChanged) {
|
||||
func (w *Window) OnHistoryAppended(e events.HistoryAppended) {
|
||||
fyne.Do(func() {
|
||||
if w.selected == e.TaskID {
|
||||
w.appendConv(formatRole(e.Role) + e.Content)
|
||||
w.chat.Append(ui.FormatRole(e.Role) + e.Content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user