feat(web): 添加全局 Toast 通知系统和资产预览导航功能
- 新增 ToastProvider 和 useToast hook,支持全局成功/错误/信息提示 - 资产预览增加左右导航按钮、键盘快捷键(方向键)和计数器显示 - 资产库增加图片/视频类型筛选标签页及计数 - 对话列表增加最近对话展示、搜索优化和删除确认 - 消息增加删除确认对话框 - 优化聊天自动滚动行为,仅在用户未手动滚动时跟随新内容 - 新增删除消息 API 端点 - 优化消息历史清理逻辑,过滤错误消息和孤儿 tool 消息 - 添加自定义滚动条样式 - 优化账户参考图显示逻辑,支持本地文件显示 - 修复对话创建流程,直接导航到新创建的对话
This commit is contained in:
@@ -381,6 +381,48 @@ async function streamOpenAI(
|
||||
}
|
||||
}
|
||||
|
||||
// Clean and validate message history before sending to API
|
||||
function sanitizeHistory(messages: DbMessage[]): DbMessage[] {
|
||||
// 1. Filter out error messages stored in DB
|
||||
const cleaned = messages.filter((m) => {
|
||||
if (m.role === 'assistant' && m.content.startsWith('抱歉,出错了:')) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// 2. Validate sequence: tool messages must follow assistant with tool_calls
|
||||
const result: DbMessage[] = [];
|
||||
for (let i = 0; i < cleaned.length; i++) {
|
||||
const msg = cleaned[i];
|
||||
|
||||
// Tool message: check if preceding assistant has tool_calls
|
||||
if (msg.role === 'tool') {
|
||||
let hasPrecedingToolCall = false;
|
||||
for (let j = result.length - 1; j >= 0; j--) {
|
||||
const prev = result[j];
|
||||
if (prev.role === 'assistant') {
|
||||
if (prev.tool_calls) {
|
||||
try {
|
||||
const parsed = JSON.parse(prev.tool_calls);
|
||||
const blocks = Array.isArray(parsed) ? parsed : parsed.content_blocks;
|
||||
if (blocks?.some((b: ContentBlock) => b.type === 'tool_use')) {
|
||||
hasPrecedingToolCall = true;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
break; // stop at nearest assistant
|
||||
}
|
||||
if (prev.role === 'tool') continue; // skip consecutive tool messages
|
||||
break;
|
||||
}
|
||||
if (!hasPrecedingToolCall) continue; // skip orphan tool message
|
||||
}
|
||||
|
||||
result.push(msg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function handleChatMessage(ws: WebSocket, convId: string, content: string) {
|
||||
const userMsgId = randomUUID();
|
||||
getDb().prepare(
|
||||
@@ -398,9 +440,10 @@ async function handleChatMessage(ws: WebSocket, convId: string, content: string)
|
||||
}
|
||||
getDb().prepare('UPDATE conversations SET updated_at = datetime(\'now\') WHERE id = ?').run(convId);
|
||||
|
||||
const history = getDb().prepare(
|
||||
const rawHistory = getDb().prepare(
|
||||
'SELECT * FROM messages WHERE conversation_id = ? AND id != ? ORDER BY created_at'
|
||||
).all(convId, userMsgId) as DbMessage[];
|
||||
const history = sanitizeHistory(rawHistory);
|
||||
|
||||
ws.send(JSON.stringify({ type: 'status', data: { status: 'thinking' } }));
|
||||
|
||||
@@ -419,9 +462,7 @@ async function handleChatMessage(ws: WebSocket, convId: string, content: string)
|
||||
const errMsg = (err as Error).message;
|
||||
console.error('[chat] LLM error:', errMsg);
|
||||
const errId = randomUUID();
|
||||
getDb().prepare(
|
||||
'INSERT INTO messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)'
|
||||
).run(errId, convId, 'assistant', `抱歉,出错了:${errMsg}`);
|
||||
// Don't store error in DB to avoid polluting history
|
||||
ws.send(JSON.stringify({
|
||||
type: 'message',
|
||||
data: { id: errId, role: 'assistant', content: `抱歉,出错了:${errMsg}` },
|
||||
|
||||
Reference in New Issue
Block a user