diff --git a/web/src/assets/images/conversation/ai.png b/web/src/assets/images/conversation/ai.png new file mode 100644 index 00000000..3783a543 Binary files /dev/null and b/web/src/assets/images/conversation/ai.png differ diff --git a/web/src/assets/images/conversation/user.png b/web/src/assets/images/conversation/user.png new file mode 100644 index 00000000..671ab044 Binary files /dev/null and b/web/src/assets/images/conversation/user.png differ diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index 0276916f..3ef69136 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:17 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-27 14:17:38 + * @Last Modified time: 2026-03-31 15:01:53 */ import { type FC, useRef, useEffect, useState } from 'react' import clsx from 'clsx' @@ -37,6 +37,22 @@ const ChatContent: FC = ({ const prevDataLengthRef = useRef(data.length); const isScrolledToBottomRef = useRef(true); const audioRef = useRef(null) + const [expandedReasoning, setExpandedReasoning] = useState>(new Set()) + const [manualToggledReasoning, setManualToggledReasoning] = useState>(new Set()) + + const toggleReasoning = (index: number) => { + setManualToggledReasoning(prev => new Set(prev).add(index)) + setExpandedReasoning(prev => { + const next = new Set(prev) + next.has(index) ? next.delete(index) : next.add(index) + return next + }) + } + + const isReasoningExpanded = (index: number) => { + if (manualToggledReasoning.has(index)) return expandedReasoning.has(index) + return !data[index]?.content + } const [playingIndex, setPlayingIndex] = useState(null) const handlePlay = (audio_url: string, audio_status?: string) => { @@ -124,7 +140,7 @@ const ChatContent: FC = ({ {labelFormat(item)} } - {item.meta_data?.files && item.meta_data?.files.length > 0 && + {item.meta_data?.files && item.meta_data?.files.length > 0 && {item.meta_data?.files?.map((file) => { if (file.type.includes('image')) { return ( @@ -178,6 +194,22 @@ const ChatContent: FC = ({ 'rb:mt-1.5': labelPosition === 'top', 'rb:mb-1.5': labelPosition === 'bottom', })}> + {item.meta_data?.reasoning_content &&
+ toggleReasoning(index)} + > + {t('memoryConversation.reasoning_content')} +
+
+ {isReasoningExpanded(index) && } +
} {item.status &&
} {item.subContent && renderRuntime && renderRuntime(item, index)} {/* Render message content using Markdown component */} diff --git a/web/src/components/Chat/index.tsx b/web/src/components/Chat/index.tsx index 49feaf33..f7c0f32e 100644 --- a/web/src/components/Chat/index.tsx +++ b/web/src/components/Chat/index.tsx @@ -27,12 +27,14 @@ const Chat: FC = ({ fileList, fileChange, className, - renderRuntime + renderRuntime, + conversationId }) => { return (
{/* Chat content display area */} void; className?: string; renderRuntime?: (item: ChatItem, index: number) => ReactNode; + conversationId?: string | null; } /** diff --git a/web/src/components/PageScrollList/index.tsx b/web/src/components/PageScrollList/index.tsx index fd449848..f3ef0f86 100644 --- a/web/src/components/PageScrollList/index.tsx +++ b/web/src/components/PageScrollList/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-02 15:18:19 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-27 15:52:37 + * @Last Modified time: 2026-03-31 15:31:18 */ /** * PageScrollList Component @@ -48,7 +48,7 @@ interface PageScrollListProps> { /** API endpoint URL */ url: string; /** Function to render each list item */ - renderItem: (item: T) => React.ReactNode; + renderItem: (item: T, index: number) => React.ReactNode; /** Query parameters for API request */ query?: Q; /** Number of columns in grid layout */ @@ -57,6 +57,8 @@ interface PageScrollListProps> { className?: string; needLoading?: boolean; heightClass?: string; + gutter?: [number, number] | number; + onTotalChange?: (total: number) => void; } const defaultHeightClass = 'rb:h-[calc(100vh-116px)]!'; @@ -70,6 +72,8 @@ const PageScrollList = forwardRef(>({ className = '', needLoading = true, heightClass, + gutter = [12, 12], + onTotalChange, }: PageScrollListProps, ref: React.Ref) => { /** Expose refresh method to parent component */ useImperativeHandle(ref, () => ({ @@ -88,6 +92,7 @@ const PageScrollList = forwardRef(>({ const pageRef = useRef(1); const loadingRef = useRef(false); const hasMoreRef = useRef(true); + const [total, setTotal] = useState(0); /** Load more data from API with pagination */ const loadMoreData = (reset?: boolean) => { @@ -107,6 +112,9 @@ const PageScrollList = forwardRef(>({ setData(prev => reset ? results : [...prev, ...results]); hasMoreRef.current = response.page?.hasnext; setHasMore(response.page?.hasnext); + const newTotal = response.page?.total || 0; + setTotal(newTotal); + onTotalChange?.(newTotal); }) .catch(() => { hasMoreRef.current = false; @@ -156,11 +164,11 @@ const PageScrollList = forwardRef(>({ {/* Render grid list or empty state */} {data.length > 0 ? ( {data.map((item, index) => ( - {renderItem(item)} + {renderItem(item, index)} ))} diff --git a/web/src/components/RbModal/index.css b/web/src/components/RbModal/index.css index 8dabc4ab..56d95248 100644 --- a/web/src/components/RbModal/index.css +++ b/web/src/components/RbModal/index.css @@ -1,3 +1,6 @@ +.rb-modal { + top: 40px; +} .rb-modal .ant-modal-footer .ant-btn { height: 32px !important; padding: 0 15px !important; diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 47f68705..8c5f6237 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -627,6 +627,8 @@ export const en = { vision: 'Vision', audio: 'Audio', video: 'Video', + thinking: 'Deep Thinking', + is_thinking: 'Deep Thinking Support', }, knowledgeBase: { home: 'Home', @@ -1421,6 +1423,7 @@ export const en = { citation: 'Citation and Attribution', citation_desc: 'Display the attribution of source documents and generated content', invalidVariablesTitle: "The following undefined variables are referenced in the conversation opening. Do you want to save the opening configuration?", + deep_thinking: 'Enable Deep Thinking', apps: 'My Apps', sharing: 'Sharing', @@ -1594,6 +1597,8 @@ export const en = { core_entities: 'Core Entities', communityDetailEmptyDesc: 'Click on a community in the chart on the left to view details', communityLoadingTip: 'Generating community graph', + assistant: 'AI Assistant', + totalRagMemory: 'Total number of memories', }, space: { createSpace: 'Create Space', @@ -1782,6 +1787,11 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re vision_id: 'Vision model', audio_id: 'Audio model', video_id: 'Video model', + onlyDelete: 'Only Delete Fill', + semanticFiltering: 'Semantic Filtering', + sceneFocus: 'Scene Focus', + loose: 'Loose', + strict: 'Strict', }, memoryConversation: { searchPlaceholder: 'Enter user ID...', @@ -1829,6 +1839,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re stopAudioRecorder: 'Stop Recording', startAudioRecorder: 'Start Recording', citations: 'Citations', + reasoning_content: 'Deep reasoning Content', }, login: { title: 'Red Bear Memory Science', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 8c22a506..c50d86ed 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -795,6 +795,7 @@ export const zh = { citation: '引用和归属', citation_desc: '显示源文档和生成内容的归属部分', invalidVariablesTitle: "对话开场白中引用了以下未定义的变量,是否保存开场白配置?", + deep_thinking: '开启深度思考', apps: '我的应用', sharing: '共享', @@ -1274,6 +1275,8 @@ export const zh = { vision: '视觉', audio: '音频', video: '视频', + thinking: '深度思考', + is_thinking: '支持深度思考', }, timezones: { 'Asia/Shanghai': '中国标准时间 (UTC+8)', @@ -1592,6 +1595,8 @@ export const zh = { core_entities: '核心实体', communityDetailEmptyDesc: '点击左侧图表中的社区查看详情', communityLoadingTip: '社区图谱生成中', + assistant: 'AI 助手', + totalRagMemory: '记忆总数', }, space: { createSpace: '创建空间', @@ -1778,6 +1783,11 @@ export const zh = { vision_id: '视觉模型', audio_id: '音频模型', video_id: '视频模型', + onlyDelete: '仅删填充', + semanticFiltering: '语义过滤', + sceneFocus: '场景聚焦', + loose: '宽松', + strict: '严格', }, memoryConversation: { chatEmpty:'有什么我可以帮您的吗?', @@ -1825,6 +1835,7 @@ export const zh = { stopAudioRecorder: '停止录音', startAudioRecorder: '开始录音', citations: '引用', + reasoning_content: '深度思考内容', }, login: { title: '红熊记忆科学', diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 0cfdde05..07859527 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:21 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-27 18:13:51 + * @Last Modified time: 2026-03-31 16:50:10 */ import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useMemo } from 'react'; import { useTranslation } from 'react-i18next' @@ -194,7 +194,7 @@ const Agent = forwardRef { - modelConfigModalRef.current?.handleOpen('model') + modelConfigModalRef.current?.handleOpen('model', { ...defaultModel, model_parameters : values?.model_parameters }) } /** * Clear all debugging chat sessions @@ -287,7 +287,7 @@ const Agent = forwardRef { const opening_statement = form.getFieldValue(['features', 'opening_statement']) - console.log('opening_statement', opening_statement, defaultModel, chatList) if (opening_statement?.enabled && opening_statement?.statement && opening_statement?.statement.trim() !== '') { const assistantMsg: ChatItem = { diff --git a/web/src/views/ApplicationConfig/TestChat/index.tsx b/web/src/views/ApplicationConfig/TestChat/index.tsx index de98b9a7..b3fca33f 100644 --- a/web/src/views/ApplicationConfig/TestChat/index.tsx +++ b/web/src/views/ApplicationConfig/TestChat/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-03-13 17:27:52 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 15:35:13 + * @Last Modified time: 2026-03-31 16:04:15 */ import { type FC, useState, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -171,6 +171,7 @@ const TestChat: FC = ({ ...lastMsg, content: lastMsg.content + content, meta_data: { + ...(lastMsg.meta_data || {}), audio_url: audio_url || lastMsg.meta_data?.audio_url, audio_status: audio_status || lastMsg.meta_data?.audio_status, citations: citations || lastMsg.meta_data?.citations @@ -180,6 +181,24 @@ const TestChat: FC = ({ return newList }) } + const updateAssistantReasoningMessage = (content: string) => { + if (!content) return + if (streamLoading) setStreamLoading(false) + setChatList(prev => { + const newList = [...prev] + const lastMsg = newList[newList.length - 1] + if (lastMsg?.role === 'assistant') { + newList[newList.length - 1] = { + ...lastMsg, + meta_data: { + ...(lastMsg.meta_data || {}), + reasoning_content: (lastMsg.meta_data?.reasoning_content || '') + content + } + } + } + return newList + }) + } const updateErrorAssistantMessage = (message_length: number) => { if (message_length > 0) return @@ -273,6 +292,10 @@ const TestChat: FC = ({ case 'start': if (conversation_id && conversationId !== conversation_id) setConversationId(conversation_id) break + case 'reasoning': + updateAssistantReasoningMessage(content) + if (conversation_id && conversationId !== conversation_id) setConversationId(conversation_id) + break case 'message': updateAssistantMessage(content) if (conversation_id && conversationId !== conversation_id) setConversationId(conversation_id) diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx index 42ae43a9..c2abf17d 100644 --- a/web/src/views/ApplicationConfig/components/Chat.tsx +++ b/web/src/views/ApplicationConfig/components/Chat.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:39 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-27 17:59:07 + * @Last Modified time: 2026-03-31 15:02:07 */ /** * Chat debugging component for application testing @@ -141,6 +141,36 @@ const Chat: FC = ({ } } /** Update assistant message with streaming content */ + const updateAssistantReasoningMessage = (content?: string, model_config_id?: string, conversation_id?: string) => { + if (!content || !model_config_id) return + updateChatList(prev => { + const targetIndex = prev.findIndex(item => item.model_config_id === model_config_id); + if (targetIndex !== -1) { + const modelChatList = [...prev] + const curModelChat = modelChatList[targetIndex] + const curChatMsgList = curModelChat.list || [] + const lastMsg = curChatMsgList[curChatMsgList.length - 1] + if (lastMsg && lastMsg.role === 'assistant') { + modelChatList[targetIndex] = { + ...modelChatList[targetIndex], + conversation_id, + list: [ + ...curChatMsgList.slice(0, curChatMsgList.length - 1), + { + ...lastMsg, + meta_data: { + reasoning_content: (lastMsg.meta_data?.reasoning_content || '') + (content || ''), + } + } + ] + } + } + return [...modelChatList] + } + return prev; + }) + } + /** Update assistant message with streaming content */ const updateAssistantMessage = (content?: string, model_config_id?: string, conversation_id?: string, audio_url?: string, citations?: any[]) => { if ((!content && !audio_url && (!citations || citations?.length < 1)) || !model_config_id) return updateChatList(prev => { @@ -160,6 +190,7 @@ const Chat: FC = ({ ...lastMsg, content: lastMsg.content + (content || ''), meta_data: { + ...(lastMsg.meta_data || {}), ...(audio_url !== undefined ? { audio_url, audio_status: 'pending' } : {}), citations: citations || lastMsg.meta_data?.citations } @@ -274,6 +305,9 @@ const Chat: FC = ({ }; switch (item.event) { + case 'model_reasoning': + updateAssistantReasoningMessage(content, model_config_id, conversation_id) + break; case 'model_message': updateAssistantMessage(content, model_config_id, conversation_id, audio_url) break; diff --git a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx index 148afd5a..a80a5905 100644 --- a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:28:07 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-25 11:28:02 + * @Last Modified time: 2026-03-31 16:56:57 */ /** * Model Configuration Modal @@ -11,7 +11,7 @@ */ import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'; -import { Form, type SelectProps } from 'antd'; +import { Form, type SelectProps, Checkbox } from 'antd'; import { useTranslation } from 'react-i18next'; import type { ModelConfig, ModelConfigModalRef, Config, Source } from '../types' @@ -70,7 +70,8 @@ const ModelConfigModal = forwardRef( if (source === 'model') { form.setFieldsValue({ ...(data?.model_parameters || {}), - default_model_config_id: data.default_model_config_id || '' + default_model_config_id: data.default_model_config_id || '', + capability: model?.capability || [] }) } else if (source === 'chat' || source === 'multi_agent') { if (model) { @@ -103,9 +104,12 @@ const ModelConfigModal = forwardRef( const handleChange: SelectProps['onChange'] = (_value, option) => { if (source === 'chat') { form.setFieldValue('label', (option as Model).name) - } else { - form.setFieldValue('capability', (option as Model).capability) } + + form.setFieldsValue({ + capability: (option as Model).capability, + deep_thinking: false, + }) } /** Expose methods to parent component */ @@ -115,8 +119,12 @@ const ModelConfigModal = forwardRef( })); useEffect(() => { - form.setFieldsValue({...(data?.model_parameters || {})}) + const { deep_thinking: _, ...rest } = data?.model_parameters || {} + form.setFieldsValue(rest) }, [values?.default_model_config_id]) + + + console.log('handleChange values', values) return ( ( /> } - {source === 'model' &&