深度思考

main
wangyunfei 1 month ago
parent eb53daf45a
commit 37dce725f3

@ -80,108 +80,53 @@ export const callDeepSeekAPI = async (messages, options = {}) => {
return content return content
} }
// 本地服务调用 - 流式版本
// 本地服务调用 - 非流式
export const callLocalChatAPI = async (prompt, onStreamData) => { export const callLocalChatAPI = async (prompt, onStreamData) => {
const base = '/api/chat/stream' const base = '/api/chat/stream'
const url = `${base}?prompt=${encodeURIComponent(prompt)}` const url = `${base}?prompt=${encodeURIComponent(prompt)}`
// 逐步调试日志
// eslint-disable-next-line no-console
console.debug('[callLocalChatAPI] url:', url)
try { try {
// eslint-disable-next-line no-console
console.debug('[callLocalChatAPI] fetch start')
const resp = await fetch(url, { method: 'GET' }) const resp = await fetch(url, { method: 'GET' })
// eslint-disable-next-line no-console
console.debug('[callLocalChatAPI] fetch done', {
ok: resp.ok,
status: resp.status,
statusText: resp.statusText,
headers: Object.fromEntries(resp.headers.entries())
})
if (!resp.ok) { if (!resp.ok) {
const txt = await resp.text().catch(() => null) const txt = await resp.text().catch(() => null)
// eslint-disable-next-line no-console
console.error('[callLocalChatAPI] non-OK response body:', txt)
const msg = txt || resp.statusText || `HTTP_${resp.status}` const msg = txt || resp.statusText || `HTTP_${resp.status}`
const err = new Error(msg) const err = new Error(msg)
err.status = resp.status err.status = resp.status
throw err throw err
} }
// 流式读取响应 //等待完整响应
const reader = resp.body.getReader() const responseText = await resp.text()
const decoder = new TextDecoder() // console.log('API response text:', responseText)
let buffer = ''
// 解析响应内容
let thoughtContent = '' let thoughtContent = ''
let messageContent = '' let messageContent = ''
let currentEvent = '' let currentEvent = ''
try { // 按行处理响应
while (true) { const lines = responseText.split(/\r?\n/)
const { done, value } = await reader.read()
if (done) break for (const line of lines) {
if (line.startsWith('event:')) {
buffer += decoder.decode(value, { stream: true }) currentEvent = line.substring(6).trim()
} else if (line.startsWith('data:')) {
const data = line.substring(5)
// 按行处理缓冲区 if (currentEvent === 'thought') {
const lines = buffer.split(/\r?\n/) thoughtContent += data
buffer = lines.pop() || '' // 保留最后一行(可能不完整) } else if (currentEvent === 'message') {
messageContent += data
for (const line of lines) {
if (line.startsWith('event:')) {
currentEvent = line.substring(6).trim()
} else if (line.startsWith('data:')) {
const data = line.substring(5)
if (currentEvent === 'thought') {
thoughtContent += data
console.log('Thought data received:', data, 'Total thought:', thoughtContent) // 调试信息
// 流式输出思考内容 - 立即传递新接收到的数据
if (onStreamData) {
onStreamData({
type: 'thought',
content: data, // 新接收到的数据
totalContent: thoughtContent, // 累积的完整内容
isComplete: false
})
}
} else if (currentEvent === 'message') {
messageContent += data
console.log('Message data received:', data, 'Total message:', messageContent) // 调试信息
// 流式输出消息内容 - 立即传递新接收到的数据
if (onStreamData) {
onStreamData({
type: 'message',
content: data, // 新接收到的数据
totalContent: messageContent, // 累积的完整内容
isComplete: false
})
}
} else if (currentEvent === 'complete') {
// 完成信号
if (onStreamData) {
onStreamData({
type: 'complete',
content: '',
isComplete: true,
thoughtContent: thoughtContent,
messageContent: messageContent
})
}
// 流式处理完成直接返回null避免重复处理
return null
}
}
} }
} }
} finally {
reader.releaseLock()
} }
// 返回最终结果 // 返回最终结果
console.log('API returning:', { thought: thoughtContent, message: messageContent }) // 调试信息 console.log('API returning:3333', { thought: thoughtContent, message: messageContent }) // 调试信息
// 确保返回的内容不为undefined // 确保返回的内容不为undefined
return { return {

@ -43,75 +43,36 @@ function ChatConversation() {
const [editModalVisible, setEditModalVisible] = useState(false) const [editModalVisible, setEditModalVisible] = useState(false)
const [editingConversation, setEditingConversation] = useState(null) const [editingConversation, setEditingConversation] = useState(null)
const [newTitle, setNewTitle] = useState('') const [newTitle, setNewTitle] = useState('')
const [deepThinkingEnabled, setDeepThinkingEnabled] = useState(false)
// 流式输出状态 const [typingMessage, setTypingMessage] = useState('')
const [streamingThought, setStreamingThought] = useState('')
const [streamingMessage, setStreamingMessage] = useState('')
const [isStreaming, setIsStreaming] = useState(false)
const [displayedThought, setDisplayedThought] = useState('')
const [displayedMessage, setDisplayedMessage] = useState('')
// 流式显示控制
const [isTyping, setIsTyping] = useState(false) const [isTyping, setIsTyping] = useState(false)
// 流式输出状态 - 已移除
const messagesEndRef = useRef(null) const messagesEndRef = useRef(null)
const inputRef = useRef(null) const inputRef = useRef(null)
const typewriterTimeoutRef = useRef(null) const typewriterTimeoutRef = useRef(null)
// 获取当前会话 // 获取当前会话
const currentConversation = conversations.find(conv => conv.id === currentConversationId) const currentConversation = conversations.find(conv => conv.id === currentConversationId)
const currentMessages = currentConversation?.messages || [] const currentMessages = currentConversation?.messages || [] // 当前会话消息
// 自动滚动到底部 - 禁用
// 调试信息
console.log('Current conversation state:', {
conversations: conversations.length,
currentConversationId,
currentConversation: !!currentConversation,
currentMessages: currentMessages.length
})
// console.log(currentMessages,"=========================333333")
const chatScrollRef = useRef(null); const chatScrollRef = useRef(null);
// useEffect(() => {
// if (chatScrollRef.current) {
// chatScrollRef.current.scrollTop = chatScrollRef.current.scrollHeight;
// }
// }, [currentMessages, loading]); // currentMessages 是你的消息数组
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
} }
// 打字机效果函数 // 流式输出相关函数 - 已移除
const typewriterEffect = (targetText, setDisplayedText, delay = 20) => {
let currentIndex = 0
const currentDisplayed = displayedThought + displayedMessage
const typeNextChar = () => {
if (currentIndex < targetText.length) {
setDisplayedText(targetText.substring(0, currentIndex + 1))
currentIndex++
typewriterTimeoutRef.current = setTimeout(typeNextChar, delay)
}
}
typeNextChar()
}
// 监听流式内容变化,实现真正的流式显示
useEffect(() => {
if (streamingThought && streamingThought !== displayedThought) {
// 直接显示新内容,实现真正的流式显示
setDisplayedThought(streamingThought)
setIsTyping(true)
}
}, [streamingThought])
useEffect(() => {
if (streamingMessage && streamingMessage !== displayedMessage) {
// 直接显示新内容,实现真正的流式显示
setDisplayedMessage(streamingMessage)
setIsTyping(true)
}
}, [streamingMessage])
// 当流式输出停止时,停止打字状态
useEffect(() => {
if (!isStreaming) {
setIsTyping(false)
}
}, [isStreaming])
const handleChange = value => { const handleChange = value => {
@ -138,9 +99,34 @@ function ChatConversation() {
} }
}, []) }, [])
// useEffect(() => { // 打字机效果函数 - 简化版本用于测试
// scrollToBottom() const typewriterEffect = (text, onComplete) => {
// }, [currentMessages]) console.log('Typewriter effect started with text:', text)
let index = 0
setTypingMessage('')
setIsTyping(true)
const typeNextWords = () => {
if (index < text.length) {
// 简化每次显示3个字符
const nextIndex = Math.min(index + 3, text.length)
const currentText = text.substring(0, nextIndex)
console.log('Typing progress:', currentText)
setTypingMessage(currentText)
index = nextIndex
// 500ms间隔
typewriterTimeoutRef.current = setTimeout(typeNextWords, 500)
} else {
console.log('Typewriter effect completed')
setIsTyping(false)
onComplete(text)
}
}
typeNextWords()
}
// 创建新对话 // 创建新对话
const createNewConversation = () => { const createNewConversation = () => {
@ -195,60 +181,31 @@ function ChatConversation() {
setInputValue('') setInputValue('')
setLoading(true) setLoading(true)
setIsStreaming(true)
setStreamingThought('')
setStreamingMessage('')
setDisplayedThought('')
setDisplayedMessage('')
try { try {
// 调用本地后端接口(流式版本) // 调用本地后端接口(非流式版本)
const result = await callLocalChatAPI(messageText, (streamData) => { const result = await callLocalChatAPI(messageText)
console.log('Stream data received:', streamData) console.log('API result:', result)
if (streamData.type === 'thought') {
console.log('Setting streamingThought:', streamData.content) // 直接处理结果
// 使用totalContent作为完整内容content作为新增内容 const thought = result?.thought || ''
setStreamingThought(streamData.totalContent || streamData.content) const message = result?.message || ''
} else if (streamData.type === 'message') { console.log('Processed content:', { thought, message, deepThinkingEnabled })
console.log('Setting streamingMessage:', streamData.content)
// 使用totalContent作为完整内容content作为新增内容 // 根据深度思考按钮状态决定是否包含思考内容
setStreamingMessage(streamData.totalContent || streamData.content) const finalContent = deepThinkingEnabled
} else if (streamData.type === 'complete') { ? thought + (message ? '\n\n' + message : '')
// 流式输出完成,将最终内容添加到对话存储 : message
// 使用complete事件中传递的最终内容
const finalThought = streamData.thoughtContent || streamingThought console.log('Final content:', finalContent)
const finalMessage = streamData.messageContent || streamingMessage
const finalContent = finalThought + (finalMessage ? '\n\n' + finalMessage : '') if (finalContent.trim()) {
// 暂时直接添加消息,不使用打字机效果
console.log('Complete event - finalThought:', finalThought) console.log('Adding message directly:', finalContent)
console.log('Complete event - finalMessage:', finalMessage) conversationStore.addMessage('assistant', finalContent)
console.log('Complete event - finalContent:', finalContent) } else {
console.log('No content, adding fallback message')
if (finalContent.trim()) { conversationStore.addMessage('assistant', '抱歉,我无法生成回复内容。')
conversationStore.addMessage('assistant', finalContent)
} else {
conversationStore.addMessage('assistant', '抱歉,我无法生成回复内容。')
}
setIsStreaming(false)
setStreamingThought('')
setStreamingMessage('')
setDisplayedThought('')
setDisplayedMessage('')
}
})
// 如果API没有返回流式数据直接处理结果
if (result && !isStreaming) {
const thought = result?.thought || ''
const message = result?.message || ''
const finalContent = thought + (message ? '\n\n' + message : '')
if (finalContent.trim()) {
conversationStore.addMessage('assistant', finalContent)
} else {
conversationStore.addMessage('assistant', '抱歉,我无法生成回复内容。')
}
} }
} catch (error) { } catch (error) {
const errorMessage = handleAPIError(error) const errorMessage = handleAPIError(error)
@ -256,11 +213,6 @@ function ChatConversation() {
// 添加错误消息到独立存储 // 添加错误消息到独立存储
conversationStore.addMessage('assistant', `抱歉,我遇到了一个问题:${errorMessage}`) conversationStore.addMessage('assistant', `抱歉,我遇到了一个问题:${errorMessage}`)
setIsStreaming(false)
setStreamingThought('')
setStreamingMessage('')
setDisplayedThought('')
setDisplayedMessage('')
} finally { } finally {
setLoading(false) setLoading(false)
} }
@ -318,6 +270,17 @@ function ChatConversation() {
// 渲染单条消息 ================================== // 渲染单条消息 ==================================
const renderMessage = (msg, index) => { const renderMessage = (msg, index) => {
const isUser = msg.role === 'user' const isUser = msg.role === 'user'
const displayContent = msg.content || ''
// 调试信息
if (msg.role === 'assistant') {
console.log('Rendering assistant message:', {
index,
content: msg.content,
displayContent
})
}
return ( return (
<div <div
key={index} key={index}
@ -325,7 +288,7 @@ function ChatConversation() {
> >
{!isUser && ( {!isUser && (
<div className='ds-avatar'> <div className='ds-avatar'>
<RobotOutlined /> <RobotOutlined />
</div> </div>
)} )}
{isUser && <div className='ds-avatar user'><LinuxOutlined /></div>} {isUser && <div className='ds-avatar user'><LinuxOutlined /></div>}
@ -337,12 +300,13 @@ function ChatConversation() {
{msg.timestamp {msg.timestamp
? (typeof msg.timestamp === 'string' ? (typeof msg.timestamp === 'string'
? msg.timestamp ? msg.timestamp
: msg.timestamp.toLocaleString()) : msg.timestamp.toLocaleString()) // 时间
: ''} : ''}
</span> </span>
{!isUser && ( {!isUser && (
<span className='actions'> <span className='actions'>
<Tooltip title='复制'> {/* 复制按钮 */}
<Tooltip title='复制'>
<Button <Button
size='small' size='small'
type='text' type='text'
@ -358,7 +322,8 @@ function ChatConversation() {
}} }}
/> />
</Tooltip> </Tooltip>
<Tooltip title='重新生成(示例)'> {/* 重新生成按钮 */}
<Tooltip title='重新生成(示例)'>
<Button <Button
size='small' size='small'
type='text' type='text'
@ -380,69 +345,15 @@ function ChatConversation() {
{/* 回答框 */} {/* 回答框 */}
<div className='ds-message-content'> <div className='ds-message-content'>
<ReactMarkdown {displayContent ? (
remarkPlugins={[remarkGfm]} <ReactMarkdown
> remarkPlugins={[remarkGfm]}
{msg.content || ''} >
</ReactMarkdown> {displayContent}
</div> </ReactMarkdown>
</div> ) : (
</div> <div style={{ color: '#999', fontStyle: 'italic' }}>
) 暂无内容
}
// 渲染流式输出消息
const renderStreamingMessage = () => {
if (!isStreaming && !displayedThought && !displayedMessage) return null
return (
<div className='ds-message-row from-assistant streaming'>
<div className='ds-avatar'>
<RobotOutlined />
</div>
<div className='ds-message-body'>
<div className='ds-message-meta'>
<span className='role'>AI对话</span>
<span className='time'>正在思考</span>
</div>
<div className='ds-message-content'>
{/* 思考内容 - 浅灰色 */}
{displayedThought && (
<div className='ds-thought-content'>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
>
{displayedThought}
</ReactMarkdown>
</div>
)}
{/* 回复内容 - 正常颜色 */}
{displayedMessage && (
<div className='ds-response-content'>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
>
{displayedMessage}
</ReactMarkdown>
{/* 流式输入光标 */}
{isTyping && isStreaming && (
<span className='ds-typing-cursor'>|</span>
)}
</div>
)}
{/* 流式输出指示器 */}
{isStreaming && (
<div className='ds-streaming-indicator'>
<Spin
indicator={<LoadingOutlined style={{ fontSize: 14 }} spin />}
size='small'
/>
<span className='streaming-text'>
{isTyping ? '正在输入...' : '正在思考...'}
</span>
</div> </div>
)} )}
</div> </div>
@ -450,6 +361,7 @@ function ChatConversation() {
</div> </div>
) )
} }
// ================================================= // =================================================
return ( return (
@ -467,11 +379,8 @@ function ChatConversation() {
{/* 历史消息 */} {/* 历史消息 */}
{currentMessages.map(renderMessage)} {currentMessages.map(renderMessage)}
{/* 流式输出消息 */} {/* 加载状态 */}
{renderStreamingMessage()} {loading && (
{/* 加载状态(当没有流式输出时显示) */}
{loading && !isStreaming && (
<div className='ds-message-row from-assistant thinking'> <div className='ds-message-row from-assistant thinking'>
<div className='ds-avatar'> <div className='ds-avatar'>
<RobotOutlined /> <RobotOutlined />
@ -519,20 +428,16 @@ function ChatConversation() {
/> />
</div> </div>
{/* 模型切换 */} {/* 深度思考按钮 */}
{/* <Select <Button
defaultValue="智能定性" type={deepThinkingEnabled ? 'primary' : 'default'}
style={{ style={{
minWidth: 120, maxWidth: 200, marginRight: 8 minWidth: 100, maxWidth: 120, marginRight: 8
}} }}
onchange={handleChange} onClick={() => setDeepThinkingEnabled(!deepThinkingEnabled)}
options={[ >
{ value: "智能定性", label: "智能定性" }, 深度思考
// { value: "GPT-5 mini", label: "GPT-5 mini" }, </Button>
// { value: "Claude Sonnet 4", label: "Claude Sonnet 4" },
// { value: "Kimi Senior", label: "Kimi Senior" },
]}>
</Select> */}
{/* 上传文件 */} {/* 上传文件 */}
<Upload className='ds-uploading'> <Upload className='ds-uploading'>
@ -561,9 +466,9 @@ function ChatConversation() {
<Select <Select
className='ds-select' className='ds-select'
dropdownClassName="ds-select-dropdown" dropdownClassName="ds-select-dropdown"
defaultValue="模型反馈" defaultValue="模型反馈"
style={{ style={{
minWidth: 120, maxWidth: 200, marginLeft: 'auto',marginRight: 8 minWidth: 120, maxWidth: 200, marginLeft: 'auto', marginRight: 8
}} }}
onchange={handleChange} onchange={handleChange}
options={[ options={[

@ -407,6 +407,24 @@
} }
} }
// 打字机光标样式
.typewriter-cursor {
display: inline-block;
animation: typewriterBlink 1s infinite;
color: #3478f6;
font-weight: bold;
margin-left: 2px;
}
@keyframes typewriterBlink {
0%, 50% {
opacity: 1;
}
51%, 100% {
opacity: 0;
}
}
.ds-input-bar { .ds-input-bar {
// display: flex; // display: flex;
// align-items: flex-end; // align-items: flex-end;

@ -5,18 +5,18 @@ import './SystemContentList.less'
import { formatRoute, getDefaultRoute } from '@/utils/routeUtils' import { formatRoute, getDefaultRoute } from '@/utils/routeUtils'
import { import {
conversationStore, conversationStore,
} from '@/utils/pageConversationStore' } from '@/utils/pageConversationStore'
import { renderPageContent } from '../topnavbar/form/RenderPageContentForm' import { renderPageContent } from '../topnavbar/form/RenderPageContentForm'
const SystemContentList = (props) => { const SystemContentList = (props) => {
const dynamicRoute = window.dynamicRoute const dynamicRoute = window.dynamicRoute
const [expandedPage, setExpandedPage] = useState('conversation') const [expandedPage, setExpandedPage] = useState('conversation')
const conversationExpanded = expandedPage === 'conversation' const conversationExpanded = expandedPage === 'conversation'
const toggleExpand = (page) => { const toggleExpand = (page) => {
setExpandedPage(prev => prev === page ? null : page) setExpandedPage(prev => prev === page ? null : page)
} }
const [conversationConversations, setConversationConversations] = useState(conversationStore.getConversations()) const [conversationConversations, setConversationConversations] = useState(conversationStore.getConversations())
const location = useLocation() const location = useLocation()
@ -47,13 +47,13 @@ const SystemContentList = (props) => {
useEffect(() => { useEffect(() => {
const unsubscribeConversation = conversationStore.subscribe(({ conversations }) => { const unsubscribeConversation = conversationStore.subscribe(({ conversations }) => {
setConversationConversations(conversations) setConversationConversations(conversations)
}) })
return () => { return () => {
unsubscribeConversation() unsubscribeConversation()
} }
}, []) }, [])
const setRouteActive = (value, menu) => { const setRouteActive = (value, menu) => {
const curKey = value.key const curKey = value.key
@ -73,9 +73,9 @@ const SystemContentList = (props) => {
} }
return ( return (
<div className='pageContainer systemContent' style={{height:'100vh',maxWidth:'1200px',margin:'15px auto',padding:'15px'}}> <div className='pageContainer systemContent' style={{ height: '100vh', maxWidth: '1200px', margin: '15px auto', padding: '15px' }}>
<div style={{display:'flex',height:'100%'}}> <div style={{ display: 'flex', height: '100%' }}>
<div className='leftMenu' style={{width:'200px'}}> <div className='leftMenu' style={{ width: '200px' }}>
<Menu <Menu
openKeys={openKey} openKeys={openKey}
selectedKeys={selectedKey} selectedKeys={selectedKey}
@ -84,10 +84,10 @@ const SystemContentList = (props) => {
onOpenChange={value => setOpenKey(value)} onOpenChange={value => setOpenKey(value)}
mode='vertical' mode='vertical'
/> />
{/* 这是历史会话 */} {/* 历史会话 */}
<div className='contentHistory'> <div className='contentHistory'>
{renderPageContent('conversation', conversationConversations, conversationExpanded)} {renderPageContent('conversation', conversationConversations, conversationExpanded)}
</div> </div>
</div> </div>
<div className='rightContentMain'> <div className='rightContentMain'>

Loading…
Cancel
Save