feat(ui): 实现消息 parts 有序渲染
- Server: API 返回 parts 数组保持原始顺序 - Server: 添加 MessagePart 类型定义 (text/tool/reasoning) - UI: ChatMessage 按 parts 顺序交叉渲染文本和工具调用 - UI: 新增 ToolPartItem 组件渲染单个工具 part - UI: useChat 创建消息时添加 parts 字段
This commit is contained in:
@@ -6,7 +6,12 @@
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getSessionManager } from '../session/manager.js';
|
||||
import { CreateSessionInputSchema, type ToolCallInfo, type MergedMessage } from '../types.js';
|
||||
import {
|
||||
CreateSessionInputSchema,
|
||||
type ToolCallInfo,
|
||||
type MergedMessage,
|
||||
type MessagePart,
|
||||
} from '../types.js';
|
||||
|
||||
export const sessionsRouter = new Hono();
|
||||
|
||||
@@ -163,13 +168,40 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
for (const msgInfo of messageInfos) {
|
||||
const parts = await PartStorage.getByIds(msgInfo.id, msgInfo.partIds);
|
||||
|
||||
// 提取文本内容
|
||||
// 转换 Parts 为前端格式(保持顺序)
|
||||
const messageParts: MessagePart[] = parts
|
||||
.filter((p) => p.type === 'text' || p.type === 'tool' || p.type === 'reasoning')
|
||||
.map((p): MessagePart => {
|
||||
if (p.type === 'text') {
|
||||
return { type: 'text', id: p.id, text: p.text ?? '' };
|
||||
}
|
||||
if (p.type === 'reasoning') {
|
||||
return { type: 'reasoning', id: p.id, text: p.text ?? '' };
|
||||
}
|
||||
// tool
|
||||
const state = p.state!;
|
||||
const startTime = state.time?.start;
|
||||
const endTime = state.time?.end;
|
||||
return {
|
||||
type: 'tool',
|
||||
id: p.id,
|
||||
toolCallId: p.toolCallId ?? '',
|
||||
toolName: p.toolName ?? '',
|
||||
status: state.status,
|
||||
arguments: state.input ?? {},
|
||||
result: state.status === 'completed' ? state.output : undefined,
|
||||
error: state.status === 'error' ? state.error : undefined,
|
||||
duration: startTime && endTime ? endTime - startTime : undefined,
|
||||
};
|
||||
});
|
||||
|
||||
// 兼容字段:提取文本内容
|
||||
const textContent = parts
|
||||
.filter((p) => p.type === 'text')
|
||||
.map((p) => p.text ?? '')
|
||||
.join('');
|
||||
|
||||
// 提取工具调用
|
||||
// 兼容字段:提取工具调用
|
||||
const toolCalls: ToolCallInfo[] = parts
|
||||
.filter((p) => p.type === 'tool' && p.state)
|
||||
.map((p) => {
|
||||
@@ -191,8 +223,9 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
id: msgInfo.id,
|
||||
sessionId: msgInfo.sessionId,
|
||||
role: msgInfo.role,
|
||||
content: textContent,
|
||||
timestamp: new Date(msgInfo.createdAt).toISOString(),
|
||||
parts: messageParts,
|
||||
content: textContent || undefined,
|
||||
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user