1. 项目概述当AI工程化思维遇上Superpowers最近在社区里看到不少朋友在讨论AI应用开发尤其是如何快速把一个大模型的想法落地成一个可交互、可演示的Demo。很多人一上来就埋头写代码调API结果要么是代码结构混乱到后期无法维护要么是Demo跑起来但性能、扩展性一塌糊涂。这让我想起了自己早期踩过的那些坑。所以今天我想换个思路不单纯讲某个AI模型怎么调用而是聊聊如何用“AI工程化”的思维借助一个叫Superpowers的现代前端开发环境从零开始有条不紊地构建一个高质量的AI小Demo。简单来说这个项目标题“Superpowers 实战用 AI 工程化思维从零构建小Demo”的核心是方法论与工具链的结合。AI工程化思维指的是将软件工程中那些久经考验的最佳实践——比如模块化设计、清晰的开发流程、自动化测试、可维护的代码结构——系统地应用到AI驱动的应用开发中。它关注的不只是模型效果更是整个应用的生命周期如何高效开发、如何稳定部署、如何易于迭代。而Superpowers则是一个基于Web技术栈、集成了现代开发工具如Vite、TypeScript、ESLint等的快速开发环境或脚手架它能极大地简化前端项目的初始化、构建和开发体验让我们能把更多精力聚焦在AI功能逻辑本身而非环境配置上。这个Demo的目标用户可以是刚接触AI应用的前端开发者希望将大模型能力集成到网页中也可以是算法工程师想为自己的模型快速搭建一个展示界面甚至是产品经理想快速验证一个AI交互概念。无论你是谁通过这套方法你收获的将不仅仅是一个能跑的Demo更是一套可持续、可复用的开发模式。2. 核心思路拆解AI工程化与Superpowers的协同价值2.1 为什么需要AI工程化思维在构建AI Demo时我们常陷入“一次性脚本”的陷阱把所有代码堆在一个文件里API密钥硬编码UI和逻辑强耦合没有错误处理。这样做的结果是Demo脆弱不堪任何改动都可能引发连锁错误更别提后续增加功能或交给别人维护了。AI工程化思维要求我们从项目伊始就思考以下几个维度关注点分离将AI模型调用、业务逻辑、用户界面、状态管理、配置管理清晰地分开。例如模型服务调用应该封装成独立的模块或类UI组件只负责渲染和用户交互它们通过定义良好的接口进行通信。配置外部化API端点、密钥、模型参数等所有可变配置必须从代码中抽离放入环境变量或配置文件中。这是安全性和灵活性的基石。错误处理与用户体验大模型调用可能超时、返回非预期内容或直接失败。工程化思维要求我们预设这些情况并提供友好的用户反馈如加载状态、错误提示而不是让页面白屏或控制台报错。可测试性关键的业务逻辑和AI交互模块应该易于编写单元测试或集成测试确保核心功能稳定。开发体验利用现代工具链实现热重载、代码检查、自动格式化提升开发效率减少低级错误。2.2 Superpowers作为加速器的角色Superpowers这里我们将其理解为一个高度集成、开箱即用的现代前端开发套件的价值正是为上述工程化实践提供了“基础设施”。它通常预置了模块化与组件化支持天然支持Vue/React等组件化框架或原生ES模块让关注点分离变得顺理成章。TypeScript集成提供静态类型检查能在编码阶段就发现许多潜在错误对于AI API返回的复杂数据结构定义清晰的Interface或Type能极大提升开发效率和代码可靠性。内置构建工具如Vite或Webpack提供极速的热更新HMR让你修改代码后能立刻在浏览器看到效果这对调试AI交互界面至关重要。代码质量工具集成ESLint、Prettier强制保持代码风格一致避免格式争论。开发服务器与代理方便处理跨域问题并可以轻松配置API代理将前端请求转发到后端的AI服务。因此Superpowers解决了“环境搭建”和“开发体验”的痛点让我们能快速进入“业务逻辑开发”阶段而AI工程化思维则指导我们如何在Superpowers搭建好的舞台上编写出健壮、可维护的AI应用代码。两者结合是实现从零到一高效构建高质量Demo的关键。3. 实战准备定义Demo与初始化Superpowers项目3.1 定义我们的AI Demo智能会话助手为了具体说明我们构建一个经典的Demo一个基于大模型API的智能会话助手界面。它包含以下核心功能一个聊天界面展示对话历史。一个输入框允许用户发送消息。调用后端AI服务这里为了简化我们假设调用一个开源的或第三方的大语言模型API如DeepSeek、通义千问等提供的API获取回复。实时流式输出回复内容提升用户体验。管理对话历史并支持简单的上下文记忆。3.2 Superpowers项目初始化与环境配置假设我们选择了一个类似create-vite或特定框架CLI的Superpowers工具来初始化项目。这里以创建一个Vue 3 TypeScript Vite的项目为例这是目前非常流行且高效的组合。# 使用 npm create 命令快速创建项目 npm create vuelatest my-ai-chat-demo在创建过程中通过命令行交互选择需要的特性✅ TypeScript✅ JSX可选根据喜好✅ Vue Router可选如果Demo需要多页面✅ Pinia状态管理强烈推荐用于管理对话状态✅ ESLint Prettier代码质量项目创建完成后进入目录并安装依赖cd my-ai-chat-demo npm install接下来是工程化配置的关键一步环境变量管理。在项目根目录创建.env.development和.env.production文件。.env.development:VITE_APP_TITLEAI Chat Demo (Dev) VITE_API_BASE_URLhttp://localhost:3000/api # 假设你的后端代理地址 # 注意前端环境变量通常以 VITE_ 开头Vite才会将其暴露给客户端 # 敏感信息如API KEY绝对不应该放在前端环境变量中应通过后端服务转发.env.production:VITE_APP_TITLEAI Chat Demo VITE_API_BASE_URL/api # 生产环境使用相对路径或完整后端地址重要安全提示永远不要将真正的AI服务API密钥放入前端环境变量或代码中。前端代码对用户是透明的密钥会暴露。正确的做法是前端调用我们自己的后端服务可以是Node.js、Python Flask/FastAPI等编写的一个轻量代理由后端服务持有密钥并转发请求给AI服务商。我们的Demo架构应包含这个简单的后端代理。3.3 项目结构设计按照工程化思维我们规划一个清晰的项目结构src/ ├── api/ # 所有API请求封装 │ ├── chat.ts # 聊天相关的API函数 │ └── index.ts # API实例如基于axios的实例配置拦截器 ├── components/ # 可复用UI组件 │ ├── ChatMessage.vue │ └── MessageInput.vue ├── composables/ # Vue组合式函数或React hooks │ └── useChat.ts # 封装聊天核心逻辑状态、发送消息、流式处理 ├── stores/ # Pinia状态管理 │ └── chat.ts # 管理对话列表、当前会话状态 ├── types/ # TypeScript类型定义 │ └── chat.ts # 定义Message, Conversation等接口 ├── utils/ # 工具函数 │ └── streamParser.ts # 处理SSE或流式响应解析 ├── App.vue └── main.ts这个结构确保了模块职责单一便于协作和维护。4. 核心模块实现工程化下的AI功能集成4.1 类型定义与状态管理/types/chat.ts/stores/chat.ts首先我们使用TypeScript定义核心数据结构这是工程化的基础。/types/chat.ts:export interface Message { id: string; role: user | assistant | system; content: string; timestamp: number; } export interface Conversation { id: string; title: string; // 可以根据第一条消息生成 messages: Message[]; createdAt: number; } export type ChatStatus idle | loading | streaming | error;接着使用Pinia创建全局状态存储管理所有对话和当前状态。/stores/chat.ts:import { defineStore } from pinia; import { ref, computed } from vue; import type { Conversation, Message, ChatStatus } from /types/chat; import { generateUniqueId } from /utils/helpers; // 一个生成唯一ID的工具函数 export const useChatStore defineStore(chat, () { // 状态 const conversations refConversation[]([]); const currentConversationId refstring | null(null); const status refChatStatus(idle); const error refstring | null(null); // Getter const currentConversation computed(() conversations.value.find(c c.id currentConversationId.value) ); const currentMessages computed(() currentConversation.value?.messages || [] ); // Actions const createNewConversation (firstMessage?: string) { const newConv: Conversation { id: generateUniqueId(), title: firstMessage ? firstMessage.substring(0, 20) ... : 新对话, messages: [], createdAt: Date.now(), }; conversations.value.unshift(newConv); // 新对话放在最前面 currentConversationId.value newConv.id; return newConv.id; }; const addMessageToCurrentConversation (message: OmitMessage, id | timestamp) { const conv currentConversation.value; if (!conv) { const newConvId createNewConversation(message.content); // 递归调用此时currentConversation已更新 return addMessageToCurrentConversation(message); } const newMessage: Message { ...message, id: generateUniqueId(), timestamp: Date.now(), }; conv.messages.push(newMessage); }; const setStatus (newStatus: ChatStatus) { status.value newStatus; }; const setError (err: string | null) { error.value err; }; // 初始化一个对话 createNewConversation(); return { // 状态 conversations, currentConversationId, status, error, // Getter currentConversation, currentMessages, // Actions createNewConversation, addMessageToCurrentConversation, setStatus, setError, }; });这个Store集中管理了所有聊天状态任何组件需要读取或修改聊天数据都通过这个Store进行保证了数据流清晰、可预测。4.2 API层封装与错误处理/api/chat.tsAPI层是与后端服务通信的桥梁封装这里能统一处理请求、响应、错误和通用配置。首先在/api/index.ts中创建一个配置好的axios实例或使用fetch APIimport axios from axios; const apiClient axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, // 使用环境变量 timeout: 30000, // 30秒超时对于AI生成可以设长一些 headers: { Content-Type: application/json, }, }); // 请求拦截器可以在这里添加认证token等 apiClient.interceptors.request.use( (config) { // 示例从本地存储获取token // const token localStorage.getItem(token); // if (token) config.headers.Authorization Bearer ${token}; return config; }, (error) Promise.reject(error) ); // 响应拦截器统一处理错误 apiClient.interceptors.response.use( (response) response.data, // 直接返回data简化调用处代码 (error) { const message error.response?.data?.message || error.message || 网络请求失败; console.error(API请求错误:, error); // 可以在这里触发全局的错误提示 // 例如使用一个全局的UI toast组件 return Promise.reject(new Error(message)); } ); export default apiClient;然后在/api/chat.ts中封装具体的聊天接口import apiClient from ./index; import type { Message } from /types/chat; // 定义请求和响应体的类型 interface ChatCompletionRequest { messages: Array{ role: Message[role]; content: string }; stream?: boolean; // 是否使用流式输出 model?: string; // 模型名称 // ... 其他可能的参数如temperature, max_tokens等 } interface ChatCompletionResponse { id: string; choices: Array{ message: { role: string; content: string }; finish_reason: string; }; // ... 其他字段 } // 普通非流式调用 export const sendChatMessage async (params: ChatCompletionRequest): PromiseChatCompletionResponse { // 注意这里实际调用的是我们自己的后端代理由后端去调用真正的AI API const response await apiClient.postChatCompletionResponse(/v1/chat/completions, params); return response; }; // 流式调用使用Server-Sent Events, SSE export const sendChatMessageStream async ( params: ChatCompletionRequest, onChunk: (chunk: string, isDone: boolean) void ): Promisevoid { const requestParams { ...params, stream: true }; try { const response await fetch(${import.meta.env.VITE_API_BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(requestParams), }); if (!response.ok || !response.body) { throw new Error(HTTP error! status: ${response.status}); } const reader response.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) { onChunk(, true); // 通知流结束 break; } buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop() || ; // 最后一行可能不完整放回buffer for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6); if (data [DONE]) { onChunk(, true); return; } try { const parsed JSON.parse(data); const content parsed.choices[0]?.delta?.content || ; if (content) { onChunk(content, false); } } catch (e) { console.warn(解析SSE数据失败:, e, 原始数据:, data); } } } } } catch (error) { console.error(流式请求失败:, error); onChunk(, true); // 出错也标记结束 throw error; } };这个API层封装了两种调用方式并处理了复杂的流式响应解析将底层细节隐藏为上层业务逻辑提供干净的接口。4.3 核心逻辑封装组合式函数/composables/useChat.ts在Vue 3中组合式函数是封装可复用逻辑的利器。我们将聊天的核心交互逻辑封装在这里。import { ref, computed } from vue; import { useChatStore } from /stores/chat; import { sendChatMessageStream } from /api/chat; import type { Message } from /types/chat; export function useChat() { const store useChatStore(); const inputText ref(); const isStreaming ref(false); const accumulatedContent ref(); // 用于累积流式返回的内容 // 发送消息 const sendMessage async () { const text inputText.value.trim(); if (!text || store.status loading || store.status streaming) return; // 1. 添加用户消息到状态 store.addMessageToCurrentConversation({ role: user, content: text, }); inputText.value ; // 清空输入框 store.setStatus(streaming); isStreaming.value true; accumulatedContent.value ; store.setError(null); // 2. 准备发送给AI的消息历史通常只发送最近的若干条以控制上下文长度 const recentMessages store.currentMessages.slice(-10); // 取最后10条作为上下文 const messagesForApi recentMessages.map(msg ({ role: msg.role, content: msg.content, })); // 3. 创建并添加一个初始的、内容为空的助手消息占位 const assistantMessageId temp_${Date.now()}; store.addMessageToCurrentConversation({ role: assistant, content: , // 初始内容为空后续流式更新 }); // 获取刚添加的这条消息的引用在真实场景中可能需要更精细的状态管理 const lastMessageIndex store.currentMessages.length - 1; try { await sendChatMessageStream( { messages: messagesForApi, stream: true, model: deepseek-chat, // 示例模型 }, (chunk, isDone) { if (chunk) { accumulatedContent.value chunk; // 更新Store中最后一条消息的内容这里需要直接操作Store状态或使用Action // 为了响应式我们通过Store的action来更新 // 假设我们有一个updateLastMessageContent的action // 简化演示直接修改store.currentMessages在Pinia action外不推荐这里仅示意 // 更好的做法是在store中定义一个updateMessageContent的action const messages store.currentMessages; if (messages[lastMessageIndex]) { messages[lastMessageIndex].content accumulatedContent.value; } } if (isDone) { store.setStatus(idle); isStreaming.value false; // 流式结束可以做一些清理或最终处理 console.log(Stream finished.); } } ); } catch (err: any) { console.error(发送消息失败:, err); store.setStatus(error); store.setError(err.message || 请求失败请重试); isStreaming.value false; // 可选将失败的助手消息内容改为错误提示 const messages store.currentMessages; if (messages[lastMessageIndex]) { messages[lastMessageIndex].content 抱歉出错了: ${err.message}; } } }; return { inputText, isStreaming, sendMessage, // 暴露store的状态和getter以供组件使用 messages: computed(() store.currentMessages), status: computed(() store.status), error: computed(() store.error), }; }这个组合式函数useChat成为了连接UI、状态和API的枢纽。它处理了用户输入、管理加载状态、调用流式API并实时更新界面。组件只需要引入这个函数绑定数据和方法即可逻辑非常清晰。4.4 UI组件实现/components/有了坚实的底层支撑UI组件的工作就变得简单而专注。它们主要负责渲染和用户交互。ChatMessage.vue(用于渲染单条消息)template div :class[message, message--${message.role}] div classmessage-avatar{{ avatarText }}/div div classmessage-content div classmessage-role{{ roleName }}/div div classmessage-text{{ message.content }}/div div classmessage-time{{ formattedTime }}/div /div /div /template script setup langts import { computed } from vue; import type { Message } from /types/chat; import { formatTime } from /utils/helpers; const props defineProps{ message: Message; }(); const avatarText computed(() (props.message.role user ? 你 : AI)); const roleName computed(() (props.message.role user ? 用户 : 助手)); const formattedTime computed(() formatTime(props.message.timestamp)); /script style scoped .message { display: flex; padding: 1rem; border-bottom: 1px solid #eee; } .message--user { background-color: #f9f9f9; } .message--assistant { background-color: #fff; } .message-avatar { width: 36px; height: 36px; border-radius: 50%; background-color: #4a90e2; color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; margin-right: 1rem; flex-shrink: 0; } .message-content { flex: 1; } .message-role { font-size: 0.875rem; color: #666; margin-bottom: 0.25rem; } .message-text { white-space: pre-wrap; /* 保留换行符 */ line-height: 1.6; } .message-time { font-size: 0.75rem; color: #999; text-align: right; margin-top: 0.5rem; } /styleMessageInput.vue(输入框组件)template div classmessage-input textarea v-modellocalText keydown.enter.exact.preventhandleSend :placeholderplaceholder :disableddisabled rows3 classinput-area / button clickhandleSend :disableddisabled || !canSend classsend-button {{ buttonText }} /button /div /template script setup langts import { computed, ref, watch } from vue; const props defineProps{ modelValue: string; disabled?: boolean; isLoading?: boolean; placeholder?: string; }(); const emit defineEmits{ update:modelValue: [value: string]; send: []; }(); const localText ref(props.modelValue); watch(() props.modelValue, (newVal) { localText.value newVal; }); watch(localText, (newVal) { emit(update:modelValue, newVal); }); const canSend computed(() localText.value.trim().length 0); const buttonText computed(() (props.isLoading ? 思考中... : 发送)); const handleSend () { if (canSend.value !props.disabled) { emit(send); } }; /script style scoped .message-input { display: flex; border-top: 1px solid #ddd; padding: 1rem; background: #fff; } .input-area { flex: 1; padding: 0.75rem; border: 1px solid #ccc; border-radius: 8px; font-size: 1rem; resize: none; font-family: inherit; } .input-area:focus { outline: none; border-color: #4a90e2; } .send-button { margin-left: 1rem; padding: 0 1.5rem; background-color: #4a90e2; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1rem; align-self: flex-end; } .send-button:hover:not(:disabled) { background-color: #3a7bc8; } .send-button:disabled { background-color: #ccc; cursor: not-allowed; } /style最后在App.vue中将所有部分组合起来template div classapp-container header classapp-header h1{{ appTitle }}/h1 button clickcreateNewChat classnew-chat-btn新对话/button /header main classchat-main div classconversation-list !-- 左侧对话列表 -- div v-forconv in conversations :keyconv.id clickswitchConversation(conv.id) :class{ active: conv.id currentConversationId } classconv-item {{ conv.title }} /div /div div classchat-area div classmessages-container ChatMessage v-formsg in messages :keymsg.id :messagemsg / div v-ifstatus streaming classstreaming-indicator AI正在思考... /div /div div classinput-container MessageInput v-modelinputText sendsendMessage :disabledstatus streaming :is-loadingstatus streaming placeholder输入您的问题... / div v-iferror classerror-message{{ error }}/div /div /div /main /div /template script setup langts import { computed } from vue; import ChatMessage from ./components/ChatMessage.vue; import MessageInput from ./components/MessageInput.vue; import { useChatStore } from ./stores/chat; import { useChat } from ./composables/useChat; const appTitle import.meta.env.VITE_APP_TITLE; const store useChatStore(); const { inputText, sendMessage, messages, status, error, } useChat(); const conversations computed(() store.conversations); const currentConversationId computed(() store.currentConversationId); const createNewChat () { store.createNewConversation(); }; const switchConversation (id: string) { store.currentConversationId.value id; }; /script style /* 全局样式省略主要布局样式 */ .app-container { display: flex; flex-direction: column; height: 100vh; } .app-header { padding: 1rem; background: #2c3e50; color: white; display: flex; justify-content: space-between; align-items: center; } .chat-main { display: flex; flex: 1; overflow: hidden; } .conversation-list { width: 250px; border-right: 1px solid #ddd; overflow-y: auto; background: #f8f9fa; } .chat-area { flex: 1; display: flex; flex-direction: column; } .messages-container { flex: 1; overflow-y: auto; padding: 1rem; } .input-container { border-top: 1px solid #ddd; } /* ... 其他样式 */ /style至此一个具备工程化架构的AI聊天Demo前端部分就基本完成了。它结构清晰模块职责明确状态管理集中并且支持流式输出。5. 后端代理与部署考量5.1 实现一个简单的Node.js后端代理如前所述出于安全考虑我们需要一个后端服务来转发请求并添加API密钥。这里给出一个极简的Express.js示例// server.js import express from express; import cors from cors; import fetch from node-fetch; // 或者使用axios import dotenv from dotenv; dotenv.config(); // 加载.env文件 const app express(); const PORT process.env.PORT || 3000; app.use(cors()); // 允许前端跨域 app.use(express.json()); // 你的AI服务API密钥从环境变量读取 const AI_API_KEY process.env.AI_API_KEY; const AI_API_BASE process.env.AI_API_BASE || https://api.deepseek.com; app.post(/api/v1/chat/completions, async (req, res) { try { const { messages, stream, ...otherParams } req.body; const response await fetch(${AI_API_BASE}/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${AI_API_KEY}, }, body: JSON.stringify({ messages, stream, ...otherParams }), }); // 如果是流式响应直接pipe转发 if (stream) { res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); response.body.pipe(res); } else { const data await response.json(); res.json(data); } } catch (error) { console.error(代理请求失败:, error); res.status(500).json({ error: Internal Server Error }); } }); app.listen(PORT, () { console.log(后端代理服务器运行在 http://localhost:${PORT}); });你需要创建.env文件存放AI_API_KEY等敏感信息并运行npm install express cors dotenv node-fetch安装依赖。5.2 部署注意事项环境分离确保开发、测试、生产环境使用不同的配置环境变量。安全加固后端代理应增加请求频率限制、输入验证等防止滥用。前端构建使用npm run build构建前端静态文件可以部署到Vercel、Netlify、GitHub Pages或任何静态托管服务。后端部署Node.js服务可以部署到Railway、Render、或自己的云服务器。记得设置好环境变量。CORS生产环境下后端应精确配置CORS允许的源前端域名而不是简单的cors()。6. 常见问题、调试技巧与优化建议6.1 开发与调试中的常见问题流式响应不显示或中断检查后端代理是否正确处理了stream: true参数并正确设置了Content-Type: text/event-stream响应头。前端SSE解析逻辑streamParser.ts是否能处理各种边缘情况如数据块不完整、[DONE]信号。调试在浏览器开发者工具的“网络”选项卡中查看对代理接口的请求和响应。流式响应应该显示为“EventStream”类型并能看到持续的数据流。如果看不到问题可能在后端。TypeScript类型错误场景调用AI API返回的数据结构复杂定义类型时可能遗漏字段。技巧可以先使用any类型或更宽松的类型让代码跑起来然后在浏览器控制台打印出完整的响应对象根据实际结构来完善/types/目录下的接口定义。也可以利用一些在线工具将JSON响应快速转换为TypeScript接口。状态管理混乱现象UI显示不对或者操作后状态没有及时更新。排查充分利用Vue Devtools或React Devtools检查Pinia/Vuex/Context中的状态变化是否如预期。确保状态的修改都通过Actions/Mutations避免直接修改。环境变量未生效记住Vite中只有以VITE_开头的变量才会被嵌入到客户端代码中。在代码中通过import.meta.env.VITE_XXX访问。服务端环境变量如后端API密钥完全不同需要通过process.env访问且不会暴露给浏览器。6.2 性能与体验优化建议上下文长度管理大模型API通常有token限制。我们的Demo只发送了最近10条消息。在实际应用中可能需要更智能的策略比如计算token数或者总结历史对话。加载状态与骨架屏在消息发送和接收时提供清晰的加载指示如我们示例中的“思考中...”。对于对话列表首次加载时可以显示骨架屏提升感知性能。错误重试机制网络请求可能失败。可以为发送消息的函数增加重试逻辑例如最多重试2次并在UI上提供“重试”按钮。本地存储对话使用localStorage或IndexedDB将对话历史保存在浏览器本地避免页面刷新后丢失。注意定期清理避免存储过大。代码分割与懒加载如果Demo功能变多可以利用Vite/Rollup的代码分割功能将不同路由或非首屏组件拆分成独立的chunk加快初始加载速度。6.3 工程化思维的延伸这个Demo展示的是一个相对简单的场景。随着功能复杂化工程化思维需要进一步深化单元测试为/composables/useChat.ts、/api/chat.ts以及工具函数编写单元测试使用Vitest、Jest确保核心逻辑稳定。E2E测试使用Cypress或Playwright编写端到端测试模拟用户完整操作流程。CI/CD配置GitHub Actions或GitLab CI在代码推送时自动运行测试、代码检查ESLint和构建确保代码质量。文档与注释为复杂的业务逻辑和组件添加清晰的注释。可以考虑使用TypeDoc为TypeScript代码自动生成API文档。构建这个Demo的过程本质上是一次小型的“产品开发”实践。Superpowers提供了高效的生产工具而AI工程化思维确保了产品的内在质量。当你下次再有一个AI创意时不妨先花点时间规划一下项目结构思考一下状态如何管理、错误如何应对、API如何封装。这些前期投入会在你调试、扩展和维护时十倍地回报给你。