React -> AI组件 -> 调用Ollama模型, qwen3:1.7B非常聪明

React -> AI组件 -> 调用Ollama模型, qwen3:1.7B非常聪明_第1张图片

使用 React 搭建一个现代化的聊天界面,支持与 Ollama 本地部署的大语言模型进行多轮对话。界面清爽、功能完整,支持 Markdown 渲染、代码高亮、 隐藏思考标签、流式渐进反馈、暗黑模式适配等特性。


核心功能亮点

✅ 模型选择支持
  • 启动时自动请求 http://localhost:11434/api/tags 获取所有本地模型。

  • 允许用户通过下拉框动态切换聊天使用的模型。

✅ 多轮对话支持
  • 聊天上下文由历史消息 messages 组成,发送请求时一并传入。

  • 用户每次发送内容后,bot 的响应将基于历史记录生成。

✅ 实时流式响应 + 处理
  • 使用 ReadableStream 实现逐段渲染。

  • ... 区块被识别并自动隐藏,直到关闭 后再更新 UI。

✅ Markdown 渲染 & 代码高亮
  • 借助 react-markdown + remark-gfm 支持 GitHub 风格 Markdown。

  • 使用 react-syntax-highlighter 实现代码块高亮显示,自动识别语言。

✅ 响应式 UI & 暗黑模式适配
  • 使用 Tailwind CSS 快速构建布局。

  • 检测 HTML dark 类名切换对应代码主题(oneLight / oneDark)。


import React, { useState, useRef, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneLight, oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

type Message = { text: string; sender: 'user' | 'bot' };

type Props = { value: string; onChange: (e: React.ChangeEvent) => void; onSend: () => void };
const ChatInput: React.FC = React.memo(({ value, onChange, onSend }) => (
    
e.key === 'Enter' && onSend()} />
)); const ChatWindow: React.FC = () => { const [models, setModels] = useState([]); const [selectedModel, setSelectedModel] = useState(''); const [messages, setMessages] = useState([ { text: '你好,我是 Ollama!请选择模型后开始聊天。', sender: 'bot' }, ]); const [input, setInput] = useState(''); const [isThinking, setIsThinking] = useState(false); const messagesEndRef = useRef(null); const isDark = document.documentElement.classList.contains('dark'); const scrollToBottom = () => messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); useEffect(scrollToBottom, [messages, isThinking]); // 获取模型列表 useEffect(() => { fetch('http://localhost:11434/api/tags') .then(res => res.json()) .then(data => { const names = data.models?.map((m: any) => m.name) || []; setModels(names); if (names.length) setSelectedModel(names[0]); }) .catch(err => { console.error('获取模型失败:', err); setMessages(prev => [...prev, { text: '无法获取模型列表', sender: 'bot' }]); }); }, []); const handleSend = async () => { if (!input.trim() || !selectedModel) return; // 1. 把用户消息加入 setMessages(prev => [...prev, { text: input, sender: 'user' }]); setInput(''); // 2. 预插入一条 bot 占位,用于后面一次性更新 setMessages(prev => [...prev, { text: '', sender: 'bot' }]); // 清洗 的工具 const cleanThink = (text: string) => text.replace(/[\s\S]*?<\/think>/g, ''); let fullText = ''; let thinkOpen = false; // 标记是否在 区间 try { const response = await fetch('http://localhost:11434/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: selectedModel, messages: [{ role: 'user', content: input }], }), }); const reader = response.body!.getReader(); const decoder = new TextDecoder('utf-8'); while (true) { const { value, done } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n').filter(l => l.trim()); for (const line of lines) { try { const data = JSON.parse(line); const c = data.message?.content || ''; // 检测思考开始 if (c.includes('')) { thinkOpen = true; setIsThinking(true); } fullText += c; // 检测思考结束 if (c.includes('')) { thinkOpen = false; setIsThinking(false); // 这时才做一次性更新:清洗掉所有 think 内容,并写入 UI const display = cleanThink(fullText).trim(); setMessages(prev => { const copy = [...prev]; copy[copy.length - 1] = { text: display, sender: 'bot' }; return copy; }); } } catch (e) { console.warn('解析流片段失败:', e); } } } // 如果整个流结束后,之前从未触发 (比如模型不输出 think),那也一次性更新 if (!thinkOpen) { // 每次都更新显示 const display = cleanThink(fullText).trim(); setMessages(prev => { const copy = [...prev]; copy[copy.length - 1] = { text: display, sender: 'bot' }; return copy; }); } } catch (err) { console.error('请求出错:', err); setMessages(prev => [ ...prev, { text: '请求出错,请检查服务是否开启。', sender: 'bot' }, ]); setIsThinking(false); } }; return (
{/* 模型选择 */}
{/* 聊天记录 */}
{/* 聊天记录渲染 */} {messages.map((msg, i) => (
{msg.sender === 'bot' ? (
{String(children).replace(/\n$/, '')} ); } return ( {children} ); } }} > {msg.text}
) : (
{msg.text}
)}
))} {isThinking &&
正在思考中…
}
{/* 输入区 */} setInput(e.target.value)} onSend={handleSend} />
); }; export default ChatWindow;

你可能感兴趣的:(react.js,前端,前端框架)