相关资源与链接

Web Course 42-基于 AI Chatbot 的二次开发:本地启动

这一节课主要涉及内容:介绍课程调整方向,并开始基于AI Chatbot项目的二次开发,包括项目本地启动、分析项目架构和功能模块,以及规划后续开发路线。

课程调整

背景

变更

  • 打算提升每节课的信息密度
  • 降低每周课程更新的频次
  • 课程会增加更多有意思的与时俱进的内容
  • 课程的生命周期会增长

本课计划

  • 本地启动项目
  • 使用 Cursor 解读代码

项目分析

  • 替换 API 为 DeepSeek、豆包各模型

  • 基础知识快速 Go Through

    • Next.js
    • React.js
    • React Server Components
    • NextAuth.js 认证系统
    • PostgreSQL 和 Mysql
    • Drizzle ORM
    • WebSocket
    • AI SDK
      • 了解 AI SDK 的 Provider 接口
      • 学习不同 AI 服务商的 API 集成方式
      • 了解流式响应处理
  • 第三方服务

  • UI 组件库

    • Radix UI:基础组件库
    • Tailwind CSS:样式系统
    • Framer Motion:动画效果
    • shadcn/ui:UI 组件集合
  • AI Chat 技术栈分析

    让我帮你分析这个基于 Next.js 的 AI 聊天项目。

    1. 核心功能

    1. AI 聊天功能
    • 支持多种 AI 模型切换 (OpenAI、Anthropic、Fireworks 等)
    • 实时流式对话响应
    • 支持代码、文本、图片等多种内容类型
    • 聊天历史记录保存
    1. 用户系统
    • 基于 NextAuth.js 的邮箱密码登录
    • 用户会话管理
    • 聊天记录与用户关联
    1. Artifacts 工作区
    # Artifacts
    
    Artifacts is a special user interface mode that allows you to have a workspace like interface along with the chat interface. This is similar to [ChatGPT's Canvas](<https://openai.com/index/introducing-canvas>) and [Claude's Artifacts](<https://www.anthropic.com/news/artifacts>).
    
    The template already ships with the following artifacts:
    
    - **Text Artifact**: A artifact that allows you to work with text content like drafting essays and emails.
    - **Code Artifact**: A artifact that allows you to write and execute code (Python).
    - **Image Artifact**: A artifact that allows you to work with images like editing, annotating, and processing images.
    - **Sheet Artifact**: A artifact that allows you to work with tabular data like creating, editing, and analyzing data.
    
    
    • 文本编辑器
    • 代码编辑器(支持 Python)
    • 图片生成和编辑
    • 表格数据处理
    1. UI/UX 特性
    • 响应式设计
    • 深色/浅色主题切换
    • 可折叠侧边栏
    • Toast 通知系统

    2. 二次开发建议

    1. 技术栈学习
    • Next.js App Router 和 React Server Components
    • TypeScript
    • NextAuth.js 认证系统
    • Drizzle ORM
    • Tailwind CSS
    • AI SDK
    1. 替换 Model Provider
    import { openai } from '@ai-sdk/openai';
    import { fireworks } from '@ai-sdk/fireworks';
    import {
      customProvider,
      extractReasoningMiddleware,
      wrapLanguageModel,
    } from 'ai';
    
    export const DEFAULT_CHAT_MODEL: string = 'chat-model-small';
    
    export const myProvider = customProvider({
      languageModels: {
        'chat-model-small': openai('gpt-4o-mini'),
        'chat-model-large': openai('gpt-4o'),
        'chat-model-reasoning': wrapLanguageModel({
          model: fireworks('accounts/fireworks/models/deepseek-r1'),
          middleware: extractReasoningMiddleware({ tagName: 'think' }),
        }),
        'title-model': openai('gpt-4-turbo'),
        'artifact-model': openai('gpt-4o-mini'),
      },
      imageModels: {
        'small-model': openai.image('dall-e-2'),
        'large-model': openai.image('dall-e-3'),
      },
    });
    
    
    • 了解 AI SDK 的 Provider 接口
    • 学习不同 AI 服务商的 API 集成方式
    • 了解流式响应处理
    1. 扩展登录方式
    import { compare } from 'bcrypt-ts';
    import NextAuth, { type User, type Session } from 'next-auth';
    import Credentials from 'next-auth/providers/credentials';
    
    import { getUser } from '@/lib/db/queries';
    
    import { authConfig } from './auth.config';
    
    interface ExtendedSession extends Session {
      user: User;
    }
    
    export const {
      handlers: { GET, POST },
      auth,
      signIn,
      signOut,
    } = NextAuth({
      ...authConfig,
      providers: [
        Credentials({
          credentials: {},
          async authorize({ email, password }: any) {
            const users = await getUser(email);
            if (users.length === 0) return null;
            // biome-ignore lint: Forbidden non-null assertion.
            const passwordsMatch = await compare(password, users[0].password!);
            if (!passwordsMatch) return null;
            return users[0] as any;
          },
        }),
      ],
      callbacks: {
        async jwt({ token, user }) {
          if (user) {
            token.id = user.id;
          }
    
          return token;
        },
        async session({
          session,
          token,
        }: {
          session: ExtendedSession;
          token: any;
        }) {
          if (session.user) {
            session.user.id = token.id as string;
          }
    
          return session;
        },
      },
    });
    
    
    • 学习 NextAuth.js 的 Provider 配置
    • OAuth 集成(Google、GitHub 等)
    • 了解 JWT 和 Session 管理
    1. 用户权限管理
    • 设计权限模型和数据库 Schema
    • 了解 Next.js 中间件机制
    import NextAuth from 'next-auth';
    
    import { authConfig } from '@/app/(auth)/auth.config';
    
    export default NextAuth(authConfig).auth;
    
    export const config = {
      matcher: ['/', '/:id', '/api/:path*', '/login', '/register'],
    };
    
    
    • 实现 RBAC (基于角色的访问控制)
    • API 路由权限控制
    1. 数据库扩展
    import type { InferSelectModel } from 'drizzle-orm';
    import {
      pgTable,
      varchar,
      timestamp,
      json,
      uuid,
      text,
      primaryKey,
      foreignKey,
      boolean,
    } from 'drizzle-orm/pg-core';
    
    export const user = pgTable('User', {
      id: uuid('id').primaryKey().notNull().defaultRandom(),
      email: varchar('email', { length: 64 }).notNull(),
      password: varchar('password', { length: 64 }),
    });
    
    export type User = InferSelectModel<typeof user>;
    
    
    • Drizzle ORM 的高级用法
    • 数据库关系设计
    • 数据迁移管理

    从 Vue/Nuxt 转到 React/Next.js,主要需要注意:

    1. React 的组件生命周期和 Hooks 概念
    2. Next.js 的服务端渲染方式与 Nuxt 的区别
    3. React 的状态管理方案(Context, Redux 等)
    4. TypeScript 在 React 项目中的应用

    这个项目是一个很好的学习资源,建议你先熟悉基础架构,然后逐步进行功能扩展。

  • AI Chat 功能模块分析

    让我帮你分析这个项目的主要功能模块:

    1. 核心聊天功能

    • Chat 组件:负责主要的聊天界面和交互
    export function Chat({
      id,
      initialMessages,
      selectedChatModel,
      selectedVisibilityType,
      isReadonly,
    }: {
      id: string;
      initialMessages: Array<Message>;
      selectedChatModel: string;
      selectedVisibilityType: VisibilityType;
      isReadonly: boolean;
    }) {
      const { mutate } = useSWRConfig();
    
      const {
        messages,
        setMessages,
        handleSubmit,
        input,
        setInput,
        append,
        isLoading,
        stop,
        reload,
      } = useChat({
        id,
        body: { id, selectedChatModel: selectedChatModel },
        initialMessages,
        experimental_throttle: 100,
        sendExtraMessageFields: true,
        generateId: generateUUID,
        onFinish: () => {
          mutate('/api/history');
        },
        onError: (error) => {
          toast.error('An error occured, please try again!');
        },
      });
    
      const { data: votes } = useSWR<Array<Vote>>(
        `/api/vote?chatId=${id}`,
        fetcher,
      );
    
      const [attachments, setAttachments] = useState<Array<Attachment>>([]);
      const isArtifactVisible = useArtifactSelector((state) => state.isVisible);
    
      return (
        <>
          <div className="flex flex-col min-w-0 h-dvh bg-background">
            <ChatHeader
              chatId={id}
              selectedModelId={selectedChatModel}
              selectedVisibilityType={selectedVisibilityType}
              isReadonly={isReadonly}
            />
    
            <Messages
              chatId={id}
              isLoading={isLoading}
              votes={votes}
              messages={messages}
              setMessages={setMessages}
              reload={reload}
              isReadonly={isReadonly}
              isArtifactVisible={isArtifactVisible}
            />
    
            <form className="flex mx-auto px-4 bg-background pb-4 md:pb-6 gap-2 w-full md:max-w-3xl">
              {!isReadonly && (
                <MultimodalInput
                  chatId={id}
                  input={input}
                  setInput={setInput}
                  handleSubmit={handleSubmit}
                  isLoading={isLoading}
                  stop={stop}
                  attachments={attachments}
                  setAttachments={setAttachments}
                  messages={messages}
                  setMessages={setMessages}
                  append={append}
                />
              )}
            </form>
          </div>
    
          <Artifact
            chatId={id}
            input={input}
            setInput={setInput}
            handleSubmit={handleSubmit}
            isLoading={isLoading}
            stop={stop}
            attachments={attachments}
            setAttachments={setAttachments}
            append={append}
            messages={messages}
            setMessages={setMessages}
            reload={reload}
            votes={votes}
            isReadonly={isReadonly}
          />
        </>
      );
    }
    
    
    • AI 模型集成:支持多个 AI 提供商的语言和图像模型
    export const myProvider = customProvider({
      languageModels: {
        'chat-model-small': openai('gpt-4o-mini'),
        'chat-model-large': openai('gpt-4o'),
        'chat-model-reasoning': wrapLanguageModel({
          model: fireworks('accounts/fireworks/models/deepseek-r1'),
          middleware: extractReasoningMiddleware({ tagName: 'think' }),
        }),
        'title-model': openai('gpt-4-turbo'),
        'artifact-model': openai('gpt-4o-mini'),
      },
      imageModels: {
        'small-model': openai.image('dall-e-2'),
        'large-model': openai.image('dall-e-3'),
      },
    });
    
    

    2. Artifacts 工作区系统

    这是一个独特的功能,提供了类似 ChatGPT 的 Canvas 功能,包含四种主要类型:

    • 文本编辑器 (Text Artifact)
    export const textDocumentHandler = createDocumentHandler<'text'>({
      kind: 'text',
      onCreateDocument: async ({ title, dataStream }) => {
        let draftContent = '';
    
        const { fullStream } = streamText({
          model: myProvider.languageModel('artifact-model'),
          system:
            'Write about the given topic. Markdown is supported. Use headings wherever appropriate.',
          experimental_transform: smoothStream({ chunking: 'word' }),
          prompt: title,
        });
    
        for await (const delta of fullStream) {
          const { type } = delta;
    
          if (type === 'text-delta') {
            const { textDelta } = delta;
    
            draftContent += textDelta;
    
            dataStream.writeData({
              type: 'text-delta',
              content: textDelta,
            });
          }
        }
    
        return draftContent;
      },
      onUpdateDocument: async ({ document, description, dataStream }) => {
        let draftContent = '';
    
        const { fullStream } = streamText({
          model: myProvider.languageModel('artifact-model'),
          system: updateDocumentPrompt(document.content, 'text'),
          experimental_transform: smoothStream({ chunking: 'word' }),
          prompt: description,
          experimental_providerMetadata: {
            openai: {
              prediction: {
                type: 'content',
                content: document.content,
              },
            },
          },
        });
    
        for await (const delta of fullStream) {
          const { type } = delta;
    
          if (type === 'text-delta') {
            const { textDelta } = delta;
    
            draftContent += textDelta;
            dataStream.writeData({
              type: 'text-delta',
              content: textDelta,
            });
          }
        }
    
        return draftContent;
      },
    });
    
    
    • 代码编辑器 (Code Artifact)
    • 支持 Python 代码编写和执行
    • 使用 CodeMirror 作为编辑器
    • 图像生成器 (Image Artifact)
    export const imageDocumentHandler = createDocumentHandler<'image'>({
      kind: 'image',
      onCreateDocument: async ({ title, dataStream }) => {
        let draftContent = '';
    
        const { image } = await experimental_generateImage({
          model: myProvider.imageModel('small-model'),
          prompt: title,
          n: 1,
        });
    
        draftContent = image.base64;
    
        dataStream.writeData({
          type: 'image-delta',
          content: image.base64,
        });
    
        return draftContent;
      },
      onUpdateDocument: async ({ description, dataStream }) => {
        let draftContent = '';
    
        const { image } = await experimental_generateImage({
          model: myProvider.imageModel('small-model'),
          prompt: description,
          n: 1,
        });
    
        draftContent = image.base64;
    
        dataStream.writeData({
          type: 'image-delta',
          content: image.base64,
        });
    
        return draftContent;
      },
    });
    
    
    • 表格处理器 (Sheet Artifact)
    • 使用 react-data-grid 处理表格数据

    3. 认证系统

    基于 NextAuth.js 的认证系统,目前支持:

    • 邮箱密码登录
    • 会话管理
    • 访问控制

    4. 数据持久化

    使用 Vercel 的存储解决方案:

    • Vercel Postgres:存储聊天历史和用户数据
    • Vercel Blob:文件存储
    • Drizzle ORM:数据库操作

    5. UI 组件系统

    • Radix UI:基础组件库
    • Tailwind CSS:样式系统
    • Framer Motion:动画效果
    • shadcn/ui:UI 组件集合

    6. 开发工具链

        "dev": "next dev --turbo",
        "build": "tsx lib/db/migrate && next build",
        "start": "next start",
        "lint": "next lint && biome lint --write --unsafe",
        "lint:fix": "next lint --fix && biome lint --write --unsafe",
        "format": "biome format --write",
        "db:generate": "drizzle-kit generate",
        "db:migrate": "npx tsx lib/db/migrate.ts",
        "db:studio": "drizzle-kit studio",
        "db:push": "drizzle-kit push",
        "db:pull": "drizzle-kit pull",
        "db:check": "drizzle-kit check",
        "db:up": "drizzle-kit up"
      },
    
    

    包含完整的开发工具链:

    • TypeScript 支持
    • ESLint + Biome 代码检查
    • 数据库迁移工具
    • 开发服务器配置

    7. 实时数据流处理

    • 支持流式响应
    • 实时内容更新
    • WebSocket 通信

    8. 文档版本控制

      const [mode, setMode] = useState<'edit' | 'diff'>('edit');
      const [document, setDocument] = useState<Document | null>(null);
      const [currentVersionIndex, setCurrentVersionIndex] = useState(-1);
    
    
    • 支持文档版本历史
    • Diff 视图比较
    • 版本回滚功能

    这个项目的架构设计非常现代化,使用了最新的 Next.js 15 和 React 19 RC 版本,采用了 App Router 和 React Server Components。整体来看,它是一个功能完整的 AI 辅助创作平台,而不仅仅是一个简单的聊天机器人。

    在这个项目中,实际上并没有直接使用传统的 WebSocket,而是使用了更现代的流式响应技术。主要通过以下几个关键技术实现实时通信:

    1. AI SDK 的流式响应

    return createDataStreamResponse({
      execute: (dataStream) => {
        const result = streamText({
          model: myProvider.languageModel(selectedChatModel),
          system: systemPrompt({ selectedChatModel }),
          messages,
          maxSteps: 5,
          experimental_transform: smoothStream({ chunking: 'word' }),
          experimental_generateMessageId: generateUUID,
          tools: {
            getWeather,
            createDocument: createDocument({ session, dataStream }),
            updateDocument: updateDocument({ session, dataStream }),
            requestSuggestions: requestSuggestions({
              session,
              dataStream,
            }),
          },
          // ... 其他配置
        });
    
        result.mergeIntoDataStream(dataStream, {
          sendReasoning: true,
        });
      }
    });
    
    

    这里使用了 Vercel 的 AI SDK 提供的流式响应功能,基于:

    • Server-Sent Events (SSE)
    • ReadableStream API
    • Web Streams API

    2. 客户端实时数据处理

    const {
      messages,
      setMessages,
      handleSubmit,
      input,
      setInput,
      append,
      isLoading,
      stop,
      reload,
    } = useChat({
      id,
      body: { id, selectedChatModel: selectedChatModel },
      initialMessages,
      experimental_throttle: 100,
      sendExtraMessageFields: true,
      generateId: generateUUID,
      onFinish: () => {
        mutate('/api/history');
      },
      onError: (error) => {
        toast.error('An error occured, please try again!');
      },
    });
    
    

    使用了 AI SDK 提供的 useChat Hook,它内部实现了:

    • 自动处理流式响应
    • 消息状态管理
    • 错误处理
    • 重试机制

    3. DataStream 处理系统

    export type DataStreamDelta =
      | {
          type: 'text-delta';
          content: string;
        }
      | {
          type: 'image-delta';
          content: string;
        }
      | {
          type: 'code-delta';
          content: string;
        }
      | {
          type: 'sheet-delta';
          content: string;
        };
    
    export function DataStreamHandler({ id }: { id: string }) {
      useEffect(() => {
        const eventSource = new EventSource(`/api/chat/stream?id=${id}`);
    
        eventSource.onmessage = (event) => {
          const data = JSON.parse(event.data);
          // 处理流式数据
        };
    
        eventSource.onerror = (error) => {
          console.error('EventSource failed:', error);
          eventSource.close();
        };
    
        return () => {
          eventSource.close();
        };
      }, [id]);
    }
    
    

    项目实现了自定义的 DataStream 处理系统:

    • 使用 EventSource API
    • 支持多种数据类型的实时流
    • 错误处理和重连机制

    4. Artifacts 实时更新

    export const textDocumentHandler = createDocumentHandler<'text'>({
      onCreateDocument: async ({ title, dataStream }) => {
        let draftContent = '';
    
        const { fullStream } = streamText({
          model: myProvider.languageModel('artifact-model'),
          experimental_transform: smoothStream({ chunking: 'word' }),
          prompt: title,
        });
    
        for await (const delta of fullStream) {
          if (type === 'text-delta') {
            dataStream.writeData({
              type: 'text-delta',
              content: textDelta,
            });
          }
        }
        // ...
      }
    });
    
    

    Artifacts 系统使用了:

    • 自定义的流式数据处理
    • 实时内容更新机制
    • 状态同步系统

    5. SWR 用于数据同步

    const { data: votes } = useSWR<Array<Vote>>(
      `/api/vote?chatId=${id}`,
      fetcher,
    );
    
    

    使用 SWR 进行:

    • 数据缓存
    • 自动重新验证
    • 实时数据同步

    技术栈总结

    1. 基础设施:
      • Server-Sent Events (SSE)
      • ReadableStream API
      • Web Streams API
      • EventSource API
    2. 框架支持:
      • Next.js App Router
      • React Server Components
      • Edge Runtime
    3. 数据处理:
      • AI SDK 的流式处理
      • SWR 的数据同步
      • 自定义 DataStream 系统
    4. 状态管理:
      • React Hooks
      • SWR Cache
      • 客户端状态管理

    这种架构相比传统 WebSocket 有几个优势:

    1. 更适合单向的流式数据传输
    2. 更好的错误处理和重连机制
    3. 与 Edge Functions 更好的集成
    4. 更低的服务器资源消耗

    不过如果你需要双向实时通信,可以考虑添加 Socket.io 或 WebSocket 支持。

后续规划

  • 替换 API 模型为我们想要的模型,这个优先进行,因为可以马上用到
  • Go Through 所有涉及到的新的知识点
  • 增加新的登录/注册方式
  • 增加用户权限的管理功能
    • 后台管理界面
    • 用户权限控制
  • 扩展新的功能(后续逐渐探索)

课后作业

  • 拉取项目源码,本地启动,并配置成功
    • 大模型的 API(可选)
    • Neon 和 Vercel Blob 的配置

课程讨论与答疑

提问默认仅你和管理员可见;管理员精选后会作为公开讨论展示。

正在载入讨论…