regex/prettyFormat.go

196 lines
6.0 KiB
Go

// This file contains functions for formatting and printing chat logs to the terminal.
// These functions operate exclusively on the in-memory protobuf data structures.
// They do NOT access the filesystem; all content (from content files, snippets, etc.)
// is expected to have been loaded into the 'Content' fields of the protobuf messages
// before these functions are called.
package main
import (
"fmt"
"path/filepath"
"strings"
"go.wit.com/lib/protobuf/chatpb"
)
const termWidth = 100 // The target width for the formatted output boxes.
// prettyFormatChat is the main entry point to print a detailed view of a Chat topic.
func prettyFormatChat(chat *chatpb.Chat) {
fmt.Printf("\n========================================================\n")
fmt.Printf("== Chat Topic: %s (UUID: %s)\n", chat.GetChatName(), chat.GetUuid())
fmt.Printf("========================================================\n\n")
for _, entry := range chat.GetEntries() {
author := entry.GetFrom().String()
var formattedTime string
if ctime := entry.GetCtime(); ctime != nil {
t := ctime.AsTime()
formattedTime = t.Format("2006-01-02 15:04:05")
} else {
formattedTime = "No Timestamp"
}
// Use the in-memory Content field directly.
content := entry.GetContent()
if content != "" {
printContent(author, formattedTime, content)
}
if table := entry.GetTable(); table != nil {
printTable(table)
}
for _, toolCall := range entry.GetToolCalls() {
printToolCallBox(toolCall)
}
if snippets := entry.GetSnippets(); snippets != nil {
for _, snippet := range snippets {
printCodeSnippet(snippet)
}
}
fmt.Println()
}
}
// printContent handles the wrapping for the main conversational text.
func printContent(author, timestamp, content string) {
// Right-align USER messages, left-align GEMINI messages.
if author == "USER" {
printRightAligned(author, timestamp, content)
} else {
printLeftAligned(author, timestamp, content)
}
}
func printLeftAligned(author, timestamp, content string) {
prefix := fmt.Sprintf("✦ %s (%s):", author, timestamp)
fmt.Println(prefix)
indent := "\t"
contentWidth := termWidth - 8 // 8 spaces for a standard tab
paragraphs := strings.Split(content, "\n")
for _, paragraph := range paragraphs {
words := strings.Fields(paragraph)
if len(words) == 0 {
fmt.Println() // Preserve paragraph breaks
continue
}
currentLine := indent + words[0]
for _, word := range words[1:] {
if len(currentLine)+1+len(word) <= contentWidth {
currentLine += " " + word
} else {
fmt.Println(currentLine)
currentLine = indent + word
}
}
fmt.Println(currentLine)
}
}
func printRightAligned(author, timestamp, content string) {
prefix := fmt.Sprintf(":(%s) %s ✦", timestamp, author)
// The available width for the text.
contentWidth := termWidth - 8 // Leave a tab's worth of margin on the left
lines := strings.Split(content, "\n")
var formattedLines []string
// First, wrap all the text into lines of the correct width.
for _, line := range lines {
words := strings.Fields(line)
if len(words) == 0 {
formattedLines = append(formattedLines, "")
continue
}
currentLine := words[0]
for _, word := range words[1:] {
if len(currentLine)+1+len(word) <= contentWidth {
currentLine += " " + word
} else {
formattedLines = append(formattedLines, currentLine)
currentLine = word
}
}
formattedLines = append(formattedLines, currentLine)
}
// Now, print the formatted lines with the correct right alignment.
// The prefix is printed last and right-aligned.
for i, line := range formattedLines {
padding := termWidth - len(line)
if i == len(formattedLines)-1 { // Last line gets the prefix
padding = termWidth - len(line) - len(prefix)
}
if padding < 0 {
padding = 0
}
fmt.Printf("%s%s\n", strings.Repeat(" ", padding), line)
}
fmt.Printf("%*s\n", termWidth, prefix)
}
func printTable(table *chatpb.Table) {
if table == nil || len(table.GetRows()) == 0 {
return
}
fmt.Println("┌─[ Table Data ]──────────────────────────────────────────")
for _, row := range table.GetRows() {
fmt.Printf("│ %s\n", strings.Join(row.GetFields(), " │ "))
}
fmt.Printf("└─────────────────────────────────────────────────────────\n\n")
}
func printCodeSnippet(snippet *chatpb.CodeSnippet) {
// Use the in-memory Content field directly.
code := snippet.GetContent()
language := filepath.Base(snippet.GetFilename()) // Still useful for display
fmt.Printf("┌─[ Code Snippet: %s ]──────────────────────────────────\n", language)
for _, line := range strings.Split(strings.TrimSpace(code), "\n") {
fmt.Printf("│ %s\n", line)
}
fmt.Printf("└─────────────────────────────────────────────────────────\n\n")
}
func printToolCallBox(tc *chatpb.ToolCall) {
boxWidth := termWidth - 2
fmt.Printf(" ╭%s╮\n", strings.Repeat("─", boxWidth))
header := fmt.Sprintf(" ✔ %s %s (%s)", tc.GetName(), tc.GetInput(), tc.GetDescription())
printWrappedLine(header, boxWidth)
printEmptyLine(boxWidth)
if stdout := tc.GetOutputStdout(); stdout != "" {
for _, line := range strings.Split(stdout, "\n") {
printWrappedLine(" "+line, boxWidth)
}
}
if stderr := tc.GetOutputStderr(); stderr != "" {
for _, line := range strings.Split(stderr, "\n") {
printWrappedLine(" "+line, boxWidth)
}
}
printEmptyLine(boxWidth)
fmt.Printf(" ╰%s╯\n", strings.Repeat("─", boxWidth))
}
func printWrappedLine(text string, width int) {
if len(text) == 0 {
printEmptyLine(width)
return
}
for len(text) > width {
fmt.Printf(" │ %-*s │\n", width, text[:width])
text = text[width:]
}
fmt.Printf(" │ %-*s │\n", width, text)
}
func printEmptyLine(width int) {
fmt.Printf(" │ %*s │\n", width, "")
}