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结合!

相关推荐
GPUStack33 分钟前
从 24 到 51 tok/s:用 GPUStack 调优 4 台 DGX Spark 上的 GLM 推理
人工智能·开源软件·gpustack·推理集群
leizhen1984080933 分钟前
操作系统正在被为智能体重做_上集(漫画)
人工智能·安全
BD_Marathon34 分钟前
【RAG开发】LangChain调用大语言模型
人工智能·语言模型·langchain
大鹏的NLP博客36 分钟前
工业视觉目标检测阈值选择与生产决策评估报告
人工智能·目标检测·图像检测
xiaoyunxuexibiji37 分钟前
供应商认证管理怎么做?从资质审核到现场认证、样品认证
运维·服务器·人工智能
嘉行文39 分钟前
传统机器学习与深度学习的特点与选择
人工智能·深度学习·机器学习
咕泡科技39 分钟前
深度学习碾压机器学习?经典机器学习是否真的无用了?从LLM底层逻辑到工业落地全景解析
人工智能·深度学习·机器学习·llm·nlp
腾讯云大数据1 小时前
腾讯云AI Native数据平台功能发布合集【9月】
人工智能·腾讯云