1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
package chatpb
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
"go.wit.com/log"
"google.golang.org/protobuf/proto"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
)
func (c *Chats) AddGeminiComment(s string) *Chat {
chat := new(Chat)
chat.From = Who_GEMINI
chat.Content = s
chat.Ctime = timestamppb.New(time.Now())
c.AppendNew(chat)
return chat
}
func (c *Chats) AddUserComment(s string) *Chat {
chat := new(Chat)
chat.From = Who_USER
chat.Content = s
c.AppendNew(chat)
return chat
}
func UnmarshalChats(data []byte) (*Chats, error) {
c := new(Chats)
err := c.Unmarshal(data)
return c, err
}
func UnmarshalChatsTEXT(data []byte) (*Chats, error) {
c := new(Chats)
err := c.UnmarshalTEXT(data)
return c, err
}
func (all *Chats) AddFile(filename string) error {
// Nil checks for safety.
if all == nil {
return fmt.Errorf("cannot call AddFile on a nil *Chats object")
}
if all.Chats == nil {
all.Chats = make([]*Chat, 0)
}
data, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("Error reading file %s: %v", filename, err)
return err
}
logData, err := UnmarshalChatsTEXT(data)
if err != nil {
log.Fatalf("Error unmarshaling log file %s: %v", filename, err)
return err
}
// Iterate through the top-level messages from the source file.
for _, chatGroup := range logData.GetChats() {
if len(chatGroup.GetEntries()) > 0 {
// NEW FORMAT: This is a group, process its entries.
for _, entry := range chatGroup.GetEntries() {
// Convert the ChatEntry into a new Chat object for the flat list.
newChat := convertEntryToChat(entry, filename)
if newChat != nil {
newChat.VerifyUuid()
all.AppendByUuid(newChat)
}
}
} else {
// OLD FORMAT: This is a single, flat Chat message.
// We still process it to handle its external content file correctly.
newChat := convertChatToChat(chatGroup, filename)
if newChat != nil {
newChat.VerifyUuid()
all.AppendByUuid(newChat)
}
}
}
return nil
}
// convertChatToChat handles an old-style Chat message. It creates a clean
// copy and resolves its external content file.
func convertChatToChat(chat *Chat, filename string) *Chat {
if chat == nil {
return nil
}
// Manually create a ChatEntry from the Chat fields to reuse the logic.
entry := &ChatEntry{
From: chat.GetFrom(),
Ctime: chat.GetCtime(),
Content: chat.GetContent(),
Table: chat.GetTable(),
ToolCalls: chat.GetToolCalls(),
ContentFile: chat.GetContentFile(),
Uuid: chat.GetUuid(),
Snippets: chat.GetSnippets(),
}
return convertEntryToChat(entry, filename)
}
// convertEntryToChat creates a new Chat object from a ChatEntry's data
// and resolves its external content file.
func convertEntryToChat(entry *ChatEntry, filename string) *Chat {
if entry == nil {
return nil
}
// Create a new Chat object and copy the fields.
newChat := &Chat{
From: entry.GetFrom(),
Ctime: entry.GetCtime(),
Table: entry.GetTable(),
ToolCalls: entry.GetToolCalls(),
Snippets: entry.GetSnippets(),
Uuid: entry.GetUuid(),
}
// Handle content: prefer content_file, fallback to content.
var content string
if contentFile := entry.GetContentFile(); contentFile != "" {
logDir := filepath.Dir(filename)
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()
}
newChat.Content = content
return newChat
}
func (chats *Chats) VerifyUuids() bool {
var changed bool
all := chats.SortByUuid()
for all.Scan() {
chat := all.Next()
if chat.Uuid == "" {
chat.Uuid = uuid.New().String()
changed = true
}
}
return changed
}
func (c *Chat) VerifyUuid() bool {
if c.Uuid == "" {
c.Uuid = uuid.New().String()
return true
}
return false
}
func (x *Chats) AppendNew(y *Chat) {
x.Lock()
defer x.Unlock()
var chat *Chat
chat = proto.Clone(y).(*Chat)
x.Chats = append(x.Chats, chat)
}
|