v0.3.90 6f6753c

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close