package thread import ( "encoding/json" "fmt" "io" "log" "os" "path/filepath" "strings" "time" "git.wntrmute.dev/kyle/goutils/die" ) const timeFormat = "2006-01-02 15:04" type Message struct { ID string `json:"id"` ThreadID string `json:"thread_id"` Role string `json:"role"` Status string `json:"status"` Created int64 `json:"created"` Updated int64 `json:"updated"` Object string `json:"object"` Content []struct { Type string `json:"type"` Text struct { Value string `json:"value"` Annotations []any `json:"annotations"` } `json:"text"` } `json:"content"` } func (m *Message) Date() time.Time { return time.Unix(m.Created/1000, 0) } func (m *Message) Header() string { role := m.Role switch role { case "user": role = strings.ToTitle(os.Getenv("USER")) case "assistant": role = "LLM" default: // nothing } return fmt.Sprintf("[%s] **%s**\n", m.Date().Format(timeFormat), m.Role) } func (m *Message) Body() string { content := []string{} for _, contentItem := range m.Content { // TODO: handle different content types if contentItem.Type != "text" { continue } if contentItem.Text.Annotations != nil { log.Print("warning: annotation in content not supported") } content = append(content, contentItem.Text.Value) } return strings.Join(content, "\n\n") } func LoadMessages(path string) ([]*Message, error) { messagesPath := filepath.Join(path, "messages.jsonl") file, err := os.Open(messagesPath) die.If(err) defer file.Close() jsonParser := json.NewDecoder(file) var messages []*Message for err == nil { var message Message err = jsonParser.Decode(&message) if err == nil { messages = append(messages, &message) } } if err != io.EOF { return nil, err } return messages, nil } func (m *Message) String() string { return m.Header() + m.Body() + "\n" }