-// openTree opens the tree of the chat in root and indexes its new lines. A chat
-// of total messages cannot have a node that covers a later message.
-func openTree(ctx context.Context, root string, total int, clock func() time.Time) (*Tree, error) {
- for _, dir := range []string{"tree", "index"} {
- err := os.MkdirAll(filepath.Join(root, dir), 0o700)
- if err != nil {
- return nil, fmt.Errorf("create %s directory: %w", dir, err)
- }
- }
-
- t := &Tree{
- clock: clock,
- journal: Journal{
- dir: filepath.Join(root, "tree"),
- },
- dir: filepath.Join(root, "index"),
- levels: make(map[int]*Records),
- }
-
- err := t.open(ctx, total)
- if err != nil {
- return nil, errors.Join(fmt.Errorf("index tree: %w", err), t.Close())
- }
-
- return t, nil
-}
-
-// leafSource returns the text that the leaf of message holds or summarizes.
-func leafSource(message Message) string {
- return message.Kind + ": " + message.Text
-}
-
-// freeLeaf returns the leaf of message when its source fits in a node. Such a
-// leaf holds the source word for word and needs no model call.
-func freeLeaf(message Message) (Node, bool) {
- text := leafSource(message)
- if len(text) > nodeBytes {
- return Node{}, false
- }
-
- return Node{
- Level: 0,
- Index: message.ID,
- Text: text,
- Size: len(text),
- }, true
-}
-
-// freeMerge returns the parent of two sibling nodes when both texts, joined by
-// a newline, fit in a node. Such a parent needs no model call.
-func freeMerge(left, right Node) (Node, bool) {
- if left.Size+1+right.Size > nodeBytes {
- return Node{}, false
- }
-
- text := left.Text + "\n" + right.Text
-
- return Node{
- Level: left.Level + 1,
- Index: left.Index / 2,
- Text: text,
- Size: len(text),
- }, true
-}
-
-// nodeEnd returns the ID after the last message of node (l, i).
-func nodeEnd(level, index int) int {
- return (index + 1) << level
-}
-