Skip to content

Commit 9182331

Browse files
committed
Add snapshot date information when viewing logs
1 parent 0e015ed commit 9182331

File tree

13 files changed

+241
-18
lines changed

13 files changed

+241
-18
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@
474474
"@peculiar/x509": "^1.14.3",
475475
"@repo/shared": "workspace:*",
476476
"axios": "1.13.6",
477-
"date-fns": "^4.1.0",
477+
"date-fns": "catalog:",
478478
"eventsource": "^4.1.0",
479479
"find-process": "^2.1.0",
480480
"jsonc-parser": "^3.3.1",

packages/shared/src/tasks/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export interface TaskPreset {
3131

3232
/** Result of fetching task logs: either logs or an error/unavailable state. */
3333
export type TaskLogs =
34-
| { status: "ok"; logs: readonly TaskLogEntry[] }
34+
| {
35+
status: "ok";
36+
logs: readonly TaskLogEntry[];
37+
snapshot?: boolean;
38+
snapshotAt?: string;
39+
}
3540
| { status: "not_available" }
3641
| { status: "error" };
3742

packages/shared/src/tasks/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,14 @@ export function isAgentStarting(task: Task): boolean {
7979
export function isWorkspaceStarting(task: Task): boolean {
8080
return isBuildingWorkspace(task) || isAgentStarting(task);
8181
}
82+
83+
/** Label for the log preview header, matching the Coder dashboard pattern. */
84+
export function logPreviewLabel(count: number): string {
85+
if (count === 0) {
86+
return "AI chat logs";
87+
}
88+
if (count === 1) {
89+
return "Last message of AI chat logs";
90+
}
91+
return `Last ${count} messages of AI chat logs`;
92+
}

packages/tasks/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@tanstack/react-query": "catalog:",
1515
"@vscode-elements/react-elements": "catalog:",
1616
"@vscode/codicons": "catalog:",
17+
"date-fns": "catalog:",
1718
"react": "catalog:",
1819
"react-dom": "catalog:"
1920
},

packages/tasks/src/components/AgentChatHistory.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import {
2+
logPreviewLabel,
3+
type TaskLogEntry,
4+
type TaskLogs,
5+
} from "@repo/shared";
6+
import { formatDistanceToNowStrict } from "date-fns";
7+
18
import { LogViewer, LogViewerPlaceholder } from "./LogViewer";
29

3-
import type { TaskLogEntry, TaskLogs } from "@repo/shared";
10+
import type { ReactNode } from "react";
411

512
interface AgentChatHistoryProps {
613
taskLogs: TaskLogs;
@@ -26,14 +33,39 @@ function LogEntry({
2633
);
2734
}
2835

36+
function chatHistoryHeader(taskLogs: TaskLogs): ReactNode {
37+
if (taskLogs.status !== "ok" || taskLogs.snapshot !== true) {
38+
return "Chat history";
39+
}
40+
const label = logPreviewLabel(taskLogs.logs.length);
41+
if (taskLogs.snapshotAt === undefined) {
42+
return label;
43+
}
44+
const relativeTime = formatDistanceToNowStrict(
45+
new Date(taskLogs.snapshotAt),
46+
{ addSuffix: true },
47+
);
48+
return (
49+
<>
50+
{label}{" "}
51+
<span className="snapshot-info">
52+
<span className="codicon codicon-info" />
53+
<span className="snapshot-info-tooltip">
54+
Snapshot taken {relativeTime}
55+
</span>
56+
</span>
57+
</>
58+
);
59+
}
60+
2961
export function AgentChatHistory({
3062
taskLogs,
3163
isThinking,
3264
}: AgentChatHistoryProps) {
3365
const logs = taskLogs.status === "ok" ? taskLogs.logs : [];
3466

3567
return (
36-
<LogViewer header="Chat History">
68+
<LogViewer header={chatHistoryHeader(taskLogs)}>
3769
{logs.length === 0 ? (
3870
<LogViewerPlaceholder error={taskLogs.status === "error"}>
3971
{getEmptyMessage(taskLogs.status)}

packages/tasks/src/components/LogViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useFollowScroll } from "../hooks/useFollowScroll";
55
import type { ReactNode } from "react";
66

77
interface LogViewerProps {
8-
header: string;
8+
header: ReactNode;
99
children: ReactNode;
1010
}
1111

packages/tasks/src/index.css

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,12 +478,46 @@ vscode-icon.disabled {
478478
}
479479

480480
.log-viewer-header {
481+
position: relative;
481482
padding: 6px 8px;
482-
font-size: 0.8em;
483+
font-size: 0.9em;
483484
color: var(--vscode-descriptionForeground);
484485
border-bottom: 1px solid var(--vscode-input-border);
485486
}
486487

488+
.snapshot-info {
489+
margin-left: 3px;
490+
}
491+
492+
.snapshot-info .codicon-info {
493+
font-size: 1em;
494+
color: var(--vscode-descriptionForeground);
495+
vertical-align: middle;
496+
cursor: pointer;
497+
}
498+
499+
.snapshot-info-tooltip {
500+
display: none;
501+
position: absolute;
502+
bottom: calc(100% + 4px);
503+
left: 8px;
504+
right: 8px;
505+
width: fit-content;
506+
padding: 6px 10px;
507+
white-space: nowrap;
508+
font-size: 1em;
509+
color: var(--vscode-editorHoverWidget-foreground);
510+
background: var(--vscode-editorHoverWidget-background);
511+
border: 1px solid var(--vscode-editorHoverWidget-border);
512+
border-radius: 3px;
513+
z-index: 10;
514+
pointer-events: none;
515+
}
516+
517+
.snapshot-info:hover .snapshot-info-tooltip {
518+
display: block;
519+
}
520+
487521
.log-viewer-content {
488522
flex: 1;
489523
min-height: 0;

pnpm-lock.yaml

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ catalog:
1111
"@vscode/codicons": ^0.0.44
1212
babel-plugin-react-compiler: ^1.0.0
1313
coder: github:coder/coder#main
14+
date-fns: ^4.1.0
1415
react: ^19.2.4
1516
react-dom: ^19.2.4
1617
typescript: ^5.9.3

src/webviews/tasks/tasksPanelProvider.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,7 @@ export class TasksPanelProvider
281281
}
282282

283283
await this.refreshAndNotifyTasks();
284-
vscode.window.showInformationMessage(
285-
`Task "${taskName}" deleted`,
286-
);
284+
vscode.window.showInformationMessage(`Task "${taskName}" deleted`);
287285
}
288286

289287
private handlePauseTask(taskId: string): Promise<void> {
@@ -354,10 +352,9 @@ export class TasksPanelProvider
354352
isAxiosError(err) &&
355353
(err.response?.status === 409 || err.response?.status === 400)
356354
) {
357-
throw new Error(
358-
`Agent is not ready for messages (${errToStr(err)})`,
359-
{ cause: err },
360-
);
355+
throw new Error(`Agent is not ready for messages (${errToStr(err)})`, {
356+
cause: err,
357+
});
361358
}
362359
throw err;
363360
}
@@ -569,7 +566,12 @@ export class TasksPanelProvider
569566
private async fetchTaskLogs(taskId: string): Promise<TaskLogs> {
570567
try {
571568
const response = await this.client.getTaskLogs("me", taskId);
572-
return { status: "ok", logs: response.logs };
569+
return {
570+
status: "ok",
571+
logs: response.logs,
572+
snapshot: response.snapshot,
573+
snapshotAt: response.snapshot_at,
574+
};
573575
} catch (err) {
574576
if (isAxiosError(err) && err.response?.status === 409) {
575577
return { status: "not_available" };

0 commit comments

Comments
 (0)