- 管理端和用户中心统一切换到 chatTime 时间线模型,按真实 assistant/tool 时序渲染 - 收紧工具气泡与 JSON 展示样式,保留同气泡内的工具状态更新 - 回形针直接触发文件选择,附件列表上移到输入框上方并补充共享 helper 测试
160 lines
4.5 KiB
TypeScript
160 lines
4.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
ChatTimeHistoryMapper,
|
|
ChatTimeTimelineBuilder,
|
|
} from '../chat-time';
|
|
|
|
describe('chat-time timeline builder', () => {
|
|
it('builds assistant thinking and message in the same assistant item', () => {
|
|
const items: any[] = [];
|
|
|
|
ChatTimeTimelineBuilder.appendUserMessage(items, {
|
|
content: '你好',
|
|
created: 1,
|
|
id: 'user-1',
|
|
});
|
|
ChatTimeTimelineBuilder.appendThinkingDelta(items, '先想一下', 2);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '最终回答', 3);
|
|
ChatTimeTimelineBuilder.finalize(items);
|
|
|
|
expect(items).toHaveLength(2);
|
|
expect(items[1]).toMatchObject({
|
|
content: '最终回答',
|
|
role: 'assistant',
|
|
});
|
|
expect(items[1].segments).toMatchObject([
|
|
{ content: '先想一下', status: 'end', type: 'thinking' },
|
|
{ content: '最终回答', type: 'text' },
|
|
]);
|
|
});
|
|
|
|
it('creates a new assistant item after tool result', () => {
|
|
const items: any[] = [];
|
|
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '第一段回答', 1);
|
|
ChatTimeTimelineBuilder.upsertToolCall(items, {
|
|
name: 'search_docs',
|
|
toolCallId: 'tool-1',
|
|
value: '{"query":"java"}',
|
|
});
|
|
ChatTimeTimelineBuilder.upsertToolResult(items, {
|
|
result: '{"hits":1}',
|
|
toolCallId: 'tool-1',
|
|
});
|
|
ChatTimeTimelineBuilder.appendThinkingDelta(items, '继续思考', 2);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '第二段回答', 3);
|
|
ChatTimeTimelineBuilder.finalize(items);
|
|
|
|
expect(items).toHaveLength(3);
|
|
expect(items[0]).toMatchObject({ content: '第一段回答', role: 'assistant' });
|
|
expect(items[1]).toMatchObject({
|
|
name: 'search_docs',
|
|
result: '{"hits":1}',
|
|
role: 'tool',
|
|
status: 'TOOL_RESULT',
|
|
toolCallId: 'tool-1',
|
|
});
|
|
expect(items[2]).toMatchObject({ content: '第二段回答', role: 'assistant' });
|
|
expect(items[2].segments).toMatchObject([
|
|
{ content: '继续思考', status: 'end', type: 'thinking' },
|
|
{ content: '第二段回答', type: 'text' },
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('chat-time history mapper', () => {
|
|
it('expands structured messageChain into assistant and tool timeline items', () => {
|
|
const items = ChatTimeHistoryMapper.fromHistoryRecords([
|
|
{
|
|
contentPayload: {
|
|
messageChain: [
|
|
{
|
|
content: '先回答一点',
|
|
reasoningContent: '先思考',
|
|
role: 'assistant',
|
|
toolCalls: [
|
|
{
|
|
arguments: '{"query":"java"}',
|
|
id: 'tool-1',
|
|
name: 'search_docs',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
content: '{"hits":1}',
|
|
role: 'tool',
|
|
toolCallId: 'tool-1',
|
|
},
|
|
{
|
|
content: '最后总结',
|
|
reasoningContent: '继续思考',
|
|
role: 'assistant',
|
|
},
|
|
],
|
|
},
|
|
created: 100,
|
|
id: 'assistant-record',
|
|
senderRole: 'assistant',
|
|
},
|
|
]);
|
|
|
|
expect(items).toHaveLength(3);
|
|
expect(items[0]).toMatchObject({
|
|
content: '先回答一点',
|
|
role: 'assistant',
|
|
});
|
|
expect(items[1]).toMatchObject({
|
|
arguments: '{"query":"java"}',
|
|
name: 'search_docs',
|
|
result: '{"hits":1}',
|
|
role: 'tool',
|
|
toolCallId: 'tool-1',
|
|
});
|
|
expect(items[2]).toMatchObject({
|
|
content: '最后总结',
|
|
role: 'assistant',
|
|
});
|
|
expect(items[0]?.id).not.toBe(items[2]?.id);
|
|
});
|
|
|
|
it('falls back to legacy chains when messageChain is unavailable', () => {
|
|
const items = ChatTimeHistoryMapper.fromLegacyMessages([
|
|
{
|
|
chains: [
|
|
{
|
|
reasoning_content: '旧思考',
|
|
thinkingStatus: 'end',
|
|
},
|
|
{
|
|
id: 'tool-2',
|
|
name: 'legacy_tool',
|
|
result: '{"ok":true}',
|
|
status: 'TOOL_RESULT',
|
|
},
|
|
],
|
|
content: '旧回答',
|
|
created: '2026-05-11 10:00:00',
|
|
id: 'legacy-1',
|
|
role: 'assistant',
|
|
},
|
|
]);
|
|
|
|
expect(items).toHaveLength(2);
|
|
expect(items[0]).toMatchObject({
|
|
content: '旧回答',
|
|
role: 'assistant',
|
|
});
|
|
expect(items[0].segments).toMatchObject([
|
|
{ content: '旧思考', status: 'end', type: 'thinking' },
|
|
{ content: '旧回答', type: 'text' },
|
|
]);
|
|
expect(items[1]).toMatchObject({
|
|
name: 'legacy_tool',
|
|
result: '{"ok":true}',
|
|
role: 'tool',
|
|
toolCallId: 'tool-2',
|
|
});
|
|
});
|
|
});
|