返回笔记首页

前端开发者做大模型应用开发 - 完整技能图谱

主题配置

一、前端开发者的优势与劣势

你已经有的优势

  • 用户体验意识强:知道怎么做交互才让用户舒服
  • 异步编程熟练:Promise、async/await用得飞起
  • API调用经验:fetch、axios调了几百次了
  • 状态管理能力:Redux、Zustand、Pinia玩得溜
  • 实时通信经验:WebSocket、SSE做过聊天室
  • 前后端协作经验:知道怎么对接后端接口

需要补充的短板

  • 后端开发经验:可能没写过后端服务
  • 数据库操作:SQL写得少
  • 向量检索概念:完全陌生
  • Python生态:习惯了JavaScript
  • Prompt工程:不知道怎么让AI听话

好消息: 前端开发者转做大模型应用开发,是最有优势的群体之一!因为80%的工作都在前端交互和用户体验上。


二、核心技能地图(按重要性排序)

Level 1:必须掌握(立即能用)

1. 大模型API调用(前端直接调用)

最简单的方式 - 前端直接调用
typescript
// 1. OpenAI官方SDK(最常用)
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.NEXT_PUBLIC_OPENAI_KEY,
  dangerouslyAllowBrowser: true // 仅开发用,生产环境要走后端
});

async function chat(message: string) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: message }],
    stream: true, // 流式返回,用户体验更好
  });

  for await (const chunk of response) {
    const text = chunk.choices[0]?.delta?.content || '';
    console.log(text); // 逐字显示
  }
}
实际项目中的正确做法
typescript
// 前端代码 - 调用你自己的后端API
async function chatWithAI(message: string) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message }),
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    // 更新UI显示
    appendToChat(text);
  }
}
你需要掌握的概念
  • 流式返回(Stream)vs 一次性返回
  • Token计数(影响成本)
  • Temperature参数(控制创造性)
  • System prompt vs User prompt
  • max_tokens限制

2. 流式响应处理(前端核心技能)

为什么要用流式?
  • GPT、Deepseek生成一个回答要5-10秒,用户不能干等
  • 逐字显示像打字机,用户体验好100倍
  • 类似ChatGPT的效果
三种实现方式
方式1:Server-Sent Events (SSE) - 最常用
typescript
// React组件示例
function ChatBox() {
  const [messages, setMessages] = useState<string[]>([]);
  const [currentResponse, setCurrentResponse] = useState('');

  async function sendMessage(text: string) {
    setCurrentResponse(''); // 清空上次的响应

    const eventSource = new EventSource(
      `/api/chat-stream?message=${encodeURIComponent(text)}`
    );

    eventSource.onmessage = (event) => {
      const chunk = event.data;
      setCurrentResponse(prev => prev + chunk); // 逐字累加
    };

    eventSource.onerror = () => {
      eventSource.close();
      setMessages(prev => [...prev, currentResponse]);
      setCurrentResponse('');
    };
  }

  return (
    <div>
      {messages.map((msg, i) => <p key={i}>{msg}</p>)}
      {currentResponse && <p className="typing">{currentResponse}</p>}
    </div>
  );
}
方式2:Fetch Stream - 更灵活
typescript
async function streamChat(message: string) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ message }),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  let fullText = '';

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    fullText += chunk;

    // 实时更新UI
    updateChatDisplay(fullText);
  }
}
方式3:WebSocket - 需要双向通信时
typescript
const ws = new WebSocket('ws://localhost:3000/chat');

ws.onopen = () => {
  ws.send(JSON.stringify({ message: 'Hello AI' }));
};

ws.onmessage = (event) => {
  const chunk = JSON.parse(event.data);
  appendText(chunk.text);
};

3. 打字机效果实现

基础版 - 手动控制速度
typescript
function useTypewriter(text: string, speed = 30) {
  const [displayText, setDisplayText] = useState('');

  useEffect(() => {
    let i = 0;
    const timer = setInterval(() => {
      if (i < text.length) {
        setDisplayText(text.slice(0, i + 1));
        i++;
      } else {
        clearInterval(timer);
      }
    }, speed);

    return () => clearInterval(timer);
  }, [text, speed]);

  return displayText;
}

// 使用
function AIResponse({ text }: { text: string }) {
  const displayed = useTypewriter(text);
  return <div>{displayed}<span className="cursor">|</span></div>;
}
进阶版 - 配合真实流式数据
typescript
function StreamingText({ stream }: { stream: AsyncIterable<string> }) {
  const [text, setText] = useState('');

  useEffect(() => {
    (async () => {
      for await (const chunk of stream) {
        setText(prev => prev + chunk);
        // 自动滚动到底部
        scrollToBottom();
      }
    })();
  }, [stream]);

  return <div className="markdown-body">{text}</div>;
}

4. Markdown渲染(AI回答都是Markdown格式)

必备库
bash
npm install react-markdown remark-gfm rehype-highlight
完整示例
typescript
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import 'highlight.js/styles/github-dark.css';

function AIMessage({ content }: { content: string }) {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]} // 支持表格、删除线等
      rehypePlugins={[rehypeHighlight]} // 代码高亮
      components={{
        // 自定义代码块样式
        code({ node, inline, className, children, ...props }) {
          if (inline) {
            return <code className="inline-code">{children}</code>;
          }
          return (
            <div className="code-block">
              <button onClick={() => copyCode(children)}>复制</button>
              <code className={className} {...props}>
                {children}
              </code>
            </div>
          );
        },
        // 自定义链接在新窗口打开
        a({ href, children }) {
          return <a href={href} target="_blank" rel="noopener">{children}</a>;
        },
      }}
    >
      {content}
    </ReactMarkdown>
  );
}

5. 对话历史管理

数据结构设计
typescript
interface Message {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  timestamp: number;
  tokens?: number; // 可选:记录消耗的token
}

interface Conversation {
  id: string;
  title: string; // 对话标题(通常用第一句话生成)
  messages: Message[];
  createdAt: number;
  updatedAt: number;
}
状态管理(Zustand示例)
typescript
import create from 'zustand';
import { persist } from 'zustand/middleware';

interface ChatStore {
  conversations: Conversation[];
  currentConvId: string | null;

  // 创建新对话
  createConversation: () => string;

  // 添加消息
  addMessage: (convId: string, message: Omit<Message, 'id' | 'timestamp'>) => void;

  // 删除对话
  deleteConversation: (convId: string) => void;

  // 切换对话
  setCurrentConversation: (convId: string) => void;
}

export const useChatStore = create<ChatStore>()(
  persist(
    (set, get) => ({
      conversations: [],
      currentConvId: null,

      createConversation: () => {
        const id = crypto.randomUUID();
        const newConv: Conversation = {
          id,
          title: '新对话',
          messages: [],
          createdAt: Date.now(),
          updatedAt: Date.now(),
        };
        set(state => ({
          conversations: [newConv, ...state.conversations],
          currentConvId: id,
        }));
        return id;
      },

      addMessage: (convId, message) => {
        const fullMessage: Message = {
          ...message,
          id: crypto.randomUUID(),
          timestamp: Date.now(),
        };

        set(state => ({
          conversations: state.conversations.map(conv =>
            conv.id === convId
              ? {
                  ...conv,
                  messages: [...conv.messages, fullMessage],
                  updatedAt: Date.now(),
                  // 如果是第一条消息,用它作为标题
                  title: conv.messages.length === 0
                    ? message.content.slice(0, 30)
                    : conv.title,
                }
              : conv
          ),
        }));
      },

      deleteConversation: (convId) => {
        set(state => ({
          conversations: state.conversations.filter(c => c.id !== convId),
          currentConvId: state.currentConvId === convId
            ? state.conversations[0]?.id || null
            : state.currentConvId,
        }));
      },

      setCurrentConversation: (convId) => {
        set({ currentConvId: convId });
      },
    }),
    {
      name: 'chat-storage', // localStorage key
    }
  )
);
使用示例
typescript
function ChatInterface() {
  const {
    conversations,
    currentConvId,
    addMessage,
    createConversation
  } = useChatStore();

  const currentConv = conversations.find(c => c.id === currentConvId);

  async function handleSend(text: string) {
    // 1. 添加用户消息
    addMessage(currentConvId!, { role: 'user', content: text });

    // 2. 调用AI
    const response = await chatWithAI(text);

    // 3. 添加AI回复
    addMessage(currentConvId!, { role: 'assistant', content: response });
  }

  return (
    <div className="chat-container">
      <MessageList messages={currentConv?.messages || []} />
      <InputBox onSend={handleSend} />
    </div>
  );
}

Level 2:重要但可以边做边学

6. 基础后端开发(Node.js/Next.js)

为什么前端也要写后端?
  • API Key不能暴露在前端
  • 需要做访问控制和用户认证
  • 要记录日志、统计用量
  • 可能需要连接数据库
Next.js API Routes(最简单的后端)
typescript
// app/api/chat/route.ts
import { OpenAI } from 'openai';
import { StreamingTextResponse, OpenAIStream } from 'ai';

export const runtime = 'edge'; // 边缘运行时,更快

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  // 调用OpenAI
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages,
    stream: true,
  });

  // 转换为前端可读的流
  const stream = OpenAIStream(response);
  return new StreamingTextResponse(stream);
}
Vercel AI SDK(强烈推荐!)
typescript
import { useChat } from 'ai/react';

function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button type="submit">发送</button>
      </form>
    </div>
  );
}
这个SDK帮你搞定了流式、状态管理、错误处理,超级好用

7. Prompt工程(前端视角)

动态Prompt构建
typescript
interface PromptTemplate {
  system: string;
  examples?: Array<{ user: string; assistant: string }>;
  userMessage: string;
}

function buildPrompt(template: PromptTemplate): Message[] {
  const messages: Message[] = [
    { role: 'system', content: template.system },
  ];

  // 添加Few-shot示例
  if (template.examples) {
    template.examples.forEach(ex => {
      messages.push(
        { role: 'user', content: ex.user },
        { role: 'assistant', content: ex.assistant }
      );
    });
  }

  // 添加实际用户消息
  messages.push({ role: 'user', content: template.userMessage });

  return messages;
}

// 使用
const codeReviewPrompt: PromptTemplate = {
  system: `你是一个专业的代码审查专家。
分析代码时要关注:
1. 潜在的bug
2. 性能问题
3. 代码风格
4. 最佳实践

用中文回答,格式清晰。`,

  examples: [
    {
      user: 'const arr = [1,2,3]; for(let i=0; i<=arr.length; i++) console.log(arr[i]);',
      assistant: '发现问题:\n1. 边界错误:i<=arr.length 会导致访问undefined\n应改为:i<arr.length',
    },
  ],

  userMessage: userCode,
};

const messages = buildPrompt(codeReviewPrompt);
Prompt最佳实践(前端常用场景)
typescript
// 场景1:代码生成
const codeGenPrompt = `请生成一个React组件,要求:
- 使用TypeScript
- 使用Tailwind CSS
- 支持暗黑模式
- 添加完整的TypeScript类型定义

组件需求:${userRequirement}

返回格式:只返回代码,不要解释。`;

// 场景2:文档总结
const summaryPrompt = `请总结以下文档的核心内容,要求:
- 3-5个要点
- 每个要点一句话
- 用中文

文档内容:
${document}`;

// 场景3:数据提取(返回JSON)
const extractPrompt = `从以下文本中提取信息,以JSON格式返回。

期望格式:
{
  "name": "姓名",
  "email": "邮箱",
  "phone": "电话"
}

文本:${text}

只返回JSON,不要其他内容。`;

8. 错误处理与重试机制

typescript
async function callAIWithRetry(
  message: string,
  maxRetries = 3
): Promise<string> {
  let lastError: Error | null = null;

  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        body: JSON.stringify({ message }),
        signal: AbortSignal.timeout(30000), // 30秒超时
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      return await response.text();

    } catch (error) {
      lastError = error as Error;

      // 区分不同错误类型
      if (error.name === 'AbortError') {
        console.log(`请求超时,重试 ${i + 1}/${maxRetries}`);
      } else if (error.message.includes('429')) {
        console.log('Rate limit,等待5秒后重试');
        await sleep(5000);
      } else if (error.message.includes('500')) {
        console.log('服务器错误,重试');
        await sleep(1000 * (i + 1)); // 指数退避
      } else {
        throw error; // 其他错误直接抛出
      }
    }
  }

  throw new Error(`请求失败,已重试${maxRetries}次: ${lastError?.message}`);
}

function sleep(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

9. Token计数与成本控制

前端实时估算Token
bash
npm install gpt-tokenizer
typescript
import { encode } from 'gpt-tokenizer';

function estimateTokens(text: string): number {
  return encode(text).length;
}

function estimateCost(tokens: number, model: string): number {
  const pricing = {
    'gpt-4': { input: 0.03 / 1000, output: 0.06 / 1000 },
    'gpt-3.5-turbo': { input: 0.0015 / 1000, output: 0.002 / 1000 },
  };

  const price = pricing[model];
  return tokens * price.input; // 简化计算
}

// 在发送前提示用户
function InputBox() {
  const [text, setText] = useState('');
  const tokens = estimateTokens(text);
  const cost = estimateCost(tokens, 'gpt-4');

  return (
    <div>
      <textarea value={text} onChange={e => setText(e.target.value)} />
      <div className="stats">
        大约 {tokens} tokens,预计花费 ${cost.toFixed(4)}
      </div>
    </div>
  );
}

10. 用户认证与权限控制

简单的API Key管理
typescript
// 前端 - 让用户输入自己的API Key
function Settings() {
  const [apiKey, setApiKey] = useState(
    localStorage.getItem('openai_key') || ''
  );

  function saveKey() {
    localStorage.setItem('openai_key', apiKey);
  }

  return (
    <div>
      <input
        type="password"
        value={apiKey}
        onChange={e => setApiKey(e.target.value)}
        placeholder="输入你的OpenAI API Key"
      />
      <button onClick={saveKey}>保存</button>
    </div>
  );
}

// 调用时带上Key
async function chat(message: string) {
  const apiKey = localStorage.getItem('openai_key');
  const response = await fetch('/api/chat', {
    headers: {
      'X-API-Key': apiKey,
    },
    body: JSON.stringify({ message }),
  });
}

Level 3:进阶技能(提升竞争力)

11. RAG(检索增强生成)- 前端集成

什么是RAG? 用户问"公司Q3财报怎么样?" → 不是让AI瞎猜,而是:

  1. 在公司文档库里搜索相关内容
  2. 把搜到的内容塞进Prompt
  3. 让AI基于这些内容回答
前端需要做的
typescript
// 1. 文件上传
function DocumentUpload() {
  async function handleUpload(file: File) {
    const formData = new FormData();
    formData.append('file', file);

    await fetch('/api/documents/upload', {
      method: 'POST',
      body: formData,
    });

    alert('文档已上传,正在处理...');
  }

  return <input type="file" onChange={e => handleUpload(e.target.files[0])} />;
}

// 2. 显示引用来源
interface Source {
  content: string;
  filename: string;
  page?: number;
}

function AnswerWithSources({ answer, sources }: {
  answer: string;
  sources: Source[]
}) {
  return (
    <div>
      <div className="answer">{answer}</div>

      <div className="sources">
        <h4>来源:</h4>
        {sources.map((src, i) => (
          <div key={i} className="source-card">
            <strong>{src.filename}</strong>
            {src.page && <span>第{src.page}页</span>}
            <p>{src.content.slice(0, 200)}...</p>
          </div>
        ))}
      </div>
    </div>
  );
}
调用RAG API
typescript
async function askWithRAG(question: string) {
  const response = await fetch('/api/rag/query', {
    method: 'POST',
    body: JSON.stringify({
      question,
      topK: 3, // 取最相关的3个文档片段
    }),
  });

  const data = await response.json();
  return {
    answer: data.answer,
    sources: data.sources,
  };
}

12. 函数调用(Function Calling)

让AI调用你定义的函数
typescript
// 定义可用的工具
const tools = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: '获取指定城市的天气',
      parameters: {
        type: 'object',
        properties: {
          city: { type: 'string', description: '城市名称' },
          unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
        },
        required: ['city'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'search_products',
      description: '在商品数据库中搜索',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string' },
          category: { type: 'string' },
        },
        required: ['query'],
      },
    },
  },
];

// 实际的函数实现
async function getWeather(city: string, unit = 'celsius') {
  const response = await fetch(`https://api.weather.com/${city}`);
  return response.json();
}

async function searchProducts(query: string, category?: string) {
  // 搜索你的商品数据库
  return await db.products.search({ query, category });
}

// 处理流程
async function chatWithTools(userMessage: string) {
  // 1. 发送消息和可用工具列表
  let response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: userMessage }],
    tools,
  });

  let message = response.choices[0].message;

  // 2. 如果AI要调用函数
  if (message.tool_calls) {
    for (const toolCall of message.tool_calls) {
      const functionName = toolCall.function.name;
      const args = JSON.parse(toolCall.function.arguments);

      // 3. 执行对应的函数
      let result;
      if (functionName === 'get_weather') {
        result = await getWeather(args.city, args.unit);
      } else if (functionName === 'search_products') {
        result = await searchProducts(args.query, args.category);
      }

      // 4. 把结果返回给AI
      response = await openai.chat.completions.create({
        model: 'gpt-4',
        messages: [
          { role: 'user', content: userMessage },
          message,
          {
            role: 'tool',
            tool_call_id: toolCall.id,
            content: JSON.stringify(result),
          },
        ],
      });

      message = response.choices[0].message;
    }
  }

  return message.content;
}
前端展示函数调用过程
typescript
function FunctionCallDisplay({ call }: { call: FunctionCall }) {
  return (
    <div className="function-call">
      <div className="icon">🔧</div>
      <div>
        调用函数: <code>{call.name}</code>
        <pre>{JSON.stringify(call.arguments, null, 2)}</pre>
      </div>
    </div>
  );
}

13. 多模态交互(图片、语音)

图片上传并让AI分析
typescript
async function analyzeImage(file: File) {
  // 转成base64
  const base64 = await fileToBase64(file);

  const response = await openai.chat.completions.create({
    model: 'gpt-4-vision-preview',
    messages: [
      {
        role: 'user',
        content: [
          { type: 'text', text: '这张图片里有什么?' },
          {
            type: 'image_url',
            image_url: { url: `data:image/jpeg;base64,${base64}` }
          },
        ],
      },
    ],
  });

  return response.choices[0].message.content;
}

function fileToBase64(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const result = reader.result as string;
      resolve(result.split(',')[1]); // 去掉data:image/jpeg;base64,前缀
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}
语音输入(Web Speech API)
typescript
function VoiceInput({ onTranscript }: { onTranscript: (text: string) => void }) {
  const [isListening, setIsListening] = useState(false);

  function startListening() {
    const recognition = new (window as any).webkitSpeechRecognition();
    recognition.lang = 'zh-CN';
    recognition.continuous = false;

    recognition.onstart = () => setIsListening(true);
    recognition.onend = () => setIsListening(false);

    recognition.onresult = (event: any) => {
      const transcript = event.results[0][0].transcript;
      onTranscript(transcript);
    };

    recognition.start();
  }

  return (
    <button onClick={startListening}>
      {isListening ? '正在听...' : '🎤 语音输入'}
    </button>
  );
}

14. 向量搜索UI组件

文档相似度搜索
typescript
function DocumentSearch() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<SearchResult[]>([]);

  async function search() {
    const response = await fetch('/api/search', {
      method: 'POST',
      body: JSON.stringify({ query }),
    });

    const data = await response.json();
    setResults(data.results);
  }

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="搜索文档..."
      />
      <button onClick={search}>搜索</button>

      <div className="results">
        {results.map(r => (
          <div key={r.id} className="result-card">
            <h3>{r.title}</h3>
            <p>{r.content}</p>
            <div className="score">相似度: {(r.score * 100).toFixed(1)}%</div>
          </div>
        ))}
      </div>
    </div>
  );
}

15. 实时协作(多用户同时使用)

WebSocket + 共享对话
typescript
function CollaborativeChat({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<Message[]>([]);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    wsRef.current = new WebSocket(`ws://localhost:3000/room/${roomId}`);

    wsRef.current.onmessage = (event) => {
      const message = JSON.parse(event.data);
      setMessages(prev => [...prev, message]);
    };

    return () => wsRef.current?.close();
  }, [roomId]);

  function sendMessage(text: string) {
    wsRef.current?.send(JSON.stringify({
      type: 'user_message',
      content: text,
      userId: getCurrentUserId(),
    }));
  }

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>
          <Avatar userId={msg.userId} />
          {msg.content}
        </div>
      ))}
    </div>
  );
}

三、实战项目推荐(从易到难)

项目1:智能聊天机器人(入门)

技能练习

  • ✅ API调用
  • ✅ 流式响应
  • ✅ 对话历史
  • ✅ Markdown渲染
参考开源项目
  • ChatGPT-Next-Web
  • BetterChatGPT

项目2:AI代码助手(中级)

功能

  • 代码审查
  • 代码补全
  • 错误解释
  • 单元测试生成
技能练习
  • Prompt工程
  • Function Calling
  • Monaco Editor集成

项目3:企业知识库问答(进阶)

功能

  • 文档上传
  • 向量检索
  • 引用来源
  • 权限控制
技能练习
  • RAG
  • 后端开发
  • 数据库

项目4:AI作图工具(高级)

功能

  • 文字生成图片
  • 图片编辑
  • 风格转换
  • 历史记录
技能练习
  • DALL-E / Midjourney API
  • Canvas / Fabric.js
  • 图片处理

四、技术栈推荐

前端框架选择

plain
推荐:Next.js 14 (App Router)
- 自带API Routes,前后端一体
- 部署简单(Vercel一键部署)
- SSR/SSG支持好
- 大部分AI应用都在用

备选:
- Nuxt 3(Vue生态)
- Remix(如果你喜欢)

UI组件库

plain
推荐:shadcn/ui + Tailwind CSS
- 现代设计
- 复制即用,不需要npm install
- 暗黑模式支持好
- AI应用标配

备选:
- Chakra UI
- Ant Design
- Material UI

状态管理

plain
推荐:Zustand
- 轻量,学习成本低
- TypeScript支持好
- 配合persist中间件做持久化

备选:
- Redux Toolkit(如果团队在用)
- Jotai / Recoil

AI SDK

plain
必备:Vercel AI SDK
- 统一封装了各家API
- 自动处理流式响应
- React Hooks超好用

const { messages, input, handleSubmit } = useChat();

数据库

plain
开发阶段:
- SQLite(本地开发)
- Firebase(快速原型)

生产环境:
- PostgreSQL + Prisma ORM(推荐)
- Supabase(PostgreSQL + 即时API)

向量数据库(做RAG时用)

plain
推荐:Pinecone
- 托管服务,不用自己部署
- 免费额度够用
- API简单

备选:
- Supabase Vector(如果你已经在用Supabase)
- Weaviate(开源,功能强)

五、学习路线(30天速成)

Week 1:基础搭建

Day 1-2:环境配置

  • 注册OpenAI账号,获取API Key
  • 搭建Next.js项目
  • 配置Tailwind CSS
Day 3-5:第一个聊天应用
  • 实现基本的对话界面
  • 接入OpenAI API
  • 添加流式响应
Day 6-7:优化体验
  • 添加Markdown渲染
  • 实现打字机效果
  • 添加对话历史

Week 2:进阶功能

Day 8-10:状态管理

  • 用Zustand管理对话
  • 实现多对话切换
  • LocalStorage持久化
Day 11-12:Prompt工程
  • 学习Prompt最佳实践
  • 实现System Prompt设置
  • 添加Few-shot示例
Day 13-14:错误处理
  • 添加加载状态
  • 实现重试机制
  • 优雅的错误提示

Week 3:后端开发

Day 15-17:API Routes

  • 把API Key移到后端
  • 添加用户认证
  • 记录日志和统计
Day 18-19:数据库
  • 连接PostgreSQL
  • 存储对话历史
  • 实现用户系统
Day 20-21:部署上线
  • 部署到Vercel
  • 配置环境变量
  • 绑定域名

Week 4:高级特性

Day 22-24:RAG实现

  • 文档上传功能
  • 向量化处理
  • 检索并回答
Day 25-26:Function Calling
  • 定义可用函数
  • 处理函数调用
  • 展示调用过程
Day 27-28:多模态
  • 图片上传
  • 图片分析
  • 语音输入
Day 29-30:打磨细节
  • 响应式设计
  • 暗黑模式
  • 性能优化

六、避坑指南

❌ 常见错误1:API Key暴露

typescript
// 错误示范
const openai = new OpenAI({
  apiKey: 'sk-xxxxxx', // 千万不要硬编码在前端!
  dangerouslyAllowBrowser: true,
});

// 正确做法:通过后端代理
const response = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ message }),
});

❌ 常见错误2:没有处理流式中断

typescript
// 错误示范
for await (const chunk of stream) {
  appendText(chunk);
  // 如果用户点了停止,流还在继续
}

// 正确做法
const abortController = new AbortController();

async function streamChat() {
  try {
    for await (const chunk of stream) {
      if (abortController.signal.aborted) break;
      appendText(chunk);
    }
  } catch (e) {
    if (e.name === 'AbortError') {
      console.log('用户停止了生成');
    }
  }
}

// 停止按钮
<button onClick={() => abortController.abort()}>停止</button>

❌ 常见错误3:没有控制Token消耗

typescript
// 错误示范:把所有历史都发给AI
const response = await openai.chat.completions.create({
  messages: allMessages, // 可能有几千条消息!
});

// 正确做法:只取最近的N条
const recentMessages = messages.slice(-10); // 最近10条
const response = await openai.chat.completions.create({
  messages: [
    systemMessage,
    ...recentMessages,
  ],
});

❌ 常见错误4:UI卡顿

typescript
// 错误示范:每收到一个字符就更新整个组件
setMessages([...messages, newMessage]); // 每次都复制整个数组

// 正确做法:用reducer或者只更新最后一条
const [messages, dispatch] = useReducer(messageReducer, []);
dispatch({ type: 'APPEND_TO_LAST', text: chunk });

七、推荐学习资源

文档与教程

  1. Vercel AI SDK 官方文档(必看)
  2. OpenAI Cookbook(实战案例)
  3. LangChain.js 文档(如果要做RAG)

开源项目学习

  1. ChatGPT-Next-Web
    • GitHub: Yidadaa/ChatGPT-Next-Web
    • 学习:对话管理、UI设计
  2. Lobe Chat
    • GitHub: lobehub/lobe-chat
    • 学习:插件系统、多模态
  3. Dify
    • GitHub: langgenius/dify
    • 学习:工作流设计

视频课程

  1. B站搜索:
    • "Next.js AI应用开发"
    • "Vercel AI SDK教程"
    • "RAG实战"

八、薪资与职业发展

薪资范围(2026,前端转AI应用)

  • 初级(0-1年): 20-30k/月
    • 能做基本的聊天应用
    • 会接入API
  • 中级(1-3年): 30-50k/月
    • 能做RAG应用
    • 能优化性能和成本
    • 有完整项目经验
  • 高级(3+年): 50-80k/月
    • 能设计复杂系统架构
    • 熟悉多种AI能力集成
    • 能带团队

职业路径

plain
纯前端
  ↓
AI应用前端(会调API)
  ↓
全栈AI应用开发(前后端都会)
  ↓
AI产品架构师(懂产品+技术)

竞争优势

作为前端开发者,你的优势是:

  • ✅ 用户体验意识强(AI应用体验很重要)
  • ✅ 异步编程能力强(流式响应是核心)
  • ✅ 快速迭代能力(AI应用变化快)
  • ✅ 前端人才基数大(机会多)

九、总结:30天行动计划

Week 1:先跑起来

  • 搭建项目,调通第一个API
  • 实现基本聊天界面
  • 加入流式响应和Markdown

Week 2:做得好用

  • 完善对话管理
  • 学习Prompt工程
  • 优化用户体验

Week 3:做得专业

  • 搭建后端API
  • 加入数据库
  • 部署上线

Week 4:做得高级

  • 实现RAG或Function Calling
  • 加入多模态能力
  • 开源到GitHub