188 lines
5.7 KiB
TypeScript
188 lines
5.7 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('appends markdown deltas without altering repeated symbols', () => {
|
|
const items: any[] = [];
|
|
|
|
ChatTimeTimelineBuilder.appendThinkingDelta(items, '先想一下', 1);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '## 标题\n', 2);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '| 模型 | 说明 |\n', 3);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '| --- | --- |\n', 4);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(items, '| ACL | 访问控制列表 |\n', 5);
|
|
ChatTimeTimelineBuilder.appendMessageDelta(
|
|
items,
|
|
'Final Answer: ```echartsoption',
|
|
6,
|
|
);
|
|
|
|
expect(items).toHaveLength(1);
|
|
expect(items[0]).toMatchObject({
|
|
content:
|
|
'## 标题\n| 模型 | 说明 |\n| --- | --- |\n| ACL | 访问控制列表 |\nFinal Answer: ```echartsoption',
|
|
role: 'assistant',
|
|
typing: true,
|
|
});
|
|
expect(items[0].segments).toMatchObject([
|
|
{ content: '先想一下', status: 'end', type: 'thinking' },
|
|
{
|
|
content:
|
|
'## 标题\n| 模型 | 说明 |\n| --- | --- |\n| ACL | 访问控制列表 |\nFinal Answer: ```echartsoption',
|
|
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',
|
|
});
|
|
});
|
|
});
|