Skip to content

Commit c79118c

Browse files
github-actions[bot]Amber Agentclaude
authored
[Amber] Fix: Feature: add timestamps to UI chat message (#416)
## Automated Fix by Amber Agent This PR addresses issue #414 using the Amber background agent. ### Changes Summary - **Action Type:** execute-proposal - **Commit:** 4fd9364 - **Triggered by:** Issue label/command ### Pre-merge Checklist - [ ] All linters pass - [ ] All tests pass - [ ] Changes follow project conventions (CLAUDE.md) - [ ] No scope creep beyond issue description ### Reviewer Notes This PR was automatically generated. Please review: 1. Code quality and adherence to standards 2. Test coverage for changes 3. No unintended side effects --- 🤖 Generated with [Amber Background Agent](https://github.com/ambient-code/platform/blob/main/docs/amber-automation.md) Closes #414 Co-authored-by: Amber Agent <amber@ambient-code.ai> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1018879 commit c79118c

File tree

4 files changed

+81
-7
lines changed

4 files changed

+81
-7
lines changed

components/frontend/src/components/ui/message.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { cn } from "@/lib/utils";
55
import ReactMarkdown from "react-markdown";
66
import remarkGfm from "remark-gfm";
77
import type { Components } from "react-markdown";
8+
import { formatTimestamp } from "@/lib/format-timestamp";
89

910
export type MessageRole = "bot" | "user";
1011

@@ -18,6 +19,7 @@ export type MessageProps = {
1819
components?: Components;
1920
borderless?: boolean;
2021
actions?: React.ReactNode;
22+
timestamp?: string;
2123
};
2224

2325
const defaultComponents: Components = {
@@ -169,12 +171,13 @@ export const LoadingDots = () => {
169171

170172
export const Message = React.forwardRef<HTMLDivElement, MessageProps>(
171173
(
172-
{ role, content, isLoading, className, components, borderless, actions, ...props },
174+
{ role, content, isLoading, className, components, borderless, actions, timestamp, ...props },
173175
ref
174176
) => {
175177
const isBot = role === "bot";
176178
const avatarBg = isBot ? "bg-blue-600" : "bg-green-600";
177179
const avatarText = isBot ? "AI" : "U";
180+
const formattedTime = formatTimestamp(timestamp);
178181

179182
const avatar = (
180183
<div className="flex-shrink-0">
@@ -200,10 +203,16 @@ export const Message = React.forwardRef<HTMLDivElement, MessageProps>(
200203

201204
{/* Message Content */}
202205
<div className={cn("flex-1 min-w-0", !isBot && "max-w-[70%]")}>
206+
{/* Timestamp */}
207+
{formattedTime && (
208+
<div className={cn("text-[10px] text-muted-foreground/60 mb-1", !isBot && "text-right")}>
209+
{formattedTime}
210+
</div>
211+
)}
203212
<div className={cn(
204213
borderless ? "p-0" : "rounded-lg p-3",
205214
!borderless && (isBot ? "bg-card" : "bg-border/30")
206-
)}>
215+
)}>
207216
{/* Content */}
208217
<div className="text-sm text-foreground">
209218
{isLoading ? (

components/frontend/src/components/ui/stream-message.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const StreamMessage: React.FC<StreamMessageProps> = ({ message, onGoToRes
3636
m != null && typeof m === "object" && "toolUseBlock" in m && "resultBlock" in m;
3737

3838
if (isToolUsePair(message)) {
39-
return <ToolMessage toolUseBlock={message.toolUseBlock} resultBlock={message.resultBlock} />;
39+
return <ToolMessage toolUseBlock={message.toolUseBlock} resultBlock={message.resultBlock} timestamp={message.timestamp} />;
4040
}
4141

4242
const m = message as MessageObject;
@@ -54,13 +54,13 @@ export const StreamMessage: React.FC<StreamMessageProps> = ({ message, onGoToRes
5454
case "user_message":
5555
case "agent_message": {
5656
if (typeof m.content === "string") {
57-
return <Message role={m.type === "agent_message" ? "bot" : "user"} content={m.content} name="Claude AI" borderless={plainCard}/>;
57+
return <Message role={m.type === "agent_message" ? "bot" : "user"} content={m.content} name="Claude AI" borderless={plainCard} timestamp={m.timestamp}/>;
5858
}
5959
switch (m.content.type) {
6060
case "thinking_block":
6161
return <ThinkingMessage block={m.content} />
6262
case "text_block":
63-
return <Message role={m.type === "agent_message" ? "bot" : "user"} content={m.content.text} name="Claude AI" borderless={plainCard}/>
63+
return <Message role={m.type === "agent_message" ? "bot" : "user"} content={m.content.text} name="Claude AI" borderless={plainCard} timestamp={m.timestamp}/>
6464
case "tool_use_block":
6565
return <ToolMessage toolUseBlock={m.content} borderless={plainCard}/>
6666
case "tool_result_block":
@@ -78,6 +78,7 @@ export const StreamMessage: React.FC<StreamMessageProps> = ({ message, onGoToRes
7878
role="bot"
7979
content={m.is_error ? "Agent completed with errors." : "Agent completed successfully."}
8080
name="Claude AI"
81+
timestamp={m.timestamp}
8182
actions={
8283
<div className="flex items-center justify-between">
8384
<div className="text-xs text-muted-foreground">

components/frontend/src/components/ui/tool-message.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ import {
1515
import ReactMarkdown from "react-markdown";
1616
import type { Components } from "react-markdown";
1717
import remarkGfm from "remark-gfm";
18+
import { formatTimestamp } from "@/lib/format-timestamp";
1819

1920
export type ToolMessageProps = {
2021
toolUseBlock?: ToolUseBlock;
2122
resultBlock?: ToolResultBlock;
2223
className?: string;
2324
borderless?: boolean;
25+
timestamp?: string;
2426
};
2527

2628
const formatToolName = (toolName?: string) => {
@@ -179,7 +181,7 @@ const extractTextFromResultContent = (content: unknown): string => {
179181
};
180182

181183
export const ToolMessage = React.forwardRef<HTMLDivElement, ToolMessageProps>(
182-
({ toolUseBlock, resultBlock, className, borderless, ...props }, ref) => {
184+
({ toolUseBlock, resultBlock, className, borderless, timestamp, ...props }, ref) => {
183185
const [isExpanded, setIsExpanded] = useState(false);
184186

185187
const toolResultBlock = resultBlock;
@@ -200,10 +202,12 @@ export const ToolMessage = React.forwardRef<HTMLDivElement, ToolMessageProps>(
200202
const isSubagent = Boolean(subagentType);
201203
const subagentClasses = subagentType ? getColorClassesForName(subagentType) : undefined;
202204
const displayName = isSubagent ? subagentType : toolName;
203-
205+
204206
// Compact mode for simple tool calls (non-subagent)
205207
const isCompact = !isSubagent;
206208

209+
const formattedTime = formatTimestamp(timestamp);
210+
207211
return (
208212
<div ref={ref} className={cn(isCompact ? "mb-1" : "mb-4", className)} {...props}>
209213
<div className="flex items-start space-x-3">
@@ -224,6 +228,12 @@ export const ToolMessage = React.forwardRef<HTMLDivElement, ToolMessageProps>(
224228

225229
{/* Tool Message Content */}
226230
<div className="flex-1 min-w-0">
231+
{/* Timestamp */}
232+
{formattedTime && (
233+
<div className="text-[10px] text-muted-foreground/60 mb-1">
234+
{formattedTime}
235+
</div>
236+
)}
227237
<div
228238
className={cn(
229239
isCompact ? "" : (borderless ? "p-0" : "rounded-lg border shadow-sm"),
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Formats an ISO timestamp into a human-readable format
3+
* @param timestamp ISO 8601 timestamp string
4+
* @returns Formatted time string (HH:MM:SS)
5+
*/
6+
export function formatTimestamp(timestamp: string | undefined): string {
7+
if (!timestamp) return "";
8+
9+
try {
10+
const date = new Date(timestamp);
11+
12+
// Check if date is valid
13+
if (isNaN(date.getTime())) {
14+
return "";
15+
}
16+
17+
// Format as HH:MM:SS in local timezone
18+
const hours = date.getHours().toString().padStart(2, "0");
19+
const minutes = date.getMinutes().toString().padStart(2, "0");
20+
const seconds = date.getSeconds().toString().padStart(2, "0");
21+
22+
return `${hours}:${minutes}:${seconds}`;
23+
} catch {
24+
return "";
25+
}
26+
}
27+
28+
/**
29+
* Formats an ISO timestamp into a human-readable format with date
30+
* @param timestamp ISO 8601 timestamp string
31+
* @returns Formatted datetime string (MM/DD HH:MM:SS)
32+
*/
33+
export function formatTimestampWithDate(timestamp: string | undefined): string {
34+
if (!timestamp) return "";
35+
36+
try {
37+
const date = new Date(timestamp);
38+
39+
// Check if date is valid
40+
if (isNaN(date.getTime())) {
41+
return "";
42+
}
43+
44+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
45+
const day = date.getDate().toString().padStart(2, "0");
46+
const hours = date.getHours().toString().padStart(2, "0");
47+
const minutes = date.getMinutes().toString().padStart(2, "0");
48+
const seconds = date.getSeconds().toString().padStart(2, "0");
49+
50+
return `${month}/${day} ${hours}:${minutes}:${seconds}`;
51+
} catch {
52+
return "";
53+
}
54+
}

0 commit comments

Comments
 (0)