Skip to content

Commit a8aac2f

Browse files
authored
fix: removed hydration errors on intial page load (#134)
1 parent 2ad73b4 commit a8aac2f

File tree

8 files changed

+66
-31
lines changed

8 files changed

+66
-31
lines changed

src/components/BotMessage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('BotMessage', () => {
2626
id: 'msg-1',
2727
content: markdownTable,
2828
sender: 'agent',
29-
timestamp: new Date('2025-07-06T12:00:00Z'),
29+
timestamp: '2025-07-06T12:00:00Z',
3030
status: 'sent',
3131
}
3232
const { container } = render(<BotMessage message={message} />)

src/components/BotMessage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ export function BotMessage({ message, fileAnnotations = [] }: BotMessageProps) {
160160
)}
161161
</div>
162162
<div className="flex gap-1 items-center text-xs text-gray-500 dark:text-gray-400 space-x-1">
163-
<time dateTime={message.timestamp.toISOString()}>
164-
{formatTimestamp(message.timestamp)}
163+
<time dateTime={message.timestamp}>
164+
{formatTimestamp(new Date(message.timestamp))}
165165
</time>
166166
<button
167167
type="button"

src/components/Chat.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type { AnnotatedFile } from '@/lib/utils/code-interpreter'
2525
import { isCodeInterpreterSupported } from '@/lib/utils/prompting'
2626
import { useHasMounted } from '@/hooks/useHasMounted'
2727
import { WebSearchMessage } from './WebSearchMessage'
28+
import { getTimestamp } from '@/lib/utils/date'
2829

2930
type StreamEvent =
3031
| {
@@ -881,6 +882,7 @@ export function Chat() {
881882
type: 'user',
882883
id: generateMessageId(),
883884
content: prompt,
885+
timestamp: getTimestamp(),
884886
},
885887
])
886888

@@ -997,7 +999,7 @@ export function Chat() {
997999
id: key,
9981000
content: '', // No text, just the file
9991001
sender: 'agent',
1000-
timestamp: new Date(),
1002+
timestamp: getTimestamp(),
10011003
status: 'sent',
10021004
}}
10031005
fileAnnotations={[event.annotation]}
@@ -1013,7 +1015,7 @@ export function Chat() {
10131015
id: event.id,
10141016
content: event.content,
10151017
sender: 'agent',
1016-
timestamp: new Date(),
1018+
timestamp: getTimestamp(),
10171019
status: 'sent',
10181020
}}
10191021
fileAnnotations={event.fileAnnotations || []}
@@ -1027,7 +1029,7 @@ export function Chat() {
10271029
id: event.id,
10281030
content: event.content,
10291031
sender: 'user',
1030-
timestamp: new Date(),
1032+
timestamp: getTimestamp(),
10311033
status: 'sent',
10321034
}}
10331035
/>
@@ -1041,14 +1043,18 @@ export function Chat() {
10411043
if ('id' in event) {
10421044
const isAssistant = message.role === 'assistant'
10431045
if (isAssistant) {
1046+
if (!hasMounted) {
1047+
return null
1048+
}
1049+
10441050
return (
10451051
<BotMessage
10461052
key={key}
10471053
message={{
10481054
id: message.id,
10491055
content: message.content,
10501056
sender: 'agent',
1051-
timestamp: new Date(),
1057+
timestamp: getTimestamp(),
10521058
status: 'sent',
10531059
}}
10541060
/>
@@ -1061,7 +1067,7 @@ export function Chat() {
10611067
id: message.id,
10621068
content: message.content,
10631069
sender: 'user',
1064-
timestamp: new Date(),
1070+
timestamp: getTimestamp(),
10651071
status: 'sent',
10661072
}}
10671073
/>

src/components/UserMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function UserMessage({ message }: UserMessageProps) {
3535
<MarkdownContent content={message.content} />
3636
</div>
3737
<div className="flex items-center text-xs text-gray-500 dark:text-gray-400 space-x-1">
38-
<span>{formatTimestamp(message.timestamp)}</span>
38+
<span>{formatTimestamp(new Date(message.timestamp))}</span>
3939
{statusIcons[message.status]}
4040
</div>
4141
</div>

src/components/__snapshots__/BotMessage.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ Here is a summary table of total sales from every year. If you need additional d
198198
class="flex gap-1 items-center text-xs text-gray-500 dark:text-gray-400 space-x-1"
199199
>
200200
<time
201-
datetime="2025-07-06T12:00:00.000Z"
201+
datetime="2025-07-06T12:00:00Z"
202202
>
203203
12:00 PM
204204
</time>

src/lib/utils/date.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it, expect } from 'vitest'
2+
import { getTimestamp } from './date'
3+
4+
// Helper to check if a string is a valid ISO date
5+
function isValidISODate(str: string): boolean {
6+
const d = new Date(str)
7+
return !isNaN(d.getTime()) && str === d.toISOString()
8+
}
9+
10+
describe('getTimestamp', () => {
11+
it('returns a valid ISO string', () => {
12+
const ts = getTimestamp()
13+
expect(isValidISODate(ts)).toBe(true)
14+
})
15+
16+
it('returns a timestamp close to now', () => {
17+
const now = new Date()
18+
const ts = new Date(getTimestamp())
19+
// Should be within 1 second of now
20+
expect(Math.abs(ts.getTime() - now.getTime())).toBeLessThan(1000)
21+
})
22+
})

src/lib/utils/date.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Get the current timestamp in ISO string format
3+
* @returns The current timestamp in ISO string format
4+
*/
5+
export const getTimestamp = () => {
6+
return new Date().toISOString()
7+
}

src/mcp/client.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
11
export type MCPRequest = {
2-
user: string;
3-
prompt: string;
4-
};
2+
user: string
3+
prompt: string
4+
}
55

6-
export type MessageStatus = "sending" | "sent" | "error";
6+
export type MessageStatus = 'sending' | 'sent' | 'error'
77

88
export type Message = {
9-
id: string;
10-
content: string;
11-
sender: "user" | "agent";
12-
timestamp: Date;
13-
status: MessageStatus;
14-
};
9+
id: string
10+
content: string
11+
sender: 'user' | 'agent'
12+
timestamp: string
13+
status: MessageStatus
14+
}
1515

1616
/**
1717
* Calls the Model Control Protocol (MCP) with the user's message
1818
*/
1919
export async function callMCP({ user, prompt }: MCPRequest): Promise<string> {
2020
// Simulate network latency
21-
await new Promise(resolve => setTimeout(resolve, 1000));
22-
23-
console.log(`Calling MCP for ${user}: ${prompt}`);
21+
await new Promise((resolve) => setTimeout(resolve, 1000))
22+
23+
console.log(`Calling MCP for ${user}: ${prompt}`)
2424

2525
// This is a mock implementation - in real usage this would call your actual MCP endpoint
2626
const responses = [
2727
"I'm analyzing your request and will have an answer shortly.",
28-
"Based on the information available, I would recommend exploring these options further.",
28+
'Based on the information available, I would recommend exploring these options further.',
2929
"That's an interesting question. Let me provide some context that might help.",
3030
"I've processed your request and here's what I found in our knowledge base.",
31-
"According to our latest data, the answer to your question would be...",
32-
`Great question! As your AI assistant, I can tell you that ${prompt.toLowerCase().includes("weather") ? "the weather forecast shows mild temperatures with a chance of rain." : "there are multiple approaches to consider here."}`,
33-
];
31+
'According to our latest data, the answer to your question would be...',
32+
`Great question! As your AI assistant, I can tell you that ${prompt.toLowerCase().includes('weather') ? 'the weather forecast shows mild temperatures with a chance of rain.' : 'there are multiple approaches to consider here.'}`,
33+
]
3434

3535
// Select a response based on input to simulate some contextual awareness
36-
const responseIndex = Math.floor(prompt.length % responses.length);
37-
return Promise.resolve(responses[responseIndex]);
36+
const responseIndex = Math.floor(prompt.length % responses.length)
37+
return Promise.resolve(responses[responseIndex])
3838
}
3939

4040
/**
4141
* Generates a unique ID for messages
4242
*/
4343
export function generateMessageId(): string {
44-
return Math.random().toString(36).substring(2, 15);
45-
}
44+
return Math.random().toString(36).substring(2, 15)
45+
}

0 commit comments

Comments
 (0)