//go:build cgo package desktop import ( "strings" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" "github.com/kamelion/ratatoskr-go/internal/ui" ) // chatLimit — обрезка буфера транскрипта диалога (спец 12.12). const chatLimit = 200_000 // ChatPanel — Fyne-реализация ui.ChatPanel (спец 12.8, 12.12): транскрипт // диалога + композитор (поле ввода и кнопки команд). type ChatPanel struct { convLbl *widget.Label input *widget.Entry commands *ui.Commands retryBtn *widget.Button // «Перезапустить» — полный аналог /retry N boundTask int64 // >0 — вкладка привязана к задаче №boundTask } // NewChatPanel создаёт ChatPanel. func NewChatPanel() *ChatPanel { p := &ChatPanel{} p.convLbl = widget.NewLabel("Выберите задачу.") p.convLbl.Wrapping = fyne.TextWrapWord p.input = widget.NewEntry() p.input.SetPlaceHolder("Сообщение… (Enter — отправить)") p.retryBtn = widget.NewButton("Перезапустить", p.retryTask) p.retryBtn.Disable() // активируется при привязке вкладки к задаче (SetBoundTask) return p } // Transcript возвращает виджет транскрипта (Scroll) для встраивания в окно. func (p *ChatPanel) Transcript() fyne.CanvasObject { return container.NewScroll(p.convLbl) } // Composer возвращает виджет композитора (ввод + кнопки команд). func (p *ChatPanel) Composer() fyne.CanvasObject { cmdBar := container.NewHBox( widget.NewButton("Новая", func() { if p.commands != nil { p.commands.Start() } }), widget.NewButton("Создавай", func() { if p.commands != nil { p.commands.Approve() } }), widget.NewButton("Пропустить", func() { if p.commands != nil { p.commands.Skip() } }), p.retryBtn, widget.NewButton("Отмена", func() { if p.commands != nil { p.commands.Cancel() } }), ) return container.NewBorder(nil, nil, cmdBar, nil, p.input) } // retryTask — колбэк кнопки «Перезапустить»: отправляет "/retry N" тем же // путём, что и ввод пользователя (Commands → Router → Core), т.е. серверная // логика полностью совпадает с Telegram-командой /retry N. func (p *ChatPanel) retryTask() { if p.commands != nil { p.commands.Retry(p.boundTask) } } // SetBoundTask включает кнопку «Перезапустить», когда вкладка привязана к // задаче (taskID > 0); для свободной вкладки кнопка неактивна. func (p *ChatPanel) SetBoundTask(taskID int64) { p.boundTask = taskID if taskID > 0 { p.retryBtn.Enable() } else { p.retryBtn.Disable() } } // SetTranscript заменяет содержимое диалога (спец 12.12). func (p *ChatPanel) SetTranscript(text string) { p.convLbl.SetText(text) } // Append добавляет строку в диалог (буфер ограничен). func (p *ChatPanel) Append(text string) { s := p.convLbl.Text + text + "\n\n" if len(s) > chatLimit { s = s[len(s)-chatLimit:] } p.convLbl.SetText(s) } // Clear очищает диалог. func (p *ChatPanel) Clear() { p.convLbl.SetText("") } // SetCommands подключает команды к композитору. func (p *ChatPanel) SetCommands(c *ui.Commands) { p.commands = c p.input.OnSubmitted = func(s string) { s = strings.TrimSpace(s) if s == "" { return } p.input.SetText("") if c != nil { c.SendText(s) } } } // compile-time проверка реализации контракта. var _ ui.ChatPanel = (*ChatPanel)(nil)