manus-clone-cn / src /services /chatService.ts
Trae Assistant
fix: Restore API Key prompt and fix unused imports
5db8244
export interface Message {
role: 'user' | 'assistant' | 'system';
content: string;
timestamp?: number;
}
export interface ChatSession {
id: string;
title: string;
timestamp: number;
messages: Message[];
}
const API_URL = 'https://api.siliconflow.cn/v1/chat/completions';
const API_KEY_STORAGE_KEY = 'siliconflow_api_key';
const HISTORY_STORAGE_KEY = 'manus_chat_history';
const DEMO_SESSIONS: ChatSession[] = [
{
id: 'demo-1',
title: 'React 组件优化指南',
timestamp: Date.now() - 1000 * 60 * 60 * 2,
messages: [
{ role: 'user', content: '如何优化 React 组件的渲染性能?', timestamp: Date.now() - 1000 * 60 * 60 * 2 },
{ role: 'assistant', content: '### React 组件性能优化策略\n\n#### 1. 减少不必要的渲染\n- **使用 `React.memo`**:对于纯展示组件,使用 `memo` 包裹,避免父组件更新导致的不必要重渲染。\n- **`useCallback` 和 `useMemo`**:缓存函数和计算结果,避免作为 props 传递时导致子组件更新。\n\n#### 2. 状态管理优化\n- **状态下放 (State Colocation)**:将状态尽可能靠近使用它的组件,避免在顶层组件管理过多状态。\n- **Context 拆分**:避免将大对象放入 Context,导致所有消费者组件在部分属性变化时重渲染。\n\n#### 3. 列表渲染\n- **使用 `key`**:确保列表项有唯一且稳定的 `key`。\n- **虚拟滚动**:对于长列表,使用 `react-window` 或 `react-virtualized` 进行虚拟渲染。\n\n#### 4. 代码分割\n- **`React.lazy` + `Suspense`**:对路由组件或大型组件进行懒加载,减小首屏 bundle 体积。', timestamp: Date.now() - 1000 * 60 * 60 * 2 + 1000 }
]
},
{
id: 'demo-2',
title: 'Python 异步编程入门',
timestamp: Date.now() - 1000 * 60 * 60 * 24,
messages: [
{ role: 'user', content: 'Python 中的 async/await 是什么?', timestamp: Date.now() - 1000 * 60 * 60 * 24 },
{ role: 'assistant', content: '### Python 异步编程 (async/await) 简介\n\n`async` 和 `await` 是 Python 3.5 引入的用于编写并发代码的语法,基于 `asyncio` 库。\n\n#### 核心概念\n1. **协程 (Coroutine)**:使用 `async def` 定义的函数。调用它不会立即执行,而是返回一个协程对象。\n2. **事件循环 (Event Loop)**:负责调度和运行协程的机制。\n3. **Awaitable**:可以在 `await` 表达式中使用的对象(如协程、Task、Future)。\n\n#### 简单示例\n```python\nimport asyncio\n\nasync def say_hello():\n print("Hello")\n await asyncio.sleep(1) # 模拟IO操作\n print("World")\n\nasync def main():\n await asyncio.gather(say_hello(), say_hello())\n\nif __name__ == "__main__":\n asyncio.run(main())\n```\n\n#### 适用场景\n- **IO 密集型任务**:如网络请求、数据库读写。异步可以在等待 IO 时执行其他任务,极大提高吞吐量。\n- **Web 服务器**:如 FastAPI,天生支持异步,性能优越。', timestamp: Date.now() - 1000 * 60 * 60 * 24 + 1000 }
]
},
{
id: 'demo-3',
title: 'Docker 容器化部署',
timestamp: Date.now() - 1000 * 60 * 60 * 48,
messages: [
{ role: 'user', content: '编写一个简单的 Dockerfile', timestamp: Date.now() - 1000 * 60 * 60 * 48 },
{ role: 'assistant', content: '### Node.js 应用的 Dockerfile 示例\n\n这是一个标准的多阶段构建 Dockerfile,用于优化镜像体积:\n\n```dockerfile\n# Build Stage\nFROM node:18-alpine AS builder\nWORKDIR /app\nCOPY package*.json ./\nRUN npm ci\nCOPY . .\nRUN npm run build\n\n# Production Stage\nFROM node:18-alpine\nWORKDIR /app\nCOPY --from=builder /app/dist ./dist\nCOPY --from=builder /app/package*.json ./\nRUN npm ci --only=production\n\nEXPOSE 3000\nCMD ["node", "dist/index.js"]\n```\n\n#### 关键点解析\n- **Alpine 镜像**:使用 `alpine` 版本减小基础镜像体积。\n- **多阶段构建**:将构建环境(包含 devDependencies)和运行环境(仅生产依赖)分离。\n- **npm ci**:在 CI/CD 环境中使用,确保依赖版本与 lock 文件完全一致。', timestamp: Date.now() - 1000 * 60 * 60 * 48 + 1000 }
]
}
];
export const chatService = {
getApiKey(): string | null {
return localStorage.getItem(API_KEY_STORAGE_KEY);
},
setApiKey(key: string) {
localStorage.setItem(API_KEY_STORAGE_KEY, key);
},
getHistory(): ChatSession[] {
const stored = localStorage.getItem(HISTORY_STORAGE_KEY);
if (!stored) {
this.saveHistory(DEMO_SESSIONS);
return DEMO_SESSIONS;
}
let sessions: ChatSession[] = JSON.parse(stored);
if (sessions.length === 0) {
this.saveHistory(DEMO_SESSIONS);
return DEMO_SESSIONS;
}
let hasChanges = false;
// Data migration: Ensure all messages have timestamps
sessions = sessions.map(session => {
let sessionChanged = false;
const newMessages = session.messages.map((msg, index) => {
if (!msg.timestamp) {
sessionChanged = true;
hasChanges = true;
// Fallback: Use session timestamp + small offset to maintain order
const baseTime = session.timestamp || Date.now();
return { ...msg, timestamp: baseTime + (index * 1000) };
}
return msg;
});
if (sessionChanged) {
return { ...session, messages: newMessages };
}
return session;
});
// Persist changes if any migration happened
if (hasChanges) {
this.saveHistory(sessions);
}
return sessions;
},
saveHistory(sessions: ChatSession[]) {
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(sessions));
},
deleteSession(sessionId: string) {
const sessions = this.getHistory();
const newSessions = sessions.filter(s => s.id !== sessionId);
this.saveHistory(newSessions);
return newSessions;
},
updateSession(updatedSession: ChatSession) {
const sessions = this.getHistory();
const index = sessions.findIndex(s => s.id === updatedSession.id);
if (index !== -1) {
sessions[index] = updatedSession;
this.saveHistory(sessions);
}
return sessions;
},
createSession(initialMessage: string): ChatSession {
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
const session: ChatSession = {
id: generateUUID(),
title: initialMessage.slice(0, 20) + (initialMessage.length > 20 ? '...' : ''),
timestamp: Date.now(),
messages: [{ role: 'user', content: initialMessage, timestamp: Date.now() }]
};
return session;
},
async sendMessage(messages: Message[], onChunk: (content: string) => void, signal?: AbortSignal): Promise<void> {
let apiKey = this.getApiKey();
if (!apiKey) {
const input = prompt('Please enter your SiliconFlow API Key:');
if (input) {
this.setApiKey(input);
apiKey = input;
}
}
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey || ''}`
},
body: JSON.stringify({
model: 'deepseek-ai/DeepSeek-V3',
messages: messages,
stream: true
}),
signal // Pass the abort signal to fetch
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'API request failed');
}
const reader = response.body?.getReader();
const decoder = new TextDecoder('utf-8');
if (!reader) throw new Error('Response body is unavailable');
// Handle abort signal for the reader loop
if (signal) {
signal.addEventListener('abort', () => {
reader.cancel();
});
}
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
try {
const data = JSON.parse(line.slice(6));
const content = data.choices[0]?.delta?.content || '';
if (content) {
onChunk(content);
}
} catch (e) {
console.error('Error parsing chunk', e);
}
}
}
}
} catch (error) {
console.error('Chat error:', error);
throw error;
}
}
};