人工智能与前端的完美融合:为你的 React 应用注入 AI 能力
各位前端开发者,随着大语言模型(LLM)和 AI 技术的迅猛发展,将人工智能能力整合到前端应用已成为热门趋势。本文将带你深入探索如何在 React 应用中集成 AI 能力,从基础调用到复杂交互,打造真正智能化的用户体验。
1. AI 时代的前端开发:挑战与机遇
AI 技术给前端开发带来了全新的可能性:
- 内容生成:自动生成文本、图像和代码
- 交互增强:智能搜索、聊天机器人和个性化推荐
- 用户体验优化:通过 AI 预测用户需求和行为
- 开发效率提升:自动化编码、调试和测试
然而,这些机遇也伴随着挑战:
- 性能与延迟:如何在前端高效运行 AI 模型
- 用户隐私:敏感数据处理与模型推理
- 技术整合:将 AI 服务与现有前端架构融合
- 成本控制:平衡 AI 功能与运营成本
下面,我们将分享一系列实用技术和最佳实践,帮助你有效地在 React 应用中整合 AI 能力。
2. AI 集成架构设计:前端 AI 集成模式
markdown
┌─────────────────────────────────────────────────┐
│ 前端应用 │
│ │
│ ┌─────────────┐ ┌────────────┐ ┌────────┐ │
│ │ React UI │◄───┤ 状态管理 │◄──┤ AI中间层│ │
│ └─────────────┘ └────────────┘ └────────┘ │
│ │ ▲ │
└─────────┼──────────────────────────────────┼─────┘
│ │
▼ │
┌─────────────┐ ┌───────────┐
│ 用户交互 │ │ AI服务/API │
└─────────────┘ └───────────┘
│
▼
┌───────────┐
│ 模型服务器 │
└───────────┘
三种主要集成模式
-
云端 API 调用模式
- AI 模型运行在云端服务器
- 前端通过 API 请求获取结果
- 优势:无需前端加载模型,响应速度快
- 劣势:依赖网络连接,可能有 API 调用成本
-
前端模型运行模式
- 模型直接在浏览器中运行
- 使用 WebGL、WebAssembly 等技术
- 优势:保护隐私,无需网络请求
- 劣势:初始加载较慢,受限于浏览器性能
-
混合模式
- 轻量模型在前端运行
- 复杂任务发送到云端 API
- 优势:平衡性能与功能
- 劣势:架构复杂度增加
3. 云端 LLM 服务集成:基于 OpenAI API 构建智能问答系统
以下是一个完整的 AI 聊天组件实现,采用 React 和 OpenAI API:
tsx
// src/components/AIChatAssistant.tsx
import React, { useState, useRef, useEffect } from "react";
import { useAuth } from "../hooks/useAuth";
import { useLocalStorage } from "../hooks/useLocalStorage";
interface Message {
id: string;
role: "user" | "assistant" | "system";
content: string;
timestamp: number;
}
interface ChatHistoryProps {
messages: Message[];
isLoading: boolean;
}
// 消息历史组件
const ChatHistory: React.FC<ChatHistoryProps> = ({ messages, isLoading }) => {
const messagesEndRef = useRef<HTMLDivElement>(null);
// 自动滚动到最新消息
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
// 格式化时间
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
});
};
return (
<div className="chat-history">
{messages.map(
(message) =>
message.role !== "system" && (
<div
key={message.id}
className={`message ${
message.role === "user" ? "user-message" : "assistant-message"
}`}
>
<div className="message-content">
<ReactMarkdown>{message.content}</ReactMarkdown>
</div>
<div className="message-time">
{formatTime(message.timestamp)}
</div>
</div>
)
)}
{isLoading && (
<div className="message assistant-message">
<div className="message-content">
<div className="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
);
};
// 输入组件
const ChatInput: React.FC<{
onSendMessage: (message: string) => void;
isLoading: boolean;
}> = ({ onSendMessage, isLoading }) => {
const [input, setInput] = useState("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
// 自动调整文本区高度
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
}
}, [input]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim() && !isLoading) {
onSendMessage(input);
setInput("");
}
};
// 处理快捷键
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSubmit(e);
}
};
return (
<form className="chat-input-form" onSubmit={handleSubmit}>
<textarea
ref={textareaRef}
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="输入问题..."
rows={1}
disabled={isLoading}
/>
<button
type="submit"
className="send-button"
disabled={isLoading || !input.trim()}
>
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="2"
fill="none"
>
<path d="M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z" />
</svg>
</button>
</form>
);
};
// 主AI聊天组件
const AIChatAssistant: React.FC = () => {
const { user } = useAuth();
const [messages, setMessages] = useLocalStorage<Message[]>(
`chat-history-${user?.id}`,
[
{
id: "system-1",
role: "system",
content: "你是一个友好的AI助手,帮助用户解答前端开发问题。",
timestamp: Date.now(),
},
]
);
const [isLoading, setIsLoading] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
// 发送消息
const handleSendMessage = async (content: string) => {
if (isLoading) return;
// 创建用户消息
const userMessage: Message = {
id: `user-${Date.now()}`,
role: "user",
content,
timestamp: Date.now(),
};
// 更新消息列表,添加用户消息
setMessages((prev) => [...prev, userMessage]);
// 开始加载状态
setIsLoading(true);
// 创建一个新的AbortController实例,用于取消请求
abortControllerRef.current = new AbortController();
try {
// 准备API请求的消息格式
const apiMessages = messages.map(({ role, content }) => ({
role,
content,
}));
apiMessages.push({
role: "user",
content,
});
// 发送API请求
const response = await fetch("/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: apiMessages,
user_id: user?.id,
max_tokens: 1000,
temperature: 0.7,
stream: true,
}),
signal: abortControllerRef.current.signal,
});
if (!response.ok) {
throw new Error("API请求失败");
}
if (!response.body) {
throw new Error("响应体为空");
}
// 处理流式响应
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = "";
// 创建临时消息ID
const assistantMessageId = `assistant-${Date.now()}`;
// 添加空的助手消息
setMessages((prev) => [
...prev,
{
id: assistantMessageId,
role: "assistant",
content: "",
timestamp: Date.now(),
},
]);
// 处理流式数据
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
// 解码响应数据
const chunk = decoder.decode(value, { stream: true });
// 将块添加到助手消息中
assistantMessage += chunk;
// 更新助手消息
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantMessageId
? { ...msg, content: assistantMessage }
: msg
)
);
}
} catch (error) {
if (error.name === "AbortError") {
console.log("请求已取消");
} else {
console.error("API请求错误:", error);
// 添加错误消息
setMessages((prev) => [
...prev,
{
id: `assistant-error-${Date.now()}`,
role: "assistant",
content: "抱歉,处理您的请求时出错了。请稍后再试。",
timestamp: Date.now(),
},
]);
}
} finally {
setIsLoading(false);
abortControllerRef.current = null;
}
};
// 取消正在进行的请求
const handleCancel = () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
setIsLoading(false);
}
};
// 清空聊天记录
const handleClearChat = () => {
if (window.confirm("确定要清空聊天记录吗?")) {
setMessages([
{
id: "system-1",
role: "system",
content: "你是一个友好的AI助手,帮助用户解答前端开发问题。",
timestamp: Date.now(),
},
]);
}
};
return (
<div className="ai-chat-container">
<div className="chat-header">
<h2>AI助手</h2>
<div className="chat-actions">
{isLoading && (
<button className="cancel-button" onClick={handleCancel}>
停止生成
</button>
)}
<button className="clear-button" onClick={handleClearChat}>
清空对话
</button>
</div>
</div>
<ChatHistory messages={messages} isLoading={isLoading} />
<ChatInput onSendMessage={handleSendMessage} isLoading={isLoading} />
</div>
);
};
export default AIChatAssistant;
API 接口实现:
typescript
// pages/api/chat.ts
import { NextApiRequest, NextApiResponse } from "next";
import OpenAI from "openai";
// 初始化OpenAI客户端
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// 处理请求
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "只支持POST请求" });
}
try {
const {
messages,
user_id,
max_tokens = 1000,
temperature = 0.7,
stream = false,
} = req.body;
// 验证消息格式
if (!Array.isArray(messages) || !messages.length) {
return res.status(400).json({ error: "无效的messages参数" });
}
// 设置头部,支持流式响应
if (stream) {
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
});
}
// 创建聊天完成请求
const completion = await openai.chat.completions.create({
model: "gpt-4-1106-preview",
messages,
max_tokens,
temperature,
stream,
user: user_id,
});
// 流式响应
if (stream) {
for await (const chunk of completion as any) {
const content = chunk.choices[0]?.delta?.content || "";
if (content) {
res.write(content);
}
}
res.end();
}
// 普通响应
else {
res.status(200).json(completion);
}
} catch (error) {
console.error("API错误:", error);
// 如果连接已关闭,不尝试发送响应
if (!res.writableEnded) {
res.status(500).json({ error: "处理请求时出错" });
}
}
}
4. 前端模型集成:浏览器中的 AI 能力
使用 TensorFlow.js 实现浏览器内图像识别:
tsx
// src/components/ImageClassifier.tsx
import React, { useState, useRef, useEffect } from "react";
import * as tf from "@tensorflow/tfjs";
import { load } from "@tensorflow-models/mobilenet";
const ImageClassifier: React.FC = () => {
const [model, setModel] = useState<any>(null);
const [isModelLoading, setIsModelLoading] = useState(true);
const [predictions, setPredictions] = useState<
Array<{
className: string;
probability: number;
}>
>([]);
const [imageURL, setImageURL] = useState<string | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const imageRef = useRef<HTMLImageElement>(null);
// 加载模型
useEffect(() => {
const loadModel = async () => {
try {
setIsModelLoading(true);
// 设置加载进度回调
tf.loadLayersModel.progressBar = {
start: () => console.log("模型加载开始"),
update: (fraction: number) =>
console.log(`加载进度: ${(fraction * 100).toFixed(1)}%`),
end: () => console.log("模型加载完成"),
};
// 加载MobileNet模型
const mobilenet = await load({
version: 2,
alpha: 1.0,
});
setModel(mobilenet);
console.log("MobileNet模型加载成功");
} catch (err) {
console.error("模型加载失败:", err);
setError("模型加载失败,请刷新页面重试");
} finally {
setIsModelLoading(false);
}
};
loadModel();
// 组件卸载时清理
return () => {
if (model) {
console.log("清理模型资源");
}
};
}, []);
// 处理文件选择
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// 重置状态
setPredictions([]);
setError(null);
// 检查文件类型
if (!file.type.match("image.*")) {
setError("请选择图片文件");
return;
}
// 创建预览URL
const reader = new FileReader();
reader.onload = (e) => {
setImageURL(e.target?.result as string);
};
reader.readAsDataURL(file);
};
// 分析图像
const analyzeImage = async () => {
if (!model || !imageRef.current || !imageURL) {
return;
}
try {
setIsAnalyzing(true);
setError(null);
// 使用模型进行预测
const result = await model.classify(imageRef.current, 5);
// 更新预测结果
setPredictions(result);
} catch (err) {
console.error("图像分析失败:", err);
setError("图像分析失败,请重试");
} finally {
setIsAnalyzing(false);
}
};
// 触发文件选择
const triggerFileSelect = () => {
fileInputRef.current?.click();
};
return (
<div className="image-classifier">
<h2>浏览器AI图像识别</h2>
<div className="model-status">
{isModelLoading ? (
<div className="loading-indicator">
<div className="spinner"></div>
<p>加载AI模型中(约5-10MB)...</p>
</div>
) : (
<p className="model-ready">AI模型已加载完成,可以开始使用</p>
)}
</div>
<div className="upload-section">
<input
type="file"
accept="image/*"
onChange={handleFileChange}
ref={fileInputRef}
style={{ display: "none" }}
/>
<button
className="upload-button"
onClick={triggerFileSelect}
disabled={isModelLoading}
>
选择图片
</button>
{imageURL && (
<button
className="analyze-button"
onClick={analyzeImage}
disabled={isModelLoading || isAnalyzing}
>
{isAnalyzing ? "分析中..." : "分析图片"}
</button>
)}
</div>
{error && <div className="error-message">{error}</div>}
<div className="image-preview-container">
{imageURL && (
<div className="image-preview">
<img
src={imageURL}
alt="上传的图片"
ref={imageRef}
crossOrigin="anonymous"
/>
</div>
)}
</div>
{predictions.length > 0 && (
<div className="prediction-results">
<h3>识别结果</h3>
<ul>
{predictions.map((prediction, index) => (
<li key={index}>
<span className="prediction-label">{prediction.className}</span>
<div className="prediction-bar-container">
<div
className="prediction-bar"
style={{ width: `${prediction.probability * 100}%` }}
></div>
<span className="prediction-percent">
{(prediction.probability * 100).toFixed(2)}%
</span>
</div>
</li>
))}
</ul>
</div>
)}
</div>
);
};
export default ImageClassifier;
使用 transformers.js 实现浏览器中的文本生成:
tsx
// src/components/TextGenerator.tsx
import React, { useState, useEffect, useRef } from "react";
import { pipeline, env } from "@xenova/transformers";
// 配置transformers.js
env.allowLocalModels = false;
env.useBrowserCache = true;
const TextGenerator: React.FC = () => {
const [prompt, setPrompt] = useState("");
const [generatedText, setGeneratedText] = useState("");
const [isGenerating, setIsGenerating] = useState(false);
const [isModelLoading, setIsModelLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [loadingProgress, setLoadingProgress] = useState(0);
const generatorRef = useRef<any>(null);
const maxLength = 100;
// 加载模型
useEffect(() => {
const loadModel = async () => {
try {
setIsModelLoading(true);
setError(null);
// 加载文本生成模型(选择较小的模型以适应浏览器环境)
generatorRef.current = await pipeline(
"text-generation",
"Xenova/distilgpt2",
{
progress_callback: (progress) => {
setLoadingProgress(Math.round(progress * 100));
},
}
);
console.log("文本生成模型加载成功");
} catch (err) {
console.error("模型加载失败:", err);
setError("模型加载失败,请刷新页面重试");
} finally {
setIsModelLoading(false);
}
};
loadModel();
}, []);
// 生成文本
const generateText = async () => {
if (!generatorRef.current || !prompt.trim()) {
return;
}
try {
setIsGenerating(true);
setError(null);
setGeneratedText("");
// 使用模型生成文本
const result = await generatorRef.current(prompt, {
max_new_tokens: maxLength,
temperature: 0.7,
repetition_penalty: 1.2,
});
setGeneratedText(result[0].generated_text);
} catch (err) {
console.error("文本生成失败:", err);
setError("文本生成失败,请重试");
} finally {
setIsGenerating(false);
}
};
return (
<div className="text-generator">
<h2>浏览器内文本生成</h2>
<div className="model-status">
{isModelLoading ? (
<div className="loading-indicator">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${loadingProgress}%` }}
></div>
</div>
<p>加载AI模型中: {loadingProgress}%</p>
</div>
) : (
<p className="model-ready">AI模型已加载完成,可以开始使用</p>
)}
</div>
<div className="prompt-container">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="输入提示词..."
rows={3}
disabled={isModelLoading || isGenerating}
/>
<button
onClick={generateText}
disabled={isModelLoading || isGenerating || !prompt.trim()}
>
{isGenerating ? "生成中..." : "生成文本"}
</button>
</div>
{error && <div className="error-message">{error}</div>}
{generatedText && (
<div className="generated-text-container">
<h3>生成结果</h3>
<div className="generated-text">{generatedText}</div>
</div>
)}
</div>
);
};
export default TextGenerator;
5. 智能 UI 组件:AI 驱动的用户界面
自动表单填充组件:
tsx
// src/components/AIFormAssistant.tsx
import React, { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
interface AIFormAssistantProps {
fieldMappings: {
[key: string]: string;
};
formContext: ReturnType<typeof useFormContext>;
onError?: (error: Error) => void;
}
const AIFormAssistant: React.FC<AIFormAssistantProps> = ({
fieldMappings,
formContext,
onError,
}) => {
const { control, setValue } = formContext;
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [rawText, setRawText] = useState("");
// 智能分析文本并填充表单
const analyzeAndFill = async () => {
if (!rawText.trim()) return;
setIsAnalyzing(true);
try {
// 调用分析API
const response = await fetch("/api/analyze-text", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text: rawText,
fields: Object.keys(fieldMappings),
}),
});
if (!response.ok) {
throw new Error("分析请求失败");
}
const data = await response.json();
// 根据分析结果填充表单
Object.entries(data.results).forEach(([field, value]) => {
if (fieldMappings[field]) {
setValue(fieldMappings[field], value, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
}
});
} catch (error) {
console.error("分析文本失败:", error);
onError?.(error);
} finally {
setIsAnalyzing(false);
}
};
return (
<div className="ai-form-assistant">
<h3>AI表单助手</h3>
<p>粘贴简历文本或任何相关信息,AI将自动分析并填充表单</p>
<Controller
name="rawText"
control={control}
render={({ field }) => (
<textarea
{...field}
value={rawText}
onChange={(e) => setRawText(e.target.value)}
placeholder="在此粘贴文本..."
rows={5}
className="raw-text-input"
/>
)}
/>
<button
onClick={analyzeAndFill}
disabled={isAnalyzing || !rawText.trim()}
className="analyze-button"
>
{isAnalyzing ? (
<>
<span className="spinner"></span>
分析中...
</>
) : (
"AI智能填充"
)}
</button>
</div>
);
};
export default AIFormAssistant;
实时内容推荐组件:
tsx
// src/components/AIContentRecommender.tsx
import React, { useState, useEffect } from "react";
import { useDebounce } from "../hooks/useDebounce";
interface RecommendationItem {
id: string;
title: string;
description: string;
url: string;
type: "article" | "product" | "video";
relevanceScore: number;
}
interface AIContentRecommenderProps {
contextText: string;
userId?: string;
maxItems?: number;
onRecommendationClick?: (item: RecommendationItem) => void;
onError?: (error: Error) => void;
}
const AIContentRecommender: React.FC<AIContentRecommenderProps> = ({
contextText,
userId,
maxItems = 3,
onRecommendationClick,
onError,
}) => {
const [recommendations, setRecommendations] = useState<RecommendationItem[]>(
[]
);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 使用防抖,避免频繁API调用
const debouncedContextText = useDebounce(contextText, 500);
// 获取推荐内容
useEffect(() => {
const fetchRecommendations = async () => {
if (!debouncedContextText || debouncedContextText.length < 10) {
setRecommendations([]);
return;
}
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/recommend-content", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
context: debouncedContextText,
userId,
maxItems,
}),
});
if (!response.ok) {
throw new Error("推荐请求失败");
}
const data = await response.json();
setRecommendations(data.recommendations || []);
} catch (err) {
console.error("获取推荐内容失败:", err);
setError("无法获取推荐内容");
onError?.(err);
} finally {
setIsLoading(false);
}
};
fetchRecommendations();
}, [debouncedContextText, userId, maxItems, onError]);
// 处理推荐点击
const handleItemClick = (item: RecommendationItem) => {
// 记录点击事件
fetch("/api/track-recommendation-click", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
recommendationId: item.id,
userId,
context: contextText,
}),
}).catch((err) => {
console.error("追踪点击失败:", err);
});
// 调用外部点击处理函数
onRecommendationClick?.(item);
};
// 如果没有足够的上下文或没有推荐结果,不显示组件
if (
(!debouncedContextText || debouncedContextText.length < 10) &&
!isLoading
) {
return null;
}
return (
<div className="ai-content-recommender">
<h3>为您推荐</h3>
{isLoading && (
<div className="loading-indicator">
<div className="spinner"></div>
<p>正在分析内容,查找相关推荐...</p>
</div>
)}
{error && <div className="error-message">{error}</div>}
{!isLoading &&
recommendations.length === 0 &&
debouncedContextText.length >= 10 && (
<p className="no-recommendations">暂无相关推荐</p>
)}
<ul className="recommendations-list">
{recommendations.map((item) => (
<li key={item.id} className={`recommendation-item ${item.type}`}>
<a
href={item.url}
onClick={(e) => {
e.preventDefault();
handleItemClick(item);
window.open(item.url, "_blank");
}}
target="_blank"
rel="noopener noreferrer"
>
<div className="recommendation-content">
<span className="recommendation-type">
{getTypeLabel(item.type)}
</span>
<h4 className="recommendation-title">{item.title}</h4>
<p className="recommendation-description">{item.description}</p>
</div>
<div className="recommendation-score">
<span className="relevance-label">相关度</span>
<div className="relevance-bar">
<div
className="relevance-fill"
style={{ width: `${item.relevanceScore * 100}%` }}
></div>
</div>
</div>
</a>
</li>
))}
</ul>
</div>
);
};
// 获取内容类型标签
function getTypeLabel(type: RecommendationItem["type"]): string {
switch (type) {
case "article":
return "文章";
case "product":
return "产品";
case "video":
return "视频";
default:
return "内容";
}
}
export default AIContentRecommender;
AI 驱动的搜索组件:
tsx
// src/components/AIEnhancedSearch.tsx
import React, { useState, useEffect, useRef } from "react";
import { useDebounce } from "../hooks/useDebounce";
interface SearchResult {
id: string;
title: string;
excerpt: string;
url: string;
category: string;
score: number;
highlights?: {
title?: string[];
excerpt?: string[];
};
}
interface AIEnhancedSearchProps {
placeholder?: string;
minSearchLength?: number;
maxResults?: number;
onResultClick?: (result: SearchResult) => void;
onSearch?: (query: string, results: SearchResult[]) => void;
}
const AIEnhancedSearch: React.FC<AIEnhancedSearchProps> = ({
placeholder = "智能搜索...",
minSearchLength = 2,
maxResults = 10,
onResultClick,
onSearch,
}) => {
const [query, setQuery] = useState("");
const [results, setResults] = useState<SearchResult[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const [error, setError] = useState<string | null>(null);
const searchContainerRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
// 使用防抖处理搜索查询
const debouncedQuery = useDebounce(query, 300);
// 处理搜索
useEffect(() => {
const fetchSearchResults = async () => {
if (!debouncedQuery || debouncedQuery.length < minSearchLength) {
setResults([]);
if (isOpen) setIsOpen(false);
return;
}
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/ai-search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: debouncedQuery,
maxResults,
}),
});
if (!response.ok) {
throw new Error("搜索请求失败");
}
const data = await response.json();
setResults(data.results || []);
// 打开结果下拉框
if (data.results.length > 0) {
setIsOpen(true);
} else {
setIsOpen(false);
}
// 调用外部搜索回调
onSearch?.(debouncedQuery, data.results || []);
} catch (err) {
console.error("搜索失败:", err);
setError("搜索过程中出错");
setResults([]);
} finally {
setIsLoading(false);
}
};
fetchSearchResults();
}, [debouncedQuery, minSearchLength, maxResults, onSearch]);
// 处理点击结果
const handleResultClick = (result: SearchResult) => {
onResultClick?.(result);
setIsOpen(false);
setQuery("");
};
// 处理键盘导航
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!isOpen) return;
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setSelectedIndex((prevIndex) =>
prevIndex < results.length - 1 ? prevIndex + 1 : prevIndex
);
break;
case "ArrowUp":
e.preventDefault();
setSelectedIndex((prevIndex) => (prevIndex > 0 ? prevIndex - 1 : 0));
break;
case "Enter":
e.preventDefault();
if (selectedIndex >= 0 && selectedIndex < results.length) {
handleResultClick(results[selectedIndex]);
}
break;
case "Escape":
e.preventDefault();
setIsOpen(false);
break;
default:
break;
}
};
// 处理点击外部关闭下拉框
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
searchContainerRef.current &&
!searchContainerRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
// 呈现高亮文本
const renderHighlightedText = (text: string, highlights?: string[]) => {
if (!highlights || highlights.length === 0) {
return <span>{text}</span>;
}
const parts: React.ReactNode[] = [];
let lastIndex = 0;
highlights.forEach((highlight) => {
const index = text
.toLowerCase()
.indexOf(highlight.toLowerCase(), lastIndex);
if (index === -1) return;
if (index > lastIndex) {
parts.push(
<span key={`text-${lastIndex}-${index}`}>
{text.substring(lastIndex, index)}
</span>
);
}
parts.push(
<mark key={`highlight-${index}`}>
{text.substring(index, index + highlight.length)}
</mark>
);
lastIndex = index + highlight.length;
});
if (lastIndex < text.length) {
parts.push(
<span key={`text-${lastIndex}-end`}>{text.substring(lastIndex)}</span>
);
}
return <>{parts}</>;
};
return (
<div className="ai-enhanced-search" ref={searchContainerRef}>
<div className="search-input-container">
<input
ref={searchInputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => results.length > 0 && setIsOpen(true)}
placeholder={placeholder}
className="search-input"
aria-label="搜索"
/>
{query.length > 0 && (
<button
className="clear-search"
onClick={() => {
setQuery("");
setResults([]);
setIsOpen(false);
searchInputRef.current?.focus();
}}
aria-label="清除搜索"
>
×
</button>
)}
<div className="search-icon">
{isLoading ? (
<div className="search-spinner"></div>
) : (
<svg viewBox="0 0 24 24" width="18" height="18">
<path
fill="currentColor"
d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 0 0 1.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 0 0-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 0 0 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
/>
</svg>
)}
</div>
</div>
{isOpen && (
<div className="search-results">
{error && (
<div className="search-error">
<p>{error}</p>
</div>
)}
{results.length === 0 && !error && !isLoading && (
<div className="no-results">
<p>没有找到匹配的结果</p>
</div>
)}
{results.length > 0 && (
<ul className="results-list">
{results.map((result, index) => (
<li
key={result.id}
className={`result-item ${
index === selectedIndex ? "selected" : ""
}`}
onClick={() => handleResultClick(result)}
>
<div className="result-category">{result.category}</div>
<h4 className="result-title">
{renderHighlightedText(
result.title,
result.highlights?.title
)}
</h4>
<p className="result-excerpt">
{renderHighlightedText(
result.excerpt,
result.highlights?.excerpt
)}
</p>
</li>
))}
</ul>
)}
<div className="search-footer">
<span className="ai-powered">AI增强搜索</span>
</div>
</div>
)}
</div>
);
};
export default AIEnhancedSearch;
6. 生成式 UI:AI 驱动的动态界面组件
智能数据可视化组件:
tsx
// src/components/AIDataVisualizer.tsx
import React, { useState, useEffect } from "react";
import { Chart, registerables } from "chart.js";
import { Bar, Line, Pie, Doughnut, Scatter } from "react-chartjs-2";
// 注册Chart.js组件
Chart.register(...registerables);
interface DataPoint {
[key: string]: any;
}
interface VisualizationConfig {
type: "bar" | "line" | "pie" | "doughnut" | "scatter";
title: string;
description: string;
labels: string[];
datasets: Array<{
label: string;
data: number[];
backgroundColor?: string | string[];
borderColor?: string | string[];
borderWidth?: number;
}>;
xAxisLabel?: string;
yAxisLabel?: string;
}
interface AIDataVisualizerProps {
data: DataPoint[];
includeFields?: string[];
excludeFields?: string[];
onError?: (error: Error) => void;
}
const AIDataVisualizer: React.FC<AIDataVisualizerProps> = ({
data,
includeFields,
excludeFields,
onError,
}) => {
const [visualizationConfig, setVisualizationConfig] =
useState<VisualizationConfig | null>(null);
const [alternativeVisualizations, setAlternativeVisualizations] = useState<
VisualizationConfig[]
>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 获取可视化配置
useEffect(() => {
const generateVisualization = async () => {
if (!data || data.length === 0) {
setIsLoading(false);
return;
}
setIsLoading(true);
setError(null);
try {
// 准备数据
const dataFields = Object.keys(data[0]).filter((key) => {
if (includeFields && includeFields.length > 0) {
return includeFields.includes(key);
}
if (excludeFields && excludeFields.length > 0) {
return !excludeFields.includes(key);
}
return true;
});
// 调用API获取可视化配置
const response = await fetch("/api/generate-visualization", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: data.slice(0, 100), // 限制大小
fields: dataFields,
}),
});
if (!response.ok) {
throw new Error("可视化请求失败");
}
const visualizationData = await response.json();
// 设置主可视化和备选可视化
if (
visualizationData.visualizations &&
visualizationData.visualizations.length > 0
) {
setVisualizationConfig(visualizationData.visualizations[0]);
setAlternativeVisualizations(
visualizationData.visualizations.slice(1)
);
} else {
throw new Error("未能生成可视化配置");
}
} catch (err) {
console.error("生成可视化失败:", err);
setError("无法自动生成数据可视化");
onError?.(err);
} finally {
setIsLoading(false);
}
};
generateVisualization();
}, [data, includeFields, excludeFields, onError]);
// 切换到备选可视化
const switchVisualization = (index: number) => {
if (index >= 0 && index < alternativeVisualizations.length) {
// 将当前可视化添加到备选列表
if (visualizationConfig) {
setAlternativeVisualizations([
visualizationConfig,
...alternativeVisualizations.filter((_, i) => i !== index),
]);
}
// 设置新的主可视化
setVisualizationConfig(alternativeVisualizations[index]);
}
};
// 渲染图表
const renderChart = () => {
if (!visualizationConfig) return null;
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: visualizationConfig.title,
font: {
size: 16,
weight: "bold",
},
padding: {
bottom: 10,
},
},
legend: {
display: true,
position: "top" as const,
},
tooltip: {
enabled: true,
},
},
};
const data = {
labels: visualizationConfig.labels,
datasets: visualizationConfig.datasets,
};
switch (visualizationConfig.type) {
case "bar":
return (
<Bar
data={data}
options={{
...commonOptions,
scales: {
x: {
title: {
display: !!visualizationConfig.xAxisLabel,
text: visualizationConfig.xAxisLabel,
},
},
y: {
title: {
display: !!visualizationConfig.yAxisLabel,
text: visualizationConfig.yAxisLabel,
},
beginAtZero: true,
},
},
}}
/>
);
case "line":
return (
<Line
data={data}
options={{
...commonOptions,
scales: {
x: {
title: {
display: !!visualizationConfig.xAxisLabel,
text: visualizationConfig.xAxisLabel,
},
},
y: {
title: {
display: !!visualizationConfig.yAxisLabel,
text: visualizationConfig.yAxisLabel,
},
beginAtZero: true,
},
},
}}
/>
);
case "pie":
return <Pie data={data} options={commonOptions} />;
case "doughnut":
return <Doughnut data={data} options={commonOptions} />;
case "scatter":
return (
<Scatter
data={data}
options={{
...commonOptions,
scales: {
x: {
title: {
display: !!visualizationConfig.xAxisLabel,
text: visualizationConfig.xAxisLabel,
},
},
y: {
title: {
display: !!visualizationConfig.yAxisLabel,
text: visualizationConfig.yAxisLabel,
},
},
},
}}
/>
);
default:
return null;
}
};
return (
<div className="ai-data-visualizer">
{isLoading && (
<div className="loading-container">
<div className="spinner"></div>
<p>正在分析数据,生成最佳可视化...</p>
</div>
)}
{error && <div className="error-message">{error}</div>}
{!isLoading && !error && visualizationConfig && (
<div className="visualization-container">
<div className="visualization-header">
<h3>{visualizationConfig.title}</h3>
<p className="visualization-description">
{visualizationConfig.description}
</p>
</div>
<div className="chart-container">{renderChart()}</div>
{alternativeVisualizations.length > 0 && (
<div className="alternative-visualizations">
<h4>其他可视化选项</h4>
<div className="alternatives-grid">
{alternativeVisualizations.map((viz, index) => (
<div
key={`alt-${index}`}
className="alternative-item"
onClick={() => switchVisualization(index)}
>
<div className="alternative-type">
{getChartTypeLabel(viz.type)}
</div>
<div className="alternative-title">{viz.title}</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
);
};
// 获取图表类型标签
function getChartTypeLabel(type: string): string {
switch (type) {
case "bar":
return "柱状图";
case "line":
return "折线图";
case "pie":
return "饼图";
case "doughnut":
return "环形图";
case "scatter":
return "散点图";
default:
return "图表";
}
}
export default AIDataVisualizer;
7. API 端点实现:连接 AI 服务与前端
文本分析 API 端点:
typescript
// pages/api/analyze-text.ts
import { NextApiRequest, NextApiResponse } from "next";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "只支持POST请求" });
}
const { text, fields } = req.body;
if (!text || !fields || !Array.isArray(fields)) {
return res.status(400).json({ error: "缺少必要参数" });
}
try {
// 构建提示词
const prompt = `分析以下文本,提取这些字段的信息: ${fields.join(", ")}。
文本内容:
"""
${text}
"""
以JSON格式返回结果,按照以下例子格式:
{
"results": {
"${fields[0]}": "提取的值",
"${fields[1]}": "提取的值",
...
}
}
只返回JSON,不要有其他内容。`;
// 发送到OpenAI API
const completion = await openai.chat.completions.create({
model: "gpt-4-1106-preview",
response_format: { type: "json_object" },
messages: [
{
role: "system",
content: "你是一个擅长提取文本信息的AI助手,只返回指定格式的JSON。",
},
{
role: "user",
content: prompt,
},
],
temperature: 0.1,
});
// 解析响应内容
const responseContent = completion.choices[0].message.content;
try {
const parsedData = JSON.parse(responseContent || "{}");
return res.status(200).json(parsedData);
} catch (jsonError) {
console.error("JSON解析错误:", jsonError);
return res.status(500).json({ error: "响应格式错误" });
}
} catch (error) {
console.error("OpenAI API错误:", error);
return res.status(500).json({ error: "处理请求时出错" });
}
}
内容推荐 API 端点:
typescript
// pages/api/recommend-content.ts
import { NextApiRequest, NextApiResponse } from "next";
import OpenAI from "openai";
import { PrismaClient } from "@prisma/client";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const prisma = new PrismaClient();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "只支持POST请求" });
}
const { context, userId, maxItems = 3 } = req.body;
if (!context) {
return res.status(400).json({ error: "缺少必要参数" });
}
try {
// 查询用户的浏览历史和兴趣标签(如果有userId)
let userProfile = null;
if (userId) {
userProfile = await prisma.userProfile.findUnique({
where: { userId },
include: {
interests: true,
viewHistory: {
take: 20,
orderBy: { viewedAt: "desc" },
include: { content: true },
},
},
});
}
// 获取最新内容
const recentContent = await prisma.content.findMany({
take: 50,
orderBy: { createdAt: "desc" },
include: {
categories: true,
tags: true,
},
});
// 利用OpenAI API进行内容推荐
const completion = await openai.chat.completions.create({
model: "gpt-4-1106-preview",
response_format: { type: "json_object" },
messages: [
{
role: "system",
content: `你是一个内容推荐系统。根据提供的上下文和用户信息,推荐最相关的内容。
返回JSON格式的推荐列表,包含id、title、description、url、type和relevanceScore字段。`,
},
{
role: "user",
content: `用户当前阅读的内容:
"""
${context}
"""
${
userProfile
? `用户兴趣标签: ${userProfile.interests
.map((i) => i.name)
.join(", ")}`
: ""
}
可选内容:
${JSON.stringify(
recentContent.map((c) => ({
id: c.id,
title: c.title,
excerpt: c.excerpt,
url: c.url,
type: c.type,
categories: c.categories.map((cat) => cat.name),
tags: c.tags.map((tag) => tag.name),
}))
)}
根据上下文推荐${maxItems}个最相关的内容项,格式如下:
{
"recommendations": [
{
"id": "内容ID",
"title": "内容标题",
"description":"内容摘要",
"url": "内容URL",
"type": "article|product|video",
"relevanceScore": 0.95 // 0到1之间的相关度分数
},
...
]
}
只返回JSON,不要有其他内容。`,
},
],
temperature: 0.5,
});
// 解析响应内容
const responseContent = completion.choices[0].message.content;
try {
const parsedData = JSON.parse(responseContent || "{}");
// 记录推荐历史
if (userId) {
await prisma.recommendationEvent.create({
data: {
userId,
context: context.slice(0, 500), // 限制长度
recommendedItems: {
create: parsedData.recommendations.map((rec: any) => ({
contentId: rec.id,
score: rec.relevanceScore,
})),
},
},
});
}
return res.status(200).json(parsedData);
} catch (jsonError) {
console.error("JSON解析错误:", jsonError);
return res.status(500).json({ error: "响应格式错误" });
}
} catch (error) {
console.error("API错误:", error);
return res.status(500).json({ error: "处理请求时出错" });
}
}
8. AI 功能集成的最佳实践
用户体验与性能优化
- 显示加载状态:AI 处理通常需要时间,务必提供清晰的加载反馈
- 增量响应:使用流式 API 获取部分结果并立即显示
- 本地缓存:缓存常见查询结果减少 API 调用
- 优雅降级:当 AI 服务不可用时,提供备选功能
- 批处理请求:合并多个请求减少 API 调用次数
成本控制策略
- 模型选择:根据任务复杂度选择合适的模型(GPT-4/GPT-3.5 等)
- 令牌优化:精简提示词和上下文长度
- 请求节流:限制单个用户的请求频率
- 使用微调模型:对特定任务使用专门微调的模型
- 混合客户端和服务端 AI:简单任务使用本地模型,复杂任务使用云 API
隐私与安全考量
typescript
// src/utils/aiSanitizer.ts
export function sanitizeUserInput(input: string): string {
// 移除潜在的敏感信息
const sanitized = input
// 移除可能的电话号码
.replace(
/(\+\d{1,3}[\s-])?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/g,
"[电话号码已移除]"
)
// 移除可能的邮箱地址
.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[邮箱已移除]")
// 移除可能的身份证号
.replace(
/[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]/g,
"[身份证号已移除]"
)
// 移除可能的信用卡号
.replace(/\b(?:\d{4}[ -]?){3}(?:\d{4})\b/g, "[信用卡号已移除]");
return sanitized;
}
// 在发送到AI服务前进行敏感信息检查
export function checkSensitiveContent(text: string): boolean {
const sensitivePatterns = [
/密码|password|pwd/i,
/social security|社保|社会保险/i,
/私人|隐私|保密|confidential|private/i,
/secret|秘密/i,
];
return sensitivePatterns.some((pattern) => pattern.test(text));
}
AI 功能集成的核心原则
- 透明度:清晰标明哪些功能使用了 AI
- 用户控制:让用户能够选择是否使用 AI 功能
- 持续改进:收集用户反馈不断优化 AI 功能
- 可解释性:确保用户理解 AI 的决策过程
- 补充而非替代:AI 应该增强用户体验,而不是完全取代传统 UI
9. 实际案例:AI 驱动的代码编辑器
以下是一个集成了 AI 辅助功能的代码编辑器组件:
tsx
// src/components/AICodeEditor.tsx
import React, { useState, useRef, useEffect } from "react";
import Editor from "@monaco-editor/react";
import OpenAI from "openai";
interface AICodeEditorProps {
initialCode: string;
language: string;
theme?: "vs-dark" | "light";
onChange?: (code: string) => void;
onSave?: (code: string) => void;
aiApiKey?: string;
}
const AICodeEditor: React.FC<AICodeEditorProps> = ({
initialCode,
language,
theme = "vs-dark",
onChange,
onSave,
aiApiKey,
}) => {
const [code, setCode] = useState(initialCode);
const [isGenerating, setIsGenerating] = useState(false);
const [aiSuggestion, setAiSuggestion] = useState<string | null>(null);
const [showAiPanel, setShowAiPanel] = useState(false);
const [aiPrompt, setAiPrompt] = useState("");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const editorRef = useRef<any>(null);
const openai = aiApiKey
? new OpenAI({ apiKey: aiApiKey, dangerouslyAllowBrowser: true })
: null;
// 处理编辑器挂载
const handleEditorDidMount = (editor: any) => {
editorRef.current = editor;
// 添加快捷键支持
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
handleSave();
});
// 添加AI建议快捷键
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Space, () => {
const selection = editor.getSelection();
const selectedText = editor.getModel().getValueInRange(selection);
if (selectedText) {
setAiPrompt(`完善这段代码: ${selectedText}`);
handleAiSuggest(`完善这段代码: ${selectedText}`);
} else {
setShowAiPanel(true);
}
});
};
// 处理代码变更
const handleCodeChange = (value: string | undefined) => {
if (value !== undefined) {
setCode(value);
onChange?.(value);
}
};
// 保存代码
const handleSave = () => {
onSave?.(code);
};
// 应用AI建议
const applyAiSuggestion = () => {
if (!aiSuggestion) return;
const editor = editorRef.current;
if (!editor) return;
const selection = editor.getSelection();
// 如果有选择区域,替换它
if (!selection.isEmpty()) {
editor.executeEdits("ai-suggestion", [
{
range: selection,
text: aiSuggestion,
},
]);
} else {
// 否则在当前光标位置插入
const position = editor.getPosition();
editor.executeEdits("ai-suggestion", [
{
range: new monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column
),
text: aiSuggestion,
},
]);
}
// 清除建议
setAiSuggestion(null);
setShowAiPanel(false);
};
// 请求AI建议
const handleAiSuggest = async (prompt: string) => {
if (!openai) {
setErrorMessage("未配置AI API密钥");
return;
}
setIsGenerating(true);
setErrorMessage(null);
try {
const editor = editorRef.current;
const currentCode = editor.getValue();
const selection = editor.getSelection();
const selectedText =
selection && !selection.isEmpty()
? editor.getModel().getValueInRange(selection)
: "";
const selectedLines = selectedText
? `选中的代码:\n${selectedText}\n`
: "";
const completion = await openai.chat.completions.create({
model: "gpt-4-1106-preview",
messages: [
{
role: "system",
content: `你是一个代码助手。你需要帮助用户编写或完善${language}代码。
只返回代码部分,不要包含解释或其他文本。确保代码可以直接运行或集成到现有代码中。`,
},
{
role: "user",
content: `${prompt}\n\n当前文件内容:\n${currentCode}\n\n${selectedLines}`,
},
],
temperature: 0.3,
});
const suggestion = completion.choices[0].message.content;
// 移除可能的代码块标记
const cleanedSuggestion = suggestion
?.replace(/```[\w]*\n/g, "")
.replace(/```$/g, "")
.trim();
setAiSuggestion(cleanedSuggestion || null);
setShowAiPanel(true);
} catch (error) {
console.error("AI建议生成失败:", error);
setErrorMessage("AI建议生成失败,请重试");
} finally {
setIsGenerating(false);
}
};
// 处理AI操作
const handleAiAction = (action: string) => {
const editor = editorRef.current;
if (!editor) return;
const selection = editor.getSelection();
const selectedText =
selection && !selection.isEmpty()
? editor.getModel().getValueInRange(selection)
: "";
let prompt = "";
switch (action) {
case "complete":
prompt = `完成这段代码: ${selectedText}`;
break;
case "refactor":
prompt = `重构这段代码,提高可读性和性能: ${selectedText}`;
break;
case "explain":
prompt = `解释这段代码的功能: ${selectedText}`;
break;
case "optimize":
prompt = `优化这段代码的性能: ${selectedText}`;
break;
case "comment":
prompt = `为这段代码添加详细注释: ${selectedText}`;
break;
default:
return;
}
setAiPrompt(prompt);
handleAiSuggest(prompt);
};
return (
<div className="ai-code-editor">
<div className="editor-toolbar">
<button onClick={handleSave} className="save-button">
保存 (Ctrl+S)
</button>
<div className="ai-actions">
<button
onClick={() => setShowAiPanel(!showAiPanel)}
className="ai-button"
>
AI助手 (Ctrl+Space)
</button>
<div className="ai-quick-actions">
<button onClick={() => handleAiAction("complete")}>完成代码</button>
<button onClick={() => handleAiAction("refactor")}>重构</button>
<button onClick={() => handleAiAction("optimize")}>优化</button>
<button onClick={() => handleAiAction("comment")}>添加注释</button>
</div>
</div>
</div>
<div className="editor-container">
<Editor
height="70vh"
language={language}
theme={theme}
value={code}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
options={{
minimap: { enabled: true },
scrollBeyondLastLine: false,
fontSize: 14,
wordWrap: "on",
autoIndent: "full",
formatOnPaste: true,
formatOnType: true,
}}
/>
{showAiPanel && (
<div className="ai-panel">
<div className="ai-panel-header">
<h3>AI助手</h3>
<button
onClick={() => setShowAiPanel(false)}
className="close-button"
>
×
</button>
</div>
<div className="ai-prompt-container">
<input
type="text"
value={aiPrompt}
onChange={(e) => setAiPrompt(e.target.value)}
placeholder="输入提示,如'重构这段代码'或'优化性能'"
className="ai-prompt-input"
/>
<button
onClick={() => handleAiSuggest(aiPrompt)}
disabled={isGenerating || !aiPrompt.trim()}
className="generate-button"
>
{isGenerating ? "生成中..." : "生成"}
</button>
</div>
{errorMessage && (
<div className="error-message">{errorMessage}</div>
)}
{isGenerating && (
<div className="generating-indicator">
<div className="spinner"></div>
<p>AI正在分析代码并生成建议...</p>
</div>
)}
{aiSuggestion && (
<div className="ai-suggestion">
<div className="suggestion-header">
<h4>AI建议</h4>
<button onClick={applyAiSuggestion} className="apply-button">
应用
</button>
</div>
<pre className="suggestion-code">
<code>{aiSuggestion}</code>
</pre>
</div>
)}
</div>
)}
</div>
</div>
);
};
export default AICodeEditor;
10. 前端 AI 技术的未来展望
随着前端与 AI 技术的融合不断深入,我们可以预见以下趋势将在未来几年内成为主流:
1. 更智能的用户界面
- 自适应 UI:根据用户行为和偏好动态调整界面布局
- 预测性交互:预测用户意图提前加载内容和功能
- 情感感知界面:通过分析用户行为和输入调整界面风格和反馈
2. 前端开发的 AI 赋能
- AI 辅助编码:更智能的代码生成和完成
- 自动化测试:AI 生成测试用例和执行测试
- 代码优化:自动分析和优化性能瓶颈
- 设计转代码:从设计草图直接生成高质量前端代码
3. 本地化 AI 能力
- 边缘 AI:更多计算在用户设备上完成
- 轻量级模型:专为前端优化的小型 AI 模型
- 混合推理:结合本地和云端推理能力
- 持续学习:基于用户行为不断改进本地模型
4. 无缝多模态体验
- 语音 UI:自然语言控制界面和功能
- 视觉理解:摄像头/图像输入的智能解析
- AR/VR 集成:将 AI 能力延伸到虚拟和增强现实环境
- 跨设备协同:桌面和移动设备间的智能交互协调
总结与实践建议
在将 AI 集成到前端应用中时,关键是找到人工智能增强而非取代用户体验的平衡点。遵循以下原则可以帮助你成功地构建 AI 驱动的前端应用:
- 以用户为中心:AI 功能应该解决真实用户问题,而不是为技术而技术
- 渐进增强:保持应用的基本功能在没有 AI 的情况下也能正常工作
- 开发迭代:从小功能开始,收集用户反馈,不断改进
- 性能与隐私:密切关注前端 AI 功能对性能的影响,始终注重用户隐私
- 持续学习:AI 领域发展迅速,保持学习最新技术和最佳实践
下一篇预告
敬请期待我们的下一篇文章:《【React 全栈开发】服务端渲染与 API 整合最佳实践》,我们将深入探讨:
- 构建高性能的 React 服务端渲染应用
- 前端与后端 API 的无缝集成策略
- 数据获取与状态管理的全栈方案
- 身份验证与安全最佳实践
关于作者
Hi,我是 hyy,一位热爱技术的全栈开发者:
- 🚀 专注 TypeScript 全栈开发,偏前端技术栈
- 💼 多元工作背景(跨国企业、技术外包、创业公司)
- 📝 掘金活跃技术作者
- 🎵 电子音乐爱好者
- 🎮 游戏玩家
- 💻 技术分享达人
加入我们
欢迎加入前端技术交流圈,与 10000+开发者一起:
- 探讨前端最新技术趋势
- 解决开发难题
- 分享职场经验
- 获取优质学习资源
添加方式:掘金摸鱼沸点 👈 扫码进群