Spaces:
Running
Running
| 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; | |
| } | |
| } | |
| }; | |