diff --git a/apps/web-wasm/src/components/ai/WebAIChat.vue b/apps/web-wasm/src/components/ai/WebAIChat.vue index 883bd25f..a8a8ce05 100644 --- a/apps/web-wasm/src/components/ai/WebAIChat.vue +++ b/apps/web-wasm/src/components/ai/WebAIChat.vue @@ -9,7 +9,7 @@ import ConversationList from '@/components/AIChat/chat/ConversationList.vue' import SimpleAIChatInput from '@/components/AIChat/input/SimpleAIChatInput.vue' import { reportRuntimeLog } from '@/services/log-report' import { useLayoutStore } from '@/stores/layout' -import { toWebAIChatMessage } from './message-mapper' +import { toAIConversationListItem, toWebAIChatMessage } from './message-mapper' import { isAbortError, resolveSendTarget } from './send-lifecycle' import { useWebAISettingsStore } from '../../stores/web-ai-settings' @@ -40,6 +40,7 @@ const displayMessages = computed(() => { if (streamingMessage.value) result.push(toWebAIChatMessage(streamingMessage.value, true)) return result }) +const conversationListItems = computed(() => conversations.value.map(toAIConversationListItem)) const canRetry = computed(() => messages.value.at(-1)?.role === 'user') const canRegenerate = computed(() => messages.value.at(-1)?.role === 'assistant') @@ -376,7 +377,7 @@ function handleSend(payload: { content: string }) { :session-id="sessionId" :active-id="conversationId" :disabled="generating" - :conversations="conversations" + :conversations="conversationListItems" :loading="loading" :collapsible="false" embedded @@ -392,7 +393,7 @@ function handleSend(payload: { content: string }) { :session-id="sessionId" :active-id="conversationId" :disabled="generating" - :conversations="conversations" + :conversations="conversationListItems" :loading="loading" @select="selectConversation" @create="newConversation" diff --git a/apps/web-wasm/src/components/ai/message-mapper.test.ts b/apps/web-wasm/src/components/ai/message-mapper.test.ts index 7c5fd013..01733831 100644 --- a/apps/web-wasm/src/components/ai/message-mapper.test.ts +++ b/apps/web-wasm/src/components/ai/message-mapper.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict' import { describe, it } from 'node:test' -import { toWebAIContentBlocks } from './message-mapper' +import { toAIConversationListItem, toWebAIContentBlocks } from './message-mapper' describe('toWebAIContentBlocks', () => { it('maps shared runtime blocks to the existing ChatLab message renderer', () => { @@ -24,3 +24,21 @@ describe('toWebAIContentBlocks', () => { assert.equal(blocks[1]?.type === 'tool' ? blocks[1].tool.status : null, 'done') }) }) + +describe('toAIConversationListItem', () => { + it('normalizes Browser Runtime millisecond timestamps for the shared conversation list', () => { + const createdAt = Date.UTC(2025, 0, 1, 12) + const updatedAt = Date.UTC(2025, 5, 1, 12) + + const item = toAIConversationListItem({ + id: 'conversation-1', + sessionId: 'session-1', + title: 'Conversation', + createdAt, + updatedAt, + }) + + assert.equal(item.createdAt, Math.floor(createdAt / 1000)) + assert.equal(item.updatedAt, Math.floor(updatedAt / 1000)) + }) +}) diff --git a/apps/web-wasm/src/components/ai/message-mapper.ts b/apps/web-wasm/src/components/ai/message-mapper.ts index d242fec8..7bf1a083 100644 --- a/apps/web-wasm/src/components/ai/message-mapper.ts +++ b/apps/web-wasm/src/components/ai/message-mapper.ts @@ -1,7 +1,16 @@ -import type { RuntimeContentBlock, RuntimeMessage } from '@openchatlab/ai-runtime' +import type { RuntimeContentBlock, RuntimeConversation, RuntimeMessage } from '@openchatlab/ai-runtime' import type { ChatEvidencePayload } from '@openchatlab/core' +import type { AIConversationListItem } from '@/components/AIChat/chat/ConversationList.vue' import type { ChatMessage, ContentBlock } from '@/stores/aiChat' +export function toAIConversationListItem(conversation: RuntimeConversation): AIConversationListItem { + return { + ...conversation, + createdAt: Math.floor(conversation.createdAt / 1000), + updatedAt: Math.floor(conversation.updatedAt / 1000), + } +} + export function toWebAIContentBlocks(blocks: RuntimeContentBlock[] | undefined): ContentBlock[] { return (blocks ?? []).flatMap((block): ContentBlock[] => { if (block.type === 'text') return [{ type: 'text', text: block.text }]