Spaces:
Sleeping
Sleeping
File size: 8,953 Bytes
69a3dd3 5db8244 69a3dd3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
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;
}
}
};
|