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 应用状态与数据流设计

理解这个应用的关键在于理清它的数据流。整个应用围绕“消息”和“会话”两个核心概念展开。

  1. 消息(Message) :这是最基本的单元,每条消息都有一个角色( user assistant system )和内容。内容可能是纯文本,也可能包含图片URL(当由DALL·E生成时)。
  2. 会话(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 关键交互细节优化

  1. 输入框与发送 :输入框应支持多行文本( textarea ),并监听 Enter 键发送(同时按 Shift+Enter 换行)。发送按钮在请求过程中应禁用并显示加载状态(如旋转图标)。
  2. 消息流式渲染 :如前所述,流式响应需要前端实时更新DOM。使用React时,应将流式接收到的文本片段追加到状态变量中,React的响应式更新会自动处理UI渲染。注意性能,避免每次追加都导致整个消息列表重渲染。
  3. 滚动定位 :当新消息到来或内容变长时,聊天区域应自动滚动到底部。可以使用 useRef 钩子获取聊天容器的DOM元素,并在消息更新后执行 scrollTop = scrollHeight
  4. 加载状态与错误提示 :任何网络请求都必须有明确的加载状态(骨架屏、加载动画)和错误提示(Toast通知、行内错误信息)。使用类似 react-hot-toast 的库可以优雅地展示通知。
  5. 移动端适配 :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 生产环境优化与安全加固

  1. 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)。

  2. 速率限制(Rate Limiting) :防止恶意用户短时间内发送大量请求。你可以在API路由中使用 rate-limiter-flexible 等库,或利用Vercel/Netlify的边缘函数中间件来实现IP级别的限流。

  3. 错误监控与日志 :生产环境需要监控API调用失败、未捕获的异常等。可以集成Sentry、Logtail等服务。在Vercel上,可以方便地查看函数调用日志。

  4. 成本控制 :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 功能扩展方向

这个项目是一个优秀的起点,你可以在此基础上添加更多实用功能:

  1. 对话语义搜索 :当历史会话很多时,如何快速找到某次关于“Python代码调试”的聊天?可以集成一个轻量级向量数据库(如 chromadb ),在后台将每条消息进行嵌入(embedding)存储,实现基于语义的搜索。
  2. 多模态输入 :除了文本,支持上传图片并由GPT-4V(Vision)进行解读,或者上传文件(PDF、Word)让AI总结内容。
  3. 语音交互 :集成浏览器的Web Speech API,实现语音输入和TTS(文本转语音)输出,打造真正的语音助手。
  4. 插件系统 :设计一个插件架构,允许动态加载功能模块,例如联网搜索、计算器、特定领域知识库查询等。
  5. 团队协作与共享 :引入用户系统后,可以支持共享会话链接、团队知识库共同编辑等功能。

7.3 性能与成本优化技巧

  1. 上下文管理的艺术 :GPT的token是成本的主要部分。不要无脑发送全部历史。可以尝试这些策略:a) 只发送最近10轮对话;b) 自动总结超长的早期对话,将总结作为系统提示词的一部分;c) 让用户手动选择“相关历史”范围。
  2. 图片缓存 :DALL·E返回的URL有效期仅一小时。如果希望用户能永久查看已生成的图片,后端需要在收到URL后,立即将其下载并存储到你自己的对象存储(如AWS S3、Cloudflare R2)或CDN,然后返回一个你自己的持久化链接给前端。
  3. 前端资源优化 :使用Vite的代码分割、懒加载组件(如React.lazy)。对于非首屏必需的模块(如设置页面、会话历史详情),可以动态导入。
  4. 边缘函数 :将API部署在Vercel的边缘网络上,可以显著降低用户请求的延迟,尤其是对于全球用户。

这个项目从技术实现到产品思维都给了我很多启发。它清晰地展示了一个现代AI Web应用的最小可行架构。在实际开发中,最大的挑战往往不是调用API本身,而是如何设计流畅的用户交互、如何优雅地处理错误和加载状态、以及如何在功能丰富性和系统复杂度之间取得平衡。我个人的体会是,从这样一个功能明确、结构清晰的项目出发,逐步添加自己需要的特性,是学习全栈开发和AI应用集成最高效的方式。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐