view_timestamps.go RawHistoryFolder
1package tui
2
3import (
4 "strings"
5 "time"
6
7 "github.com/patriceckhart/zot/packages/provider"
8)
9
10// ChatTimestampInterval returns the interval in minutes. Missing or negative
11// values use zero minutes, showing every eligible message.
12func ChatTimestampInterval(minutes *int) int {
13 if minutes == nil || *minutes < 0 {
14 return 0
15 }
16 return *minutes
17}
18
19// ChatTimestampDateMode normalizes the date display preference.
20func ChatTimestampDateMode(mode string) string {
21 if mode == "every_message" {
22 return mode
23 }
24 return "day_start"
25}
26
27// messageTimestamps computes labels over the whole transcript, including the
28// history outside TailLimit. Decoration stays outside the content render cache.
29func (v *View) messageTimestamps() []string {
30 if !v.ChatTimestamps {
31 return nil
32 }
33 labels := make([]string, len(v.Messages))
34 interval := ChatTimestampInterval(v.ChatTimestampIntervalMinutes)
35 var last time.Time
36 for idx, m := range v.Messages {
37 if m.Time.IsZero() || !timestampMessageVisible(m) {
38 continue
39 }
40 stamp := m.Time.Local()
41 newDay := !last.IsZero() && stamp.Format("2006-01-02") != last.Format("2006-01-02")
42 if interval > 0 && !last.IsZero() && !newDay && stamp.Sub(last).Minutes() < float64(interval) {
43 continue
44 }
45 format := "15:04"
46 if ChatTimestampDateMode(v.ChatTimestampDate) == "every_message" || last.IsZero() || newDay {
47 format = "2006-01-02 15:04"
48 }
49 labels[idx] = stamp.Format(format)
50 last = stamp
51 }
52 return labels
53}
54
55func timestampMessageVisible(m provider.Message) bool {
56 if m.Meta["compaction"] == "true" {
57 return false
58 }
59 for _, c := range m.Content {
60 switch b := c.(type) {
61 case provider.TextBlock:
62 if (m.Role == provider.RoleUser || m.Role == provider.RoleAssistant) && strings.TrimSpace(b.Text) != "" {
63 return true
64 }
65 case provider.ImageBlock:
66 if m.Role == provider.RoleUser {
67 return true
68 }
69 case provider.ToolResultBlock:
70 if m.Role == provider.RoleTool {
71 return true
72 }
73 }
74 }
75 return false
76}
77
78// timestampRow occupies the already existing separator row, never content or
79// extra vertical space. Narrow terminals omit labels that cannot fit in full.
80func (v *View) timestampRow(label string, width int) string {
81 if label == "" || width-2*toolBoxOuterMargin < len(label) {
82 return ""
83 }
84 return strings.Repeat(" ", width-toolBoxOuterMargin-len(label)) + v.Theme.FG256(v.Theme.Muted, label) + strings.Repeat(" ", toolBoxOuterMargin)
85}