From df4e63ea75692a9885338a5eb2b7454e9c049716 Mon Sep 17 00:00:00 2001 From: Castor Gemini Date: Fri, 22 Aug 2025 03:19:10 -0500 Subject: [PATCH] feat(playback): Implement detailed view for showChat - Integrate the rich formatting logic into the 'showChat' function. - When 'playback' is called with a UUID, it now prints a full, detailed transcript of that specific chat topic, including content from external files, tool calls, and code snippets. --- doPlayback.go | 133 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 11 deletions(-) diff --git a/doPlayback.go b/doPlayback.go index 94fd82d..57d99cc 100644 --- a/doPlayback.go +++ b/doPlayback.go @@ -2,34 +2,35 @@ package main import ( "fmt" + "os" + "path/filepath" + "strings" + "go.wit.com/lib/protobuf/chatpb" "go.wit.com/log" ) +const termWidth = 120 // The target width for the formatted output boxes. + func doPlayback() { if argv.Playback.Uuid != "" { showChat(argv.Playback.Uuid) return } + log.Infof("Found %d chat topic(s) in the log.", len(me.chats.GetChats())) fmt.Println("-------------------------------------------------") - // Iterate through the top-level Chat messages, which are now named groups. for _, chat := range me.chats.GetChats() { - - // Get the number of entries in the chat. entryCount := len(chat.GetEntries()) - - // Get the timestamp of the first entry to represent the chat's start time. var formattedTime string - if entryCount > 0 && chat.GetEntries()[0].GetCtime() != nil { - t := chat.GetEntries()[0].GetCtime().AsTime() - formattedTime = t.Format("2006-01-02 15:04:05") // YYYY-MM-DD HH:MM:SS + if ctime := chat.GetCtime(); ctime != nil { + t := ctime.AsTime() + formattedTime = t.Format("2006-01-02 15:04:05") } else { formattedTime = "No Timestamp" } - // Print the formatted one-line summary. fmt.Printf("Topic: %-25s | Entries: %-4d | Started: %s | UUID: %s\n", chat.GetChatName(), entryCount, @@ -46,6 +47,116 @@ func showChat(uuid string) { log.Info("unknown uuid", uuid) return } - log.Info("uuid was found ok", uuid) - // TODO: show the chat entries here + + fmt.Printf("\n========================================================\n") + fmt.Printf("== Chat Topic: %s (UUID: %s)\n", chat.GetChatName(), chat.GetUuid()) + fmt.Printf("========================================================\n\n") + + // Since content files are relative, we need a path to a log file. + // This is a limitation. We'll assume the main config file's location. + logDir := filepath.Dir(me.chats.GetConfigPath()) + + 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" + } + + var content string + if contentFile := entry.GetContentFile(); contentFile != "" { + contentPath := filepath.Join(logDir, contentFile) + contentBytes, err := os.ReadFile(contentPath) + if err != nil { + content = fmt.Sprintf("--- ERROR: Could not read content file %s: %v ---", contentPath, err) + } else { + content = string(contentBytes) + } + } else { + content = entry.GetContent() + } + + if content != "" { + fmt.Printf("✦ %s (%s):\n%s\n", author, formattedTime, strings.TrimSpace(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, logDir) + } + } + fmt.Println() + } +} + +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, logDir string) { + snippetPath := filepath.Join(logDir, snippet.GetFilename()) + codeBytes, err := os.ReadFile(snippetPath) + if err != nil { + fmt.Printf("┌─[ ERROR: Could not read snippet file %s ]─\n\n", snippetPath) + return + } + code := string(codeBytes) + language := filepath.Base(snippet.GetFilename()) + 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, "") }