AI原生前端开发(016):端侧AI与React 19结合实战

前言

这一篇我们来把端侧AI和React 19完美结合!


1. 创建AI Context

jsx 复制代码
import React, { createContext, useContext, useState, useCallback, useRef } from 'react';

const AIContext = createContext(null);

// AI Provider
export function AIProvider({ children }) {
  const [session, setSession] = useState(null);
  const [isReady, setIsReady] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  // 初始化AI
  const initAI = useCallback(async (options = {}) => {
    try {
      setIsLoading(true);
      setError(null);

      if (!('ai' in window)) {
        throw new Error('当前浏览器不支持Chrome AI');
      }

      const newSession = await window.ai.assistant.create({
        systemPrompt: options.systemPrompt || '你是一个AI助手。',
        temperature: options.temperature || 0.7
      });

      setSession(newSession);
      setIsReady(true);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsLoading(false);
    }
  }, []);

  // 发送消息
  const sendMessage = useCallback(async (prompt) => {
    if (!session) {
      throw new Error('AI未初始化,请先调用initAI()');
    }

    return await session.prompt(prompt);
  }, [session]);

  // 流式发送
  const sendMessageStream = useCallback(async function* (prompt) {
    if (!session) {
      throw new Error('AI未初始化');
    }

    const stream = session.promptStreaming(prompt);
    for await (const chunk of stream) {
      yield chunk;
    }
  }, [session]);

  // 销毁
  const destroy = useCallback(() => {
    if (session) {
      session.destroy();
      setSession(null);
      setIsReady(false);
    }
  }, [session]);

  return (
    <AIContext.Provider value={{
      isReady,
      isLoading,
      error,
      initAI,
      sendMessage,
      sendMessageStream,
      destroy
    }}>
      {children}
    </AIContext.Provider>
  );
}

// Hook
export function useAI() {
  const context = useContext(AIContext);
  if (!context) {
    throw new Error('useAI must be used within AIProvider');
  }
  return context;
}

2. 完整的AI聊天应用

jsx 复制代码
import React, { useState, useRef, useEffect } from 'react';
import { AIProvider, useAI } from './ai-context';

function ChatMessage({ role, content }) {
  return (
    <div className={`p-4 rounded-lg max-w-[80%] ${role === 'user' ? 'ml-auto bg-blue-600 text-white' : 'bg-gray-100'}`}>
      <p>{content}</p>
    </div>
  );
}

function ChatApp() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [isGenerating, setIsGenerating] = useState(false);
  const messagesEndRef = useRef(null);
  const { isReady, initAI, sendMessageStream } = useAI();

  // 初始化
  useEffect(() => {
    if (!isReady) {
      initAI({
        systemPrompt: '你是一个友好的AI助手。用中文回答。'
      });
    }
  }, [isReady, initAI]);

  // 自动滚动到底部
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const handleSend = async () => {
    if (!input.trim() || isGenerating || !isReady) return;

    // 添加用户消息
    const userMessage = { role: 'user', content: input };
    setMessages(prev => [...prev, userMessage]);
    setInput('');
    setIsGenerating(true);

    try {
      // 添加AI回复占位符
      const aiMessageId = Date.now();
      setMessages(prev => [...prev, { role: 'assistant', content: '', id: aiMessageId }]);

      // 流式生成
      let fullContent = '';
      for await (const chunk of sendMessageStream(input)) {
        fullContent += chunk;
        setMessages(prev => prev.map(msg => 
          msg.id === aiMessageId ? { ...msg, content: fullContent } : msg
        ));
      }

    } catch (error) {
      console.error('生成失败:', error);
    } finally {
      setIsGenerating(false);
    }
  };

  return (
    <div className="max-w-3xl mx-auto p-6 h-screen flex flex-col">
      <h1 className="text-2xl font-bold mb-6">🤖 AI聊天</h1>
      
      {/* 消息列表 */}
      <div className="flex-1 overflow-y-auto space-y-4 mb-6">
        {messages.length === 0 ? (
          <div className="text-center py-12 text-gray-500">
            开始和AI聊天吧!
          </div>
        ) : (
          messages.map((msg, index) => (
            <ChatMessage key={msg.id || index} role={msg.role} content={msg.content} />
          ))
        )}
        {isGenerating && (
          <div className="p-4 bg-gray-100 rounded-lg max-w-[80%]">
            <div className="flex gap-2">
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
            </div>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      
      {/* 输入区 */}
      <div className="flex gap-2">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && handleSend()}
          placeholder="输入消息..."
          disabled={!isReady || isGenerating}
          className="flex-1 p-3 border rounded-lg disabled:opacity-50"
        />
        <button
          onClick={handleSend}
          disabled={!input.trim() || !isReady || isGenerating}
          className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
        >
          发送
        </button>
      </div>
    </div>
  );
}

// 主App
function App() {
  return (
    <AIProvider>
      <ChatApp />
    </AIProvider>
  );
}

export default App;

3. 各种AI Hook工具

jsx 复制代码
import { useAI } from './ai-context';

// AI写Hook
export function useAIWriter() {
  const { isReady, sendMessage } = useAI();

  const write = useCallback(async (prompt, mode = 'write') => {
    let systemPrompt = '';
    switch (mode) {
      case 'write':
        systemPrompt = '你是一个写作助手。';
        break;
      case 'improve':
        systemPrompt = '请润色以下内容,让它更流畅。';
        break;
      case 'translate':
        systemPrompt = '请把以下内容翻译成中文。';
        break;
    }

    return await sendMessage(`${systemPrompt}\n\n${prompt}`);
  }, [sendMessage]);

  return { isReady, write };
}

// AI代码Hook
export function useAICode() {
  const { isReady, sendMessage } = useAI();

  const generate = useCallback(async (description) => {
    return await sendMessage(`请根据以下描述写代码:\n\n${description}\n\n只返回代码。`);
  }, [sendMessage]);

  const fix = useCallback(async (code, error) => {
    return await sendMessage(`请修复以下代码的错误:\n\n代码:\n${code}\n\n错误:\n${error}`);
  }, [sendMessage]);

  return { isReady, generate, fix };
}

4. 今日小结

这一篇我们学习了:

  1. AI Context封装
  2. 完整的AI聊天应用
  3. 各种AI Hook工具

5. 下篇预告

下一篇,我们学习 端侧AI与Vue 3结合!

相关推荐
SelectDB7 分钟前
AI Agent 的分析数据存在哪里?Trace、Prompt 与会话数据的存储选型
人工智能
SelectDB22 分钟前
京东 Apache Doris 湖仓查询优化实践:从多引擎统一到百万级查询
人工智能
伞伞悦读27 分钟前
【第81期】特征缩放怎么选:标准化、MinMax 与 Robust 的原理、泄漏对照和完整实验
人工智能·python·深度学习·机器学习
网易云信1 小时前
销冠跑了,经验留下:AI Agent 如何进入医美机构的日常工作?
人工智能
武子康2 小时前
驾驶轨迹换个坐标,就能给移动机器人用吗?
人工智能·算法·机器学习
jay神2 小时前
基于深度学习的交通流量预测系统(Web 版)
前端·人工智能·深度学习·yolo·计算机毕业设计
kaixin_啊啊2 小时前
【零基础学AI】第 25 章——计算机怎样处理文字
人工智能