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.
This commit is contained in:
parent
e69177bc53
commit
df4e63ea75
133
doPlayback.go
133
doPlayback.go
|
@ -2,34 +2,35 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.wit.com/lib/protobuf/chatpb"
|
||||||
"go.wit.com/log"
|
"go.wit.com/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const termWidth = 120 // The target width for the formatted output boxes.
|
||||||
|
|
||||||
func doPlayback() {
|
func doPlayback() {
|
||||||
if argv.Playback.Uuid != "" {
|
if argv.Playback.Uuid != "" {
|
||||||
showChat(argv.Playback.Uuid)
|
showChat(argv.Playback.Uuid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Found %d chat topic(s) in the log.", len(me.chats.GetChats()))
|
log.Infof("Found %d chat topic(s) in the log.", len(me.chats.GetChats()))
|
||||||
fmt.Println("-------------------------------------------------")
|
fmt.Println("-------------------------------------------------")
|
||||||
|
|
||||||
// Iterate through the top-level Chat messages, which are now named groups.
|
|
||||||
for _, chat := range me.chats.GetChats() {
|
for _, chat := range me.chats.GetChats() {
|
||||||
|
|
||||||
// Get the number of entries in the chat.
|
|
||||||
entryCount := len(chat.GetEntries())
|
entryCount := len(chat.GetEntries())
|
||||||
|
|
||||||
// Get the timestamp of the first entry to represent the chat's start time.
|
|
||||||
var formattedTime string
|
var formattedTime string
|
||||||
if entryCount > 0 && chat.GetEntries()[0].GetCtime() != nil {
|
if ctime := chat.GetCtime(); ctime != nil {
|
||||||
t := chat.GetEntries()[0].GetCtime().AsTime()
|
t := ctime.AsTime()
|
||||||
formattedTime = t.Format("2006-01-02 15:04:05") // YYYY-MM-DD HH:MM:SS
|
formattedTime = t.Format("2006-01-02 15:04:05")
|
||||||
} else {
|
} else {
|
||||||
formattedTime = "No Timestamp"
|
formattedTime = "No Timestamp"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the formatted one-line summary.
|
|
||||||
fmt.Printf("Topic: %-25s | Entries: %-4d | Started: %s | UUID: %s\n",
|
fmt.Printf("Topic: %-25s | Entries: %-4d | Started: %s | UUID: %s\n",
|
||||||
chat.GetChatName(),
|
chat.GetChatName(),
|
||||||
entryCount,
|
entryCount,
|
||||||
|
@ -46,6 +47,116 @@ func showChat(uuid string) {
|
||||||
log.Info("unknown uuid", uuid)
|
log.Info("unknown uuid", uuid)
|
||||||
return
|
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, "")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue