in pkg/ui/terminal.go [80:224]
func (u *TerminalUI) DocumentChanged(doc *Document, block Block) {
blockIndex := doc.IndexOf(block)
if blockIndex != doc.NumBlocks()-1 {
klog.Warningf("update to blocks other than the last block is not supported in terminal mode")
return
}
if u.currentBlock != block {
u.currentBlock = block
if u.currentBlockText != "" {
fmt.Printf("\n")
}
u.currentBlockText = ""
}
text := ""
streaming := false
var styleOptions []StyleOption
switch block := block.(type) {
case *ErrorBlock:
styleOptions = append(styleOptions, Foreground(ColorRed))
text = block.Text()
case *FunctionCallRequestBlock:
styleOptions = append(styleOptions, Foreground(ColorGreen))
text = block.Text()
case *AgentTextBlock:
styleOptions = append(styleOptions, RenderMarkdown())
if block.Color != "" {
styleOptions = append(styleOptions, Foreground(block.Color))
}
text = block.Text()
streaming = block.Streaming()
case *InputTextBlock:
fmt.Print("\n>>> ")
var reader *bufio.Reader
if u.useTTYForInput {
// Stdin was used for piped data, open the terminal directly
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
block.Observable().Set("", err)
return
}
defer tty.Close()
reader = bufio.NewReader(tty)
} else {
reader = bufio.NewReader(os.Stdin)
}
query, err := reader.ReadString('\n')
if err != nil {
block.Observable().Set("", err)
} else {
block.Observable().Set(query, nil)
}
return
case *InputOptionBlock:
fmt.Printf("%s\n", block.Prompt)
var reader *bufio.Reader
if u.useTTYForInput {
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
block.Observable().Set("", err)
return
}
defer tty.Close()
reader = bufio.NewReader(tty)
} else {
reader = bufio.NewReader(os.Stdin)
}
for {
fmt.Print(" Enter your choice (number): ")
var response string
response, err := reader.ReadString('\n')
if err != nil {
block.Observable().Set("", err)
break
}
choice := strings.TrimSpace(response)
if slices.Contains(block.Options, choice) {
block.Observable().Set(choice, nil)
break
}
// If not returned, the choice was invalid
fmt.Printf(" Invalid choice. Please enter one of: %s\n", strings.Join(block.Options, ", "))
continue
}
return
}
computedStyle := &style{}
for _, opt := range styleOptions {
opt(computedStyle)
}
if streaming && computedStyle.renderMarkdown {
// Because we can't render markdown incrementally,
// we "hold back" the text if we are streaming markdown until streaming is done
text = ""
}
printText := text
if computedStyle.renderMarkdown && printText != "" {
out, err := u.markdownRenderer.Render(printText)
if err != nil {
klog.Errorf("Error rendering markdown: %v", err)
} else {
printText = out
}
}
if u.currentBlockText != "" {
if strings.HasPrefix(text, u.currentBlockText) {
printText = strings.TrimPrefix(printText, u.currentBlockText)
} else {
klog.Warningf("text did not match text already rendered; text %q; currentBlockText %q", text, u.currentBlockText)
}
}
u.currentBlockText = text
reset := ""
switch computedStyle.foreground {
case ColorRed:
fmt.Printf("\033[31m")
reset += "\033[0m"
case ColorGreen:
fmt.Printf("\033[32m")
reset += "\033[0m"
case ColorWhite:
fmt.Printf("\033[37m")
reset += "\033[0m"
case "":
default:
klog.Info("foreground color not supported by TerminalUI", "color", computedStyle.foreground)
}
fmt.Printf("%s%s", printText, reset)
}