diff --git a/nix/hashes.json b/nix/hashes.json index c91afa86e..7f089c7e0 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-krj85tYW0P/YOnsXnO1cD5XkLtfWZzdXzj7CTNvsPPk=" + "nodeModules": "sha256-N33FQyKF6IgGIRZ8NFd9o1/sjHMwbQ6KQcnMFyN0WmI=" } diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 43b8cc9de..f5224b2f9 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -79,6 +79,7 @@ const context = createContext<{ width: number conceal: () => boolean showThinking: () => boolean + showTimestamps: () => boolean }>() function use() { @@ -109,6 +110,7 @@ export function Session() { const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">(kv.get("sidebar", "auto")) const [conceal, setConceal] = createSignal(true) const [showThinking, setShowThinking] = createSignal(true) + const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show") const wide = createMemo(() => dimensions().width > 120) const sidebarVisible = createMemo(() => sidebar() === "show" || (sidebar() === "auto" && wide())) @@ -403,6 +405,19 @@ export function Session() { dialog.clear() }, }, + { + title: "Toggle timestamps", + value: "session.toggle.timestamps", + category: "Session", + onSelect: (dialog) => { + setShowTimestamps((prev) => { + const next = !prev + kv.set("timestamps", next ? "show" : "hide") + return next + }) + dialog.clear() + }, + }, { title: "Toggle thinking blocks", value: "session.toggle.thinking", @@ -712,6 +727,7 @@ export function Session() { }, conceal, showThinking, + showTimestamps, }} > @@ -891,6 +907,7 @@ function UserMessage(props: { index: number pending?: string }) { + const ctx = use() const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0]) const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : []))) const sync = useSync() @@ -949,7 +966,13 @@ function UserMessage(props: { {sync.data.config.username ?? "You"}{" "} {Locale.time(props.message.time.created)}} + fallback={ + + {ctx.showTimestamps() + ? `· ${Locale.todayTimeOrDateTime(props.message.time.created)}` + : `· ${Locale.time(props.message.time.created)}`} + + } > QUEUED @@ -973,6 +996,7 @@ function UserMessage(props: { function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; last: boolean }) { const local = useLocal() const { theme } = useTheme() + const ctx = use() return ( <> @@ -1015,7 +1039,12 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las {Locale.titlecase(props.message.mode)}{" "} - {props.message.modelID} + + {props.message.modelID} + {ctx.showTimestamps() && + props.message.time.completed && + ` · ${Locale.todayTimeOrDateTime(props.message.time.completed)}`} + diff --git a/packages/opencode/src/util/locale.ts b/packages/opencode/src/util/locale.ts index ab2623271..e567d5763 100644 --- a/packages/opencode/src/util/locale.ts +++ b/packages/opencode/src/util/locale.ts @@ -3,9 +3,29 @@ export namespace Locale { return str.replace(/\b\w/g, (c) => c.toUpperCase()) } - export function time(input: number) { + export function time(input: number): string { const date = new Date(input) - return date.toLocaleTimeString() + return date.toLocaleTimeString(undefined, { timeStyle: "short" }) + } + + export function datetime(input: number): string { + const date = new Date(input) + const localTime = time(input) + const localDate = date.toLocaleDateString() + return `${localTime} · ${localDate}` + } + + export function todayTimeOrDateTime(input: number): string { + const date = new Date(input) + const now = new Date() + const isToday = + date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate() + + if (isToday) { + return time(input) + } else { + return datetime(input) + } } export function number(num: number): string {