基于OpenAI API构建全栈AI助手:ChatGPT与DALL·E集成实战
在现代Web开发中,API集成与全栈架构是构建智能应用的核心技术。通过调用外部AI服务API,开发者能够为应用注入强大的自然语言处理和图像生成能力,其原理在于将复杂的AI模型封装为标准的HTTP接口,实现功能解耦与灵活调用。这种技术价值在于大幅降低了AI应用的开发门槛,使开发者无需深入机器学习细节即可快速构建智能功能。典型的应用场景包括智能客服、内容创作工具和个性化助手等。本文以ChatGPT-P
1. 项目概述:一个集成了对话与图像生成的全能AI助手
最近在折腾AI应用开发,发现很多朋友都想拥有一个属于自己的、功能更全面的ChatGPT界面,既能流畅对话,又能根据描述生成图片。市面上虽然有不少开源项目,但要么功能单一,要么部署复杂。今天就来详细拆解一个我最近深度使用并做了不少优化的项目—— ChatGPT-Pro 。它本质上是一个基于OpenAI API构建的Web应用,核心卖点在于将ChatGPT(支持GPT-3.5和GPT-4)和DALL·E图像生成引擎无缝整合到了一个简洁美观的界面里。
对于开发者、产品经理或者AI爱好者来说,这个项目提供了一个绝佳的“样板间”。你不仅可以一键部署,体验融合了文本与图像生成的AI助手,更能通过其清晰的前后端结构,学习如何在实际项目中集成不同的AI模型、管理对话上下文、以及处理用户数据的本地持久化。它用到的技术栈非常现代且高效:前端是React + Vite + TailwindCSS,后端逻辑则简洁地通过API路由处理,这对于想快速上手AI应用全栈开发的朋友来说,学习曲线非常友好。接下来,我会从设计思路、环境搭建、核心功能实现到部署优化,完整地走一遍这个项目,并分享其中我踩过的坑和总结的经验。
2. 核心架构与设计思路解析
2.1 技术选型背后的考量
ChatGPT-Pro的技术栈组合看似常规,但每一项选择都针对AI Web应用的特点做了优化。前端选用 React + Vite ,看中的是Vite极快的冷启动和热更新速度。在开发AI对话应用时,我们经常需要修改组件状态来模拟流式响应,快速的HMR(热模块替换)能大幅提升开发效率。而 TailwindCSS 搭配 DaisyUI 组件库,则是为了在保证UI美观、一致的前提下,实现极高的开发效率。DaisyUI提供了诸如聊天气泡、按钮、输入框等预制样式,让我们能几乎不写自定义CSS就搭建出专业的聊天界面。
后端没有使用传统的Node.js框架(如Express),而是采用了 Next.js API Routes (根据项目结构推断)或类似的无服务器函数架构。这种选择非常适合此类以API调用为核心的应用。每个功能端点(如 /api/chat 、 /api/generate-image )都是一个独立的函数,逻辑清晰,也便于部署到Vercel、Netlify等支持Serverless的平台,实现自动扩缩容和低成本运行。
最核心的AI集成部分,项目直接使用了 OpenAI官方Node.js SDK 。这里没有引入更复杂的LangChain(尽管关键词中提到,但根据项目描述,核心逻辑可能并未深度使用),是为了保持项目的轻量和专注。对于主要需求是调用Chat Completions和Image Generations API的应用来说,官方SDK完全足够,而且更稳定,文档支持也更好。本地存储对话记录则用了浏览器的 localStorage ,这是一个简单有效的方案,保证了用户对话的隐私性(数据不出浏览器)和离线可用性(虽然调用API需要网络,但历史记录可离线查看)。
2.2 应用状态与数据流设计
理解这个应用的关键在于理清它的数据流。整个应用围绕“消息”和“会话”两个核心概念展开。
- 消息(Message) :这是最基本的单元,每条消息都有一个角色(
user、assistant或system)和内容。内容可能是纯文本,也可能包含图片URL(当由DALL·E生成时)。 - 会话(Conversation) :一个会话包含一组按顺序排列的消息,构成一次完整的对话上下文。应用需要能够创建新会话、切换不同会话、以及持久化保存会话。
前端的状态管理(通常使用React的 useState 或 useReducer )需要维护一个当前会话的消息列表,以及所有会话的元数据列表(如会话ID、标题、创建时间)。当用户发送一条新消息时,前端并不直接将其显示在界面上,而是先将其添加到本地状态的一个“pending”(等待中)状态,然后调用后端的聊天API。
后端API收到请求后,需要做几件事:首先是身份验证,验证API Key是否有效(通常由前端在请求头中传递,但更安全的做法是在后端环境变量中配置);然后,它会将当前会话的历史消息(作为上下文)连同新消息一起,按照OpenAI要求的格式组装成 messages 数组,调用 openai.chat.completions.create 方法。这里的一个关键点是 上下文窗口的管理 :GPT模型有token限制,不能无限制地发送全部历史。一个常见的策略是只发送最近N条消息,或者计算token数并截断最老的消息。项目中的“chat context”功能就是指的这个。
对于DALL·E图像生成,流程更直接:前端发送描述文本,后端调用 openai.images.generate ,收到图片URL后返回给前端,前端再将这条“assistant”消息(内容为图片)添加到当前会话中。
所有消息更新后,前端会同步将整个会话对象序列化后存入 localStorage 。这里的设计难点在于如何高效地更新 localStorage 而不引起性能问题。通常的做法是在会话切换或消息列表更新时进行存储,使用防抖(debounce)技术避免过于频繁的IO操作。
3. 从零开始:环境搭建与项目启动
3.1 前置条件与依赖安装
在开始编码之前,你需要准备好以下环境:
- Node.js :版本建议在16.x或以上。你可以从官网下载安装,或者使用nvm(Node Version Manager)来管理多个版本,这对于同时维护多个项目非常方便。
- npm 或 yarn 或 pnpm :Node.js的包管理器,随Node.js一同安装。本项目使用npm,但你也可以使用yarn或pnpm,命令略有不同。
- OpenAI API Key :这是项目的灵魂。你需要前往 OpenAI平台 注册账号,并在API Keys页面创建一个新的密钥。请务必妥善保管这个密钥,它就像你的信用卡密码。 千万不要 将它直接提交到GitHub等公开代码仓库。
首先,将项目代码克隆到本地:
git clone https://github.com/EyuCoder/chatgpt-pro.git
cd chatgpt-pro
接下来安装项目依赖。项目根目录下的 package.json 文件定义了所有需要的库。
npm install
这个过程会下载React、Vite、TailwindCSS、OpenAI SDK等所有依赖包。如果网络状况不佳,可以考虑配置npm镜像源(如淘宝源)来加速。
3.2 关键配置项详解
安装完成后,最重要的步骤是配置环境变量。项目通常会在根目录下提供一个环境变量模板文件,例如 .env.example 。你需要复制它并创建自己的 .env.local 文件(该文件通常被 .gitignore 忽略,以确保安全)。
cp .env.example .env.local
然后,用文本编辑器打开 .env.local 文件。你需要填充的最关键变量是:
OPENAI_API_KEY=sk-your-actual-secret-key-here
将 sk-your-actual-secret-key-here 替换为你从OpenAI平台获取的真实API密钥。
注意:安全第一 。
.env.local文件必须列入.gitignore。永远不要将包含真实API Key的代码提交到版本控制系统。在后续部署到Vercel或Netlify时,也需要在它们平台的环境变量设置页面中,以同样的键值对(OPENAI_API_KEY)进行配置。
此外,你可能还会看到其他可选配置,例如:
OPENAI_API_BASE_URL: 如果你需要使用代理或自定义的API端点(例如某些云服务商提供的OpenAI兼容接口),可以在这里设置。DEFAULT_MODEL: 设置默认使用的GPT模型,如gpt-3.5-turbo。NEXT_PUBLIC_...: 任何以NEXT_PUBLIC_开头的变量会被注入到前端浏览器环境中,请谨慎使用,不要在这里放置敏感信息。
配置完成后,就可以启动开发服务器了:
npm run dev
Vite会启动一个本地开发服务器,通常运行在 http://localhost:5173 。打开浏览器访问这个地址,你应该就能看到ChatGPT-Pro的界面了。如果页面空白或报错,请检查终端是否有错误日志,最常见的问题是环境变量未正确设置或依赖安装不完整。
4. 核心功能模块深度实现
4.1 对话聊天模块的实现与优化
聊天模块是这个应用的心脏。其核心是一个发送HTTP POST请求到后端API端点的函数。前端实现时,我强烈建议使用 fetch API并处理流式响应(streaming),这能极大提升用户体验,让回复像真正的ChatGPT一样逐字显示。
以下是一个简化的前端流式请求示例:
async function sendMessage(messages) {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages, model: selectedModel }),
});
if (!response.ok) throw new Error('Network response was not ok');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// 假设后端以SSE格式发送数据: "data: {\\"content\\": \\"chunk\\"}\\n\\n"
const lines = chunk.split('\\n\\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
assistantMessage += data.content;
// 更新UI,将assistantMessage实时显示在聊天框
updateUIWithStreamingText(assistantMessage);
}
}
}
// 流式结束,将完整的消息存入历史
saveMessageToHistory('assistant', assistantMessage);
}
后端API的实现则需要重点关注 错误处理 和 上下文管理 。下面是一个基于Next.js API Route的示例:
// pages/api/chat.js 或 app/api/chat/route.js (Next.js 13+ App Router)
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { messages, model = 'gpt-3.5-turbo' } = req.body;
try {
// 1. 可选:对messages进行上下文截断,防止token超限
const truncatedMessages = truncateMessagesByToken(messages, 4096); // 自定义函数
// 2. 调用OpenAI API,设置stream: true以启用流式传输
const stream = await openai.chat.completions.create({
model: model,
messages: truncatedMessages,
stream: true,
temperature: 0.7, // 控制创造性
});
// 3. 设置SSE响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 4. 流式返回数据
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
res.write(`data: ${JSON.stringify({ content })}\\n\\n`);
}
res.write('data: [DONE]\\n\\n');
res.end();
} catch (error) {
console.error('OpenAI API error:', error);
// 提供更友好的错误信息
let userMessage = '请求处理失败,请稍后重试。';
if (error.status === 429) userMessage = '请求过于频繁,请稍后再试。';
if (error.status === 401) userMessage = 'API密钥无效或过期。';
res.status(500).json({ error: userMessage });
}
}
实操心得:流式响应与错误处理 。流式响应(Server-Sent Events, SSE)的实现需要前后端配合。后端必须正确设置响应头,并以特定的
data: ...\\n\\n格式发送数据块。前端则需要用EventSource或fetch的ReadableStream来解析。错误处理至关重要,OpenAI API可能返回429(限速)、401(鉴权失败)、503(服务繁忙)等错误,必须捕获并给前端返回友好的提示,而不是让页面直接崩溃。
4.2 DALL·E图像生成模块的集成
图像生成模块相对独立。前端需要提供一个文本输入框和一个触发按钮。当用户输入描述(prompt)后,将其发送到类似 /api/generate-image 的端点。
后端的实现核心是调用 openai.images.generate 方法。有几个参数需要特别注意:
prompt: 用户输入的描述文本。清晰的描述能得到更好的图片。n: 生成图片的数量,默认为1。根据API定价,一次调用生成多张图片可能更经济,但也会消耗更多token。size: 图片尺寸,可选1024x1024、1792x1024或1024x1792。不同尺寸价格不同,1024x1024是最常用的。response_format: 返回格式,可以是url(图片URL,一小时后过期)或b64_json(Base64编码的图片数据)。对于Web应用,url更简单,但如果需要永久保存或进一步处理,b64_json是更好的选择。
// 后端API示例
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export default async function handler(req, res) {
const { prompt, size = '1024x1024', n = 1 } = req.body;
if (!prompt || prompt.trim().length === 0) {
return res.status(400).json({ error: 'Prompt is required' });
}
try {
const response = await openai.images.generate({
model: 'dall-e-3', // 或 'dall-e-2'
prompt: prompt,
n: parseInt(n),
size: size,
quality: 'standard', // dall-e-3 支持 'standard' 或 'hd'
style: 'vivid', // dall-e-3 支持 'vivid' 或 'natural'
});
const imageUrl = response.data[0].url; // 假设n=1
res.status(200).json({ url: imageUrl });
} catch (error) {
console.error('DALL·E API error:', error);
res.status(500).json({ error: '图像生成失败: ' + error.message });
}
}
前端收到图片URL后,可以创建一个新的消息对象,其内容类型为图片,并将其插入到当前会话中。同时,也需要将这条记录保存到 localStorage 。
4.3 对话持久化与本地存储策略
使用 localStorage 保存对话是一个简单直接的方案,但它有一些局限性:存储空间有限(通常5-10MB)、只能存字符串、且是同步操作可能阻塞主线程。
实现时,我们通常设计一个存储管理器:
const STORAGE_KEY = 'chatgpt-pro_conversations';
const storageManager = {
// 保存所有会话
saveConversations(conversations) {
try {
// 使用防抖,避免频繁写入
localStorage.setItem(STORAGE_KEY, JSON.stringify(conversations));
} catch (e) {
// 处理QuotaExceededError(存储空间不足)
console.error('Failed to save to localStorage:', e);
// 可以尝试清理旧数据或提示用户
this.clearOldConversations(conversations);
}
},
// 读取所有会话
loadConversations() {
try {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : [];
} catch (e) {
console.error('Failed to load from localStorage:', e);
return [];
}
},
// 清理最早的会话,直到满足大小限制
clearOldConversations(conversations, maxItems = 50) {
if (conversations.length > maxItems) {
// 按时间排序,删除最老的
conversations.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
const toKeep = conversations.slice(-maxItems);
this.saveConversations(toKeep);
return toKeep;
}
return conversations;
}
};
每个会话对象可以包含以下结构:
{
"id": "conv_123",
"title": "关于AI助手的讨论", // 可以自动生成,例如取第一条消息的前几个字
"createdAt": "2023-10-27T08:00:00Z",
"updatedAt": "2023-10-27T08:30:00Z",
"messages": [
{"role": "user", "content": "你好"},
{"role": "assistant", "content": "你好!有什么可以帮你的?"}
]
}
注意事项:数据迁移与备份 。
localStorage的数据与浏览器和域名绑定。一旦用户清空浏览器数据或更换设备,所有对话记录将丢失。对于真正需要持久化的生产级应用,应考虑引入后端数据库(如SQLite、PostgreSQL)或云存储服务。作为过渡方案,可以提供“导出/导入”功能,让用户能将对话数据以JSON文件的形式备份到本地。
5. 样式与用户体验打磨
5.1 利用TailwindCSS与DaisyUI快速构建UI
项目的UI之所以能快速成型且美观,很大程度上归功于TailwindCSS的效用优先(Utility-First)理念和DaisyUI的预制组件。例如,一个聊天消息气泡可能只需要如下代码:
<div className="chat chat-start">
<div className="chat-image avatar">
<div className="w-10 rounded-full">
<img src="/user-avatar.png" />
</div>
</div>
<div className="chat-bubble chat-bubble-primary">用户消息</div>
</div>
<div className="chat chat-end">
<div className="chat-image avatar">
<div className="w-10 rounded-full">
<img src="/bot-avatar.png" />
</div>
</div>
<div className="chat-bubble">AI回复消息</div>
</div>
DaisyUI的 chat 组件类直接提供了完整的布局和样式。对于模型切换按钮、设置面板等,也可以直接使用DaisyUI的 tabs 、 card 、 button 等类。
自定义主题也很方便。在 tailwind.config.js 中,可以覆盖DaisyUI的主题变量来匹配你的品牌色:
module.exports = {
// ...
daisyui: {
themes: [
{
mytheme: {
"primary": "#3b82f6", // 蓝色
"secondary": "#10b981", // 绿色
"accent": "#8b5cf6", // 紫色
"neutral": "#1f2937",
"base-100": "#ffffff",
},
},
],
},
}
5.2 关键交互细节优化
- 输入框与发送 :输入框应支持多行文本(
textarea),并监听Enter键发送(同时按Shift+Enter换行)。发送按钮在请求过程中应禁用并显示加载状态(如旋转图标)。 - 消息流式渲染 :如前所述,流式响应需要前端实时更新DOM。使用React时,应将流式接收到的文本片段追加到状态变量中,React的响应式更新会自动处理UI渲染。注意性能,避免每次追加都导致整个消息列表重渲染。
- 滚动定位 :当新消息到来或内容变长时,聊天区域应自动滚动到底部。可以使用
useRef钩子获取聊天容器的DOM元素,并在消息更新后执行scrollTop = scrollHeight。 - 加载状态与错误提示 :任何网络请求都必须有明确的加载状态(骨架屏、加载动画)和错误提示(Toast通知、行内错误信息)。使用类似
react-hot-toast的库可以优雅地展示通知。 - 移动端适配 :TailwindCSS的响应式工具类(如
md:、lg:)让移动端适配变得简单。确保在小屏幕上,侧边栏会话列表可以收起(可滑出抽屉),主聊天区域占据全屏。
6. 部署上线与生产环境考量
6.1 一键部署到Vercel/Netlify
这是项目最方便的特性之一。你只需要将你的代码库Fork或推送到GitHub,然后:
- Vercel :访问Vercel官网,点击“New Project”,导入你的GitHub仓库。在环境变量配置页,添加
OPENAI_API_KEY。Vercel会自动检测框架(Next.js)并完成构建部署。 - Netlify :过程类似,在Netlify点击“Add new site” -> “Import an existing project”,连接Git仓库,构建命令填写
npm run build,发布目录填写.next(如果是Next.js)或dist(如果是Vite默认输出),同样在环境变量设置中添加OPENAI_API_KEY。
实操心得:环境变量与构建 。在部署平台设置的环境变量,在构建阶段和运行时阶段都可能被需要。确保你的代码在构建时(如SSG)不会访问这些敏感变量,或者平台支持在构建时注入环境变量。对于Next.js,在
getStaticProps等函数中访问process.env,需要以NEXT_PUBLIC_为前缀的变量才会在客户端可用,否则只在服务端可用。
6.2 生产环境优化与安全加固
-
API路由保护 :目前的后端API是公开可访问的,这意味着任何人知道了你的部署地址都可以直接调用你的API端点,消耗你的OpenAI额度。 必须加固 。最简单的方法是添加一个简单的密钥验证:
// 在API路由开头添加 const AUTH_TOKEN = process.env.API_ROUTE_SECRET; const clientToken = req.headers.authorization?.split(' ')[1]; // Bearer token if (!AUTH_TOKEN || clientToken !== AUTH_TOKEN) { return res.status(401).json({ error: 'Unauthorized' }); }然后在你的前端请求头中带上这个令牌。更完善的方案是集成用户认证系统(如Auth.js、Supabase Auth)。
-
速率限制(Rate Limiting) :防止恶意用户短时间内发送大量请求。你可以在API路由中使用
rate-limiter-flexible等库,或利用Vercel/Netlify的边缘函数中间件来实现IP级别的限流。 -
错误监控与日志 :生产环境需要监控API调用失败、未捕获的异常等。可以集成Sentry、Logtail等服务。在Vercel上,可以方便地查看函数调用日志。
-
成本控制 :OpenAI API是按使用量计费的。除了前端设置使用说明,后端也应该考虑实施用量限制,例如为每个免费用户设置每日token上限。这需要结合用户系统来实现。
7. 常见问题排查与进阶扩展
7.1 开发与部署中的典型问题
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前端页面空白,控制台报错 | 1. 依赖未正确安装。 2. 环境变量未设置或设置错误。 3. 构建失败。 |
1. 删除 node_modules 和 package-lock.json ,重新运行 npm install 。 2. 检查 .env.local 文件是否存在且格式正确,变量名是否与代码中读取的(如 process.env.OPENAI_API_KEY )一致。 3. 查看终端 npm run dev 或 npm run build 的错误输出。 |
| 发送消息后返回“Network Error”或“401 Unauthorized” | 1. 后端API路由未正确处理请求。 2. OpenAI API Key无效或未正确传递。 |
1. 检查后端API路由的URL路径是否正确,请求方法(POST)是否匹配。 2. 重点检查 :确保后端环境变量 OPENAI_API_KEY 已设置且正确。在Vercel/Netlify上,需在项目设置中配置,而不是在代码里。可以在API路由中临时 console.log(process.env.OPENAI_API_KEY) (部署后查看平台日志)来验证。 |
| 流式响应不工作,一次性返回全部内容 | 1. 后端未设置正确的SSE响应头。 2. 后端未使用 stream: true 参数调用OpenAI。 3. 前端未正确解析流式数据。 |
1. 确认后端响应头包含 Content-Type: text/event-stream 等。 2. 确认 openai.chat.completions.create 调用中设置了 stream: true 。 3. 使用本文前面提供的流式读取示例代码,或使用专门的库如 eventsource-parser 。 |
localStorage 数据丢失 |
1. 用户清除了浏览器数据。 2. 存储格式变化导致解析失败。 3. 存储空间超限。 |
1. 这是 localStorage 的固有缺陷,考虑添加导出备份功能。 2. 在 loadConversations 方法中添加 try...catch ,解析失败时返回空数组并清理损坏数据。 3. 实现如 clearOldConversations 的自动清理逻辑。 |
| 部署后访问显示404 | 1. 单页应用(SPA)路由未正确配置。 2. 构建输出目录错误。 |
1. 在Vercel/Netlify等平台,需要配置重写规则,将所有非静态文件请求重定向到 index.html (Vercel对Next.js已自动处理)。 2. 确认发布目录设置正确(Next.js通常是 .next 或 out ,Vite是 dist )。 |
7.2 功能扩展方向
这个项目是一个优秀的起点,你可以在此基础上添加更多实用功能:
- 对话语义搜索 :当历史会话很多时,如何快速找到某次关于“Python代码调试”的聊天?可以集成一个轻量级向量数据库(如
chromadb),在后台将每条消息进行嵌入(embedding)存储,实现基于语义的搜索。 - 多模态输入 :除了文本,支持上传图片并由GPT-4V(Vision)进行解读,或者上传文件(PDF、Word)让AI总结内容。
- 语音交互 :集成浏览器的Web Speech API,实现语音输入和TTS(文本转语音)输出,打造真正的语音助手。
- 插件系统 :设计一个插件架构,允许动态加载功能模块,例如联网搜索、计算器、特定领域知识库查询等。
- 团队协作与共享 :引入用户系统后,可以支持共享会话链接、团队知识库共同编辑等功能。
7.3 性能与成本优化技巧
- 上下文管理的艺术 :GPT的token是成本的主要部分。不要无脑发送全部历史。可以尝试这些策略:a) 只发送最近10轮对话;b) 自动总结超长的早期对话,将总结作为系统提示词的一部分;c) 让用户手动选择“相关历史”范围。
- 图片缓存 :DALL·E返回的URL有效期仅一小时。如果希望用户能永久查看已生成的图片,后端需要在收到URL后,立即将其下载并存储到你自己的对象存储(如AWS S3、Cloudflare R2)或CDN,然后返回一个你自己的持久化链接给前端。
- 前端资源优化 :使用Vite的代码分割、懒加载组件(如React.lazy)。对于非首屏必需的模块(如设置页面、会话历史详情),可以动态导入。
- 边缘函数 :将API部署在Vercel的边缘网络上,可以显著降低用户请求的延迟,尤其是对于全球用户。
这个项目从技术实现到产品思维都给了我很多启发。它清晰地展示了一个现代AI Web应用的最小可行架构。在实际开发中,最大的挑战往往不是调用API本身,而是如何设计流畅的用户交互、如何优雅地处理错误和加载状态、以及如何在功能丰富性和系统复杂度之间取得平衡。我个人的体会是,从这样一个功能明确、结构清晰的项目出发,逐步添加自己需要的特性,是学习全栈开发和AI应用集成最高效的方式。
更多推荐



所有评论(0)