深度思考

main
wangyunfei 1 month ago
parent eb53daf45a
commit 37dce725f3

@ -80,108 +80,53 @@ export const callDeepSeekAPI = async (messages, options = {}) => {
return content
}
// 本地服务调用 - 流式版本
// 本地服务调用 - 非流式
export const callLocalChatAPI = async (prompt, onStreamData) => {
const base = '/api/chat/stream'
const url = `${base}?prompt=${encodeURIComponent(prompt)}`
// 逐步调试日志
// eslint-disable-next-line no-console
console.debug('[callLocalChatAPI] url:', url)
try {
// eslint-disable-next-line no-console
console.debug('[callLocalChatAPI] fetch start')
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) {
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 err = new Error(msg)
err.status = resp.status
throw err
}
// 流式读取响应
const reader = resp.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
//等待完整响应
const responseText = await resp.text()
// console.log('API response text:', responseText)
// 解析响应内容
let thoughtContent = ''
let messageContent = ''
let currentEvent = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
// 按行处理响应
const lines = responseText.split(/\r?\n/)
for (const line of lines) {
if (line.startsWith('event:')) {
currentEvent = line.substring(6).trim()
} else if (line.startsWith('data:')) {
const data = line.substring(5)
// 按行处理缓冲区
const lines = buffer.split(/\r?\n/)
buffer = lines.pop() || '' // 保留最后一行(可能不完整)
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
}
}
if (currentEvent === 'thought') {
thoughtContent += data
} else if (currentEvent === 'message') {
messageContent += data
}
}
} finally {
reader.releaseLock()
}
// 返回最终结果
console.log('API returning:', { thought: thoughtContent, message: messageContent }) // 调试信息
console.log('API returning:3333', { thought: thoughtContent, message: messageContent }) // 调试信息
// 确保返回的内容不为undefined
return {

@ -43,75 +43,36 @@ function ChatConversation() {
const [editModalVisible, setEditModalVisible] = useState(false)
const [editingConversation, setEditingConversation] = useState(null)
const [newTitle, setNewTitle] = useState('')
// 流式输出状态
const [streamingThought, setStreamingThought] = useState('')
const [streamingMessage, setStreamingMessage] = useState('')
const [isStreaming, setIsStreaming] = useState(false)
const [displayedThought, setDisplayedThought] = useState('')
const [displayedMessage, setDisplayedMessage] = useState('')
// 流式显示控制
const [deepThinkingEnabled, setDeepThinkingEnabled] = useState(false)
const [typingMessage, setTypingMessage] = useState('')
const [isTyping, setIsTyping] = useState(false)
// 流式输出状态 - 已移除
const messagesEndRef = useRef(null)
const inputRef = useRef(null)
const typewriterTimeoutRef = useRef(null)
// 获取当前会话
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);
// useEffect(() => {
// if (chatScrollRef.current) {
// chatScrollRef.current.scrollTop = chatScrollRef.current.scrollHeight;
// }
// }, [currentMessages, loading]); // currentMessages 是你的消息数组
const scrollToBottom = () => {
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 => {
@ -138,9 +99,34 @@ function ChatConversation() {
}
}, [])
// useEffect(() => {
// scrollToBottom()
// }, [currentMessages])
// 打字机效果函数 - 简化版本用于测试
const typewriterEffect = (text, onComplete) => {
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 = () => {
@ -195,60 +181,31 @@ function ChatConversation() {
setInputValue('')
setLoading(true)
setIsStreaming(true)
setStreamingThought('')
setStreamingMessage('')
setDisplayedThought('')
setDisplayedMessage('')
try {
// 调用本地后端接口(流式版本)
const result = await callLocalChatAPI(messageText, (streamData) => {
console.log('Stream data received:', streamData)
if (streamData.type === 'thought') {
console.log('Setting streamingThought:', streamData.content)
// 使用totalContent作为完整内容content作为新增内容
setStreamingThought(streamData.totalContent || streamData.content)
} else if (streamData.type === 'message') {
console.log('Setting streamingMessage:', streamData.content)
// 使用totalContent作为完整内容content作为新增内容
setStreamingMessage(streamData.totalContent || streamData.content)
} else if (streamData.type === 'complete') {
// 流式输出完成,将最终内容添加到对话存储
// 使用complete事件中传递的最终内容
const finalThought = streamData.thoughtContent || streamingThought
const finalMessage = streamData.messageContent || streamingMessage
const finalContent = finalThought + (finalMessage ? '\n\n' + finalMessage : '')
console.log('Complete event - finalThought:', finalThought)
console.log('Complete event - finalMessage:', finalMessage)
console.log('Complete event - finalContent:', finalContent)
if (finalContent.trim()) {
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', '抱歉,我无法生成回复内容。')
}
// 调用本地后端接口(非流式版本)
const result = await callLocalChatAPI(messageText)
console.log('API result:', result)
// 直接处理结果
const thought = result?.thought || ''
const message = result?.message || ''
console.log('Processed content:', { thought, message, deepThinkingEnabled })
// 根据深度思考按钮状态决定是否包含思考内容
const finalContent = deepThinkingEnabled
? thought + (message ? '\n\n' + message : '')
: message
console.log('Final content:', finalContent)
if (finalContent.trim()) {
// 暂时直接添加消息,不使用打字机效果
console.log('Adding message directly:', finalContent)
conversationStore.addMessage('assistant', finalContent)
} else {
console.log('No content, adding fallback message')
conversationStore.addMessage('assistant', '抱歉,我无法生成回复内容。')
}
} catch (error) {
const errorMessage = handleAPIError(error)
@ -256,11 +213,6 @@ function ChatConversation() {
// 添加错误消息到独立存储
conversationStore.addMessage('assistant', `抱歉,我遇到了一个问题:${errorMessage}`)
setIsStreaming(false)
setStreamingThought('')
setStreamingMessage('')
setDisplayedThought('')
setDisplayedMessage('')
} finally {
setLoading(false)
}
@ -318,6 +270,17 @@ function ChatConversation() {
// 渲染单条消息 ==================================
const renderMessage = (msg, index) => {
const isUser = msg.role === 'user'
const displayContent = msg.content || ''
// 调试信息
if (msg.role === 'assistant') {
console.log('Rendering assistant message:', {
index,
content: msg.content,
displayContent
})
}
return (
<div
key={index}
@ -325,7 +288,7 @@ function ChatConversation() {
>
{!isUser && (
<div className='ds-avatar'>
<RobotOutlined />
<RobotOutlined />
</div>
)}
{isUser && <div className='ds-avatar user'><LinuxOutlined /></div>}
@ -337,12 +300,13 @@ function ChatConversation() {
{msg.timestamp
? (typeof msg.timestamp === 'string'
? msg.timestamp
: msg.timestamp.toLocaleString())
: msg.timestamp.toLocaleString()) // 时间
: ''}
</span>
{!isUser && (
<span className='actions'>
<Tooltip title='复制'>
<span className='actions'>
{/* 复制按钮 */}
<Tooltip title='复制'>
<Button
size='small'
type='text'
@ -358,7 +322,8 @@ function ChatConversation() {
}}
/>
</Tooltip>
<Tooltip title='重新生成(示例)'>
{/* 重新生成按钮 */}
<Tooltip title='重新生成(示例)'>
<Button
size='small'
type='text'
@ -380,69 +345,15 @@ function ChatConversation() {
{/* 回答框 */}
<div className='ds-message-content'>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
>
{msg.content || ''}
</ReactMarkdown>
</div>
</div>
</div>
)
}
// 渲染流式输出消息
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>
{displayContent ? (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
>
{displayContent}
</ReactMarkdown>
) : (
<div style={{ color: '#999', fontStyle: 'italic' }}>
暂无内容
</div>
)}
</div>
@ -450,6 +361,7 @@ function ChatConversation() {
</div>
)
}
// =================================================
return (
@ -467,11 +379,8 @@ function ChatConversation() {
{/* 历史消息 */}
{currentMessages.map(renderMessage)}
{/* 流式输出消息 */}
{renderStreamingMessage()}
{/* 加载状态(当没有流式输出时显示) */}
{loading && !isStreaming && (
{/* 加载状态 */}
{loading && (
<div className='ds-message-row from-assistant thinking'>
<div className='ds-avatar'>
<RobotOutlined />
@ -519,20 +428,16 @@ function ChatConversation() {
/>
</div>
{/* 模型切换 */}
{/* <Select
defaultValue="智能定性"
{/* 深度思考按钮 */}
<Button
type={deepThinkingEnabled ? 'primary' : 'default'}
style={{
minWidth: 120, maxWidth: 200, marginRight: 8
minWidth: 100, maxWidth: 120, marginRight: 8
}}
onchange={handleChange}
options={[
{ value: "智能定性", label: "智能定性" },
// { value: "GPT-5 mini", label: "GPT-5 mini" },
// { value: "Claude Sonnet 4", label: "Claude Sonnet 4" },
// { value: "Kimi Senior", label: "Kimi Senior" },
]}>
</Select> */}
onClick={() => setDeepThinkingEnabled(!deepThinkingEnabled)}
>
深度思考
</Button>
{/* 上传文件 */}
<Upload className='ds-uploading'>
@ -561,9 +466,9 @@ function ChatConversation() {
<Select
className='ds-select'
dropdownClassName="ds-select-dropdown"
defaultValue="模型反馈"
defaultValue="模型反馈"
style={{
minWidth: 120, maxWidth: 200, marginLeft: 'auto',marginRight: 8
minWidth: 120, maxWidth: 200, marginLeft: 'auto', marginRight: 8
}}
onchange={handleChange}
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 {
// display: flex;
// align-items: flex-end;

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

Loading…
Cancel
Save