第 9 课:完整项目实战
一个请求的完整生命周期
这一课我们跟踪一个真实请求,从用户点击"发送"到下载 Excel 文件,看看每一步发生了什么。
用户请求:"帮我做一个健身打卡表,包含日期、运动类型、时长、消耗卡路里,底部要有本周汇总"
第 1 站:MessageInput 组件
用户在输入框里打字 → 点击"发送"按钮
MessageInput 组件:
1. 获取输入文本
2. 检查是否有附件文件(这次没有)
3. 调用 ChatInterface 的 sendMessage()
第 2 站:ChatInterface 组件
// ChatInterface.tsx
const sendMessage = async (content: string) => {
// 1. 先把用户消息显示在界面上
setMessages(prev => [...prev, {
role: 'user',
content: '帮我做一个健身打卡表...'
}]);
// 2. 设置 loading 状态
setIsLoading(true);
// 3. 通过 IPC 发送给主进程
window.electron.ipcRenderer.sendMessage('claude-code:query', {
content: '帮我做一个健身打卡表...',
files: [],
});
};
第 3 站:Electron 主进程
// main.ts
ipcMain.on('claude-code:query', async (event, { content, files }) => {
// 1. 记录当前已有的 xlsx 文件(用于后面检测新文件)
const initialFiles = ['old_report.xlsx']; // 之前的文件
// 2. 创建 AbortController
abortController = new AbortController();
// 3. 调用 Claude Agent SDK
const queryIterator = query({
prompt: '帮我做一个健身打卡表...',
options: {
cwd: './agent',
maxTurns: 100,
settingSources: ['local', 'project'],
allowedTools: ['Bash', 'Create', 'Edit', 'Read', 'Write',
'WebSearch', 'GrepTool', 'Skill',
'TodoWrite', 'TodoEdit'],
abortController,
},
});
// 4. 流式处理消息
for await (const message of queryIterator) {
messages.push(message);
event.reply('claude-code:response', message);
}
// 5. 检测新文件
// ...
});
第 4 站:Claude AI 开始工作
AI 收到任务后的内部决策过程:
AI 的思考链:
"用户要做健身打卡表 → 需要创建 Excel 文件
→ 我应该先读取 xlsx Skill 了解规范
→ 然后用 openpyxl 写 Python 代码
→ 执行代码生成 .xlsx
→ 验证公式"
第一轮:读取技能
AI → Skill 工具 → 读取 xlsx SKILL.md
消息流:
type: 'assistant'
content: [
{ type: 'text', text: '让我先了解一下 Excel 生成的规范...' },
{ type: 'tool_use', name: 'Skill', input: { command: 'xlsx' } }
]
前端收到这条消息 → 显示 "🎯 Using tool: Skill"
第二轮:创建任务列表
AI → TodoWrite 工具
消息流:
type: 'assistant'
content: [
{ type: 'tool_use', name: 'TodoWrite', input: {
todos: [
{ content: '创建健身打卡表结构', status: 'in_progress' },
{ content: '添加公式和格式', status: 'pending' },
{ content: '验证公式', status: 'pending' }
]
}}
]
前端收到 → TodoListDisplay 组件显示任务列表
第三轮:写 Python 代码
AI → Write 工具 → 创建 workout_tracker.py
消息流:
type: 'assistant'
content: [
{ type: 'text', text: '现在创建生成脚本...' },
{ type: 'tool_use', name: 'Write', input: {
file_path: 'workout_tracker.py',
content: 'from openpyxl import Workbook...'
}}
]
前端收到 → 显示 "✏️ Using tool: Write"
第四轮:执行 Python
AI → Bash 工具 → python3 workout_tracker.py
消息流:
type: 'assistant'
content: [
{ type: 'tool_use', name: 'Bash', input: {
command: 'python3 workout_tracker.py'
}}
]
然后:
type: 'result'
content: 'Workout tracker created successfully!'
前端收到 → 显示 "⚙️ Using tool: Bash" + 执行结果
第五轮:验证公式
AI → Bash 工具 → python3 .claude/skills/xlsx/recalc.py Workout_Tracker.xlsx
消息流:
type: 'result'
content: '{"status":"success","total_errors":0,"total_formulas":8}'
AI 看到"0 个错误",任务完成!
最后:AI 总结
AI 的回复:
"健身打卡表已创建!包含以下特性:
- 日期、运动类型、时长(分钟)、消耗卡路里四列
- 底部有 SUM 公式自动计算本周总时长和总卡路里
- 蓝色文字表示你可以修改的数据
- 表头已冻结,滚动时始终可见"
第 5 站:文件检测
// main.ts —— query() 循环结束后
const finalFiles = fs.readdirSync(outputDir)
.filter(f => f.endsWith('.xlsx'));
// 发现了 'Workout_Tracker.xlsx'(新文件!)
event.reply('claude-code:output-files', [{
name: 'Workout_Tracker.xlsx',
path: '/path/to/agent/Workout_Tracker.xlsx',
}]);
第 6 站:前端显示下载按钮
ChatInterface 收到 'claude-code:output-files'
→ 把文件信息关联到最后一条 AI 消息
→ Message 组件渲染下载按钮
界面效果:
🤖 AI:健身打卡表已创建!...
📥 [下载 Workout_Tracker.xlsx]
第 7 站:用户下载
用户点击 [下载 Workout_Tracker.xlsx]
↓
前端 → IPC('download-file', filePath)
↓
主进程 → dialog.showSaveDialog()
↓
用户选择保存位置
↓
主进程 → fs.copyFileSync(source, destination)
↓
文件保存到用户电脑
完整时序图
用户 React 前端 Electron 主进程 Claude AI
│ │ │ │
│──输入文字───→│ │ │
│ │──IPC:query──────→│ │
│ │ │──query()─────────→│
│ │ │ │
│ │ │←─msg: Skill──────│
│ │←─IPC:response────│ │
│ 看到工具展示 │ │ │
│ │ │←─msg: TodoWrite──│
│ │←─IPC:response────│ │
│ 看到任务列表 │ │ │
│ │ │←─msg: Write──────│
│ │←─IPC:response────│ │
│ 看到代码写入 │ │ │
│ │ │←─msg: Bash───────│
│ │←─IPC:response────│ │
│ 看到代码执行 │ │ │
│ │ │←─msg: text───────│
│ │←─IPC:response────│ │
│ 看到 AI 总结 │ │ │
│ │ │ │
│ │ │──检测新文件─→ │
│ │←─IPC:files───────│ │
│ 看到下载按钮 │ │ │
│ │ │ │
│──点击下载───→│ │ │
│ │──IPC:download───→│ │
│ │ │──另存为对话框──→ 用户电脑
│ 得到文件 │ │ │
带文件上传的场景
如果用户拖了一个 Excel 进来说"分析一下这个数据":
额外步骤:
1. MessageInput 验证文件类型和大小
2. IPC 发送 content + files
3. 主进程保存文件到 agent/problems/
Budget_1704747600000_a3b5c2.xlsx
4. prompt 里追加文件路径信息
5. AI 用 Read 工具读取文件
6. 后续流程一样
错误处理场景
如果 AI 生成的 Python 代码有 bug:
第 1 次 Bash 执行:
Error: NameError: name 'PatternFill' is not defined
AI 看到错误 → 自动修复(加上 import)
第 2 次 Bash 执行:
Workout tracker created successfully!
→ 继续验证公式 → 完成
本课小结
- 一个请求经过 7 站:输入 → 前端 → IPC → 主进程 → AI → 文件检测 → 下载
- AI 的工作通常是 5-6 轮:读技能 → 建任务 → 写代码 → 执行 → 验证
- 前端实时展示每一步的工具调用
- 主进程是"中转站",连接前端和 AI
- 有完善的错误处理和自动重试机制
课后练习
- 发送一个复杂请求(比如"做一个包含图表的销售分析"),数一数 AI 用了多少轮
- 故意发一个模糊请求("帮我做个表"),看看 AI 会不会追问你要什么内容
- 上传一个有问题的 Excel 文件,让 AI 帮你修复