77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// askRequest kommt vom question-Tool über Model.askCh. Anders als eine
|
|
// Permission-Rückfrage (bool, Tastendruck) ist die Antwort hier Freitext —
|
|
// die Antwort läuft deshalb über das normale Eingabefeld: submit() prüft
|
|
// zuerst, ob eine Frage offen ist, und behandelt den getippten Text als
|
|
// Antwort statt als neue Chat-Nachricht/Slash-Command (siehe submit in
|
|
// model.go).
|
|
type askRequest struct {
|
|
question string
|
|
options []string
|
|
resp chan string
|
|
}
|
|
|
|
type askRequestMsg struct{ req askRequest }
|
|
|
|
func listenAskRequests(ch <-chan askRequest) tea.Cmd {
|
|
return func() tea.Msg {
|
|
req, ok := <-ch
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return askRequestMsg{req: req}
|
|
}
|
|
}
|
|
|
|
// askUser ist der Hook, den loop.AskUser aufruft (siehe New()). Läuft auf
|
|
// dem Goroutine des Backend-Loops, nicht dem der TUI.
|
|
func (m *Model) askUser(ctx context.Context, question string, options []string) (string, error) {
|
|
resp := make(chan string, 1)
|
|
req := askRequest{question: question, options: options, resp: resp}
|
|
|
|
select {
|
|
case m.askCh <- req:
|
|
case <-ctx.Done():
|
|
return "", ctx.Err()
|
|
}
|
|
|
|
select {
|
|
case answer := <-resp:
|
|
return answer, nil
|
|
case <-ctx.Done():
|
|
return "", ctx.Err()
|
|
}
|
|
}
|
|
|
|
// answerQuestion schließt die offene Frage ab und schickt answer an den
|
|
// wartenden Loop-Goroutine zurück. resp ist mit Cap 1 gepuffert, der Send
|
|
// blockiert also nie. Die Bestätigung im Transkript kommt bewusst nicht von
|
|
// hier, sondern aus dem regulären ToolCallOutput-Event (renderToolResult,
|
|
// builtin.QuestionResult) — sonst erschiene die Antwort doppelt.
|
|
func (m *Model) answerQuestion(answer string) {
|
|
req := m.askReq
|
|
m.askReq = nil
|
|
m.textarea.Placeholder = defaultInputPlaceholder
|
|
req.resp <- answer
|
|
}
|
|
|
|
// renderAskRequest baut den Transkript-Block für eine offene Frage — wie
|
|
// bei Permissions kein Vollbild-Overlay, sondern ein Eintrag im normalen
|
|
// Nachrichtenfluss (siehe renderPermissionRequest).
|
|
func renderAskRequest(question string, options []string) string {
|
|
var b strings.Builder
|
|
b.WriteString("❓ Rückfrage: " + question)
|
|
if len(options) > 0 {
|
|
b.WriteString("\nVorschläge: " + strings.Join(options, " · "))
|
|
}
|
|
b.WriteString("\n\nAntwort eingeben und Enter drücken.")
|
|
return styleAskBox.Render(b.String())
|
|
}
|