diff --git a/web/src/assets/images/conversation/conversation.svg b/web/src/assets/images/conversation/conversation.svg index 2ebc02fb..a21f34bc 100644 --- a/web/src/assets/images/conversation/conversation.svg +++ b/web/src/assets/images/conversation/conversation.svg @@ -1,11 +1,12 @@ - + 对话 - - - - - + + + + + + diff --git a/web/src/assets/images/conversation/deepThinking.svg b/web/src/assets/images/conversation/deepThinking.svg index b7658bf4..58ad411f 100644 --- a/web/src/assets/images/conversation/deepThinking.svg +++ b/web/src/assets/images/conversation/deepThinking.svg @@ -1,16 +1,29 @@ 深度思考 - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/images/conversation/normalReply.svg b/web/src/assets/images/conversation/normalReply.svg new file mode 100644 index 00000000..19b8c28d --- /dev/null +++ b/web/src/assets/images/conversation/normalReply.svg @@ -0,0 +1,28 @@ + + + 正常 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/conversation/quickReply.svg b/web/src/assets/images/conversation/quickReply.svg new file mode 100644 index 00000000..9a90ef1c --- /dev/null +++ b/web/src/assets/images/conversation/quickReply.svg @@ -0,0 +1,28 @@ + + + 快速回复 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/conversation/redbear.png b/web/src/assets/images/conversation/redbear.png new file mode 100644 index 00000000..8fd5e2f6 Binary files /dev/null and b/web/src/assets/images/conversation/redbear.png differ diff --git a/web/src/components/Chat/ChatInput.tsx b/web/src/components/Chat/ChatInput.tsx index 516b9528..8d265578 100644 --- a/web/src/components/Chat/ChatInput.tsx +++ b/web/src/components/Chat/ChatInput.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:14 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-06 13:36:20 + * @Last Modified time: 2026-03-19 17:35:14 */ import { type FC, useEffect, useMemo, useState } from 'react' import { Flex, Input, Form } from 'antd' @@ -77,7 +77,7 @@ const ChatInput: FC = ({ return (
- {previewFileList.length > 0 &&
@@ -142,7 +142,8 @@ const ChatInput: FC = ({ ) })} -
} +
+
} {/* Message input form */}
diff --git a/web/src/components/Chat/index.tsx b/web/src/components/Chat/index.tsx index 9a60918a..eccd422f 100644 --- a/web/src/components/Chat/index.tsx +++ b/web/src/components/Chat/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2025-12-10 16:46:09 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-06 21:05:09 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 14:57:56 */ import { type FC } from 'react' import ChatInput from './ChatInput' @@ -25,10 +25,11 @@ const Chat: FC = ({ labelFormat, errorDesc, fileList, - fileChange + fileChange, + className }) => { return ( -
+
{/* Chat content display area */} void; + className?: string; } /** diff --git a/web/src/components/RbCard/ResultCard.tsx b/web/src/components/RbCard/ResultCard.tsx new file mode 100644 index 00000000..495148f9 --- /dev/null +++ b/web/src/components/RbCard/ResultCard.tsx @@ -0,0 +1,80 @@ +/* + * @Author: ZhaoYing + * @Date: 2026-02-03 17:30:51 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 15:44:02 + */ +/** + * ResultCard Component + * Collapsible card wrapper for configuration sections + */ + +import { type FC, type ReactNode } from 'react' +import clsx from 'clsx'; +import { Flex, Space, Tooltip } from 'antd'; + +import RbCard from '@/components/RbCard/Card' + +/** + * Component props + */ +interface ResultCardProps { + title: string | ReactNode; + subTitle?: string | ReactNode; + children: ReactNode; + expanded?: boolean; + handleExpand?: () => void; + className?: string; + headerClassName?: string; + bodyClassName?: string; + extra?: ReactNode; + isMiSans?: boolean; +} + +const ResultCard: FC = ({ + title, + subTitle, + children, + expanded, + handleExpand, + extra, + className, + headerClassName, + bodyClassName, + isMiSans = true, +}) => { + return ( + + + {title} + {subTitle && +
+
} +
+ + {extra} + {handleExpand &&
} +
+
} + headerType="borderless" + headerClassName={headerClassName ?? "rb:min-h-[40px]! rb:text-[#212332]! rb:text-[14px]!"} + bodyClassName={bodyClassName ?? "rb:py-0! rb:px-3!"} + className={className ?? "rb:bg-[#F6F6F6]!"} + > + {((expanded && handleExpand) || (!handleExpand))&& children} +
+ ) +} + +export default ResultCard \ No newline at end of file diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index f8f9f59c..457b318c 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1714,6 +1714,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re fileUrl: 'File URL', addRemoteFile: 'Add Remote File', variableConfig: 'Variable Configuration', + + chatTitle: 'Red Bear Space', }, login: { title: 'Red Bear Memory Science', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index ab0896cf..7a0ca153 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1710,6 +1710,8 @@ export const zh = { fileUrl: '文件链接', addRemoteFile: '添加远程文件', variableConfig: '变量配置', + + chatTitle: '记忆熊空间', }, login: { title: '红熊记忆科学', diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 8a67b3ae..ff04d56b 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:58:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-04 12:10:44 + * @Last Modified time: 2026-03-19 17:19:20 */ /** * Conversation Page @@ -24,7 +24,6 @@ import type { HistoryItem, QueryParams, UploadFileListModalRef } from './types' import Empty from '@/components/Empty' import { formatDateTime } from '@/utils/format'; import { randomString } from '@/utils/common' -import BgImg from '@/assets/images/conversation/bg.png' import ChatEmpty from '@/assets/images/empty/chatEmpty.png' import Chat from '@/components/Chat' import type { ChatItem } from '@/components/Chat/types' @@ -345,20 +344,26 @@ const Conversation: FC = () => { return ( -
-
+ +
+
{t('memoryConversation.chatTitle')}
+
+ + handleChangeHistory(null)} >
{t('memoryConversation.startANewConversation')} -
+ {historyList.length > 0 &&
{ {Object.entries(groupHistoryList).map(([date, items]) => (
{date.replace(/\u200e|\u200f/g, '')}
- {items.map(item => ( -
-
handleChangeHistory(item.id)} - > - {item.title} -
-
- ))} + + + {items.map(item => ( +
+
handleChangeHistory(item.id)} + > + {item.title} +
+
+ ))} +
))}
} -
diff --git a/web/src/views/MemoryConversation/index.module.css b/web/src/views/MemoryConversation/index.module.css new file mode 100644 index 00000000..32522f65 --- /dev/null +++ b/web/src/views/MemoryConversation/index.module.css @@ -0,0 +1,11 @@ +.segmented { + background: #F6F6F6; + border: 1px solid #EBEBEB; +} +.segmented:global(.ant-segmented .ant-segmented-item-label) { + min-height: 24px; + line-height: 24px; + padding: 0 8px; + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/web/src/views/MemoryConversation/index.tsx b/web/src/views/MemoryConversation/index.tsx index e0797b90..5c306428 100644 --- a/web/src/views/MemoryConversation/index.tsx +++ b/web/src/views/MemoryConversation/index.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 17:09:03 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 17:09:03 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-19 16:00:39 */ /** * Memory Conversation Page @@ -12,48 +12,39 @@ import { type FC, type ReactNode, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Col, Row, App, Skeleton, Space, Select, Flex } from 'antd' +import { Col, Row, App, Skeleton, Select, Segmented, Tooltip, Flex } from 'antd' import dayjs from 'dayjs' import type { AnyObject } from 'antd/es/_util/type'; -import clsx from 'clsx' import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg' import AnalysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png' -import Card from './components/Card' import { readService, getUserMemoryList } from '@/api/memory' import Empty from '@/components/Empty' import Markdown from '@/components/Markdown' import type { Data } from '@/views/UserMemory/types' import Chat from '@/components/Chat' -import MemoryFunctionIcon from '@/assets/images/conversation/memoryFunction.svg' -import OnlineIcon from '@/assets/images/conversation/online.svg' -import DeepThinkingIcon from '@/assets/images/conversation/deepThinking.svg' -import ButtonCheckbox from '@/components/ButtonCheckbox' -import DeepThinkingCheckedIcon from '@/assets/images/conversation/deepThinkingChecked.svg' -import OnlineCheckedIcon from '@/assets/images/conversation/onlineChecked.svg' -import MemoryFunctionCheckedIcon from '@/assets/images/conversation/memoryFunctionChecked.svg' import type { ChatItem } from '@/components/Chat/types' +import RbCard from '@/components/RbCard/Card'; +import styles from './index.module.css' +import ResultCard from '@/components/RbCard/ResultCard' /** Search mode configuration */ const searchSwitchList = [ { - icon: DeepThinkingIcon, - checkedIcon: DeepThinkingCheckedIcon, + icon:
, value: '0', - label: 'deepThinking' + key: 'deepThinking' }, { - icon: MemoryFunctionIcon, - checkedIcon: MemoryFunctionCheckedIcon, + icon:
, value: '1', - label: 'normalReply' + key: 'normalReply' }, { - icon: OnlineIcon, - checkedIcon: OnlineCheckedIcon, + icon:
, value: '2', - label: 'quickReply' + key: 'quickReply' }, ] @@ -105,7 +96,7 @@ export interface LogItem { * Content wrapper component for analysis items */ const ContentWrapper: FC<{ children: ReactNode }> = ({ children }) => ( -
+
{children}
) @@ -120,6 +111,7 @@ const MemoryConversation: FC = () => { const [userList, setUserList] = useState([]) const [search_switch, setSearchSwitch] = useState('0') const [msg, setMsg] = useState('') + const [expandedLogs, setExpandedLogs] = useState>({}) /** Load user list on mount */ useEffect(() => { @@ -139,6 +131,7 @@ const MemoryConversation: FC = () => { } setChatData(prev => [...prev, { content: msg, created_at: new Date().getTime(), role: 'user' }]) setLoading(true) + setExpandedLogs({}) readService({ message: msg, end_user_id: userId, @@ -149,6 +142,7 @@ const MemoryConversation: FC = () => { const response = res as { answer: string; intermediate_outputs: LogItem[] } setChatData(prev => [...prev, { content: response.answer || '-', created_at: new Date().getTime(), role: 'assistant' }]) setLogs(response.intermediate_outputs) + setExpandedLogs(Object.fromEntries(response.intermediate_outputs.map((_, i) => [i, true]))) }) .finally(() => { setLoading(false) @@ -175,46 +169,51 @@ const MemoryConversation: FC = () => { placeholder={t('memoryConversation.searchPlaceholder')} style={{ width: '100%', marginBottom: '16px' }} onChange={setUserId} + variant="borderless" + className="rb:bg-white rb:rounded-lg" /> - + - } - contentClassName='rb:h-[calc(100vh-362px)]' + className="rb:pt-0!" + contentClassName='rb:h-[calc(100%-144px)]' data={chatData} onChange={setMsg} onSend={handleSend} loading={loading} labelFormat={(item) => dayjs(item.created_at).locale('en').format('MMMM D, YYYY [at] h:mm A')} > - - {searchSwitchList.map(item => ( - handleChange(item.value)} - > - {t(`memoryConversation.${item.label}`)} - - ))} - + ({ + ...item, + icon: {item.icon} + }))} + shape="round" + className={styles.segmented} + onChange={handleChange} + /> - + - {loading ? @@ -226,98 +225,98 @@ const MemoryConversation: FC = () => { subTitle={t('memoryConversation.memoryConversationAnalysisEmptySubTitle')} size={[270, 170]} /> - : + : {logs.map((log, logIndex) => ( -
setExpandedLogs(prev => ({ ...prev, [logIndex]: !prev[logIndex] }))} + extra={log.type === 'verification' &&
{log.result}
} > -
{log.title}
- {log.type === 'problem_split' && Array.isArray(log.data) && log.data.length > 0 - ? - {log.data.map(vo => ( - - <> -
{vo.id}. {vo.question}
-
{vo.reason}
- -
- ))} -
- : log.type === 'problem_extension' && log.data && Object.keys(log.data).length > 0 - ? - {Object.keys(log.data).map((key: string) => ( - - <> -
{key}
- {(log.data as Record)[key].map((item, index) => ( -
{item}
+ {log.type === 'problem_split' && Array.isArray(log.data) && log.data.length > 0 + ? + {log.data.map(vo => ( + + <> +
{vo.id}. {vo.question}
+
{vo.reason}
+ +
+ ))} +
+ : log.type === 'problem_extension' && log.data && Object.keys(log.data).length > 0 + ? + {Object.keys(log.data).map((key: string) => ( + + <> +
{key}
+ {(log.data as Record)[key].map((item, index) => ( +
{item}
+ ))} + +
+ ))} +
+ : log.type === 'search_result' && log.raw_results + ? +
{log.query}
+
+ {typeof log.raw_results === 'string' + ? + : <> + {log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string }, index: number) => ( +
{item.statement}
))} - - - ))} - - : log.type === 'search_result' && log.raw_results - ? -
{log.query}
-
- {typeof log.raw_results === 'string' - ? - : <> - {log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string }, index: number) => ( -
{item.statement}
- ))} - {log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string }, index: number) => ( -
{item.content}
- ))} - - } -
-
- : log.type === 'retrieval_summary' && log.summary - ?
{log.summary}
- : log.type === 'verification' - ? -
{log.query}
-
{log.reason}
-
{log.result}
+ {log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string }, index: number) => ( +
{item.content}
+ ))} + + } +
- : log.type === 'output_type' - ? + : log.type === 'retrieval_summary' && log.summary + ? +
{log.summary}
+
+ : log.type === 'verification' + ? +
{log.query}
+
{log.reason}
+
{log.result}
+
+ : log.type === 'output_type' + ? +
{log.query}
+
{log.summary}
+
+ : log.type === 'input_summary' && log.raw_results + ?
{log.query}
-
{log.summary}
+
{log.summary}
+
+ {typeof log.raw_results === 'string' + ? + : <> + {log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string; } , index: number) => ( +
{item.statement}
+ ))} + {log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string; }, index: number) => ( +
{item.content}
+ ))} + + } +
- : log.type === 'input_summary' && log.raw_results - ? -
{log.query}
-
{log.summary}
-
- {typeof log.raw_results === 'string' - ? - : <> - {log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string; } , index: number) => ( -
{item.statement}
- ))} - {log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string; }, index: number) => ( -
{item.content}
- ))} - - } -
-
- : null - } -
+ : null + } + ))} -
} -
+ + } +
diff --git a/web/src/views/MemoryExtractionEngine/components/Result.tsx b/web/src/views/MemoryExtractionEngine/components/Result.tsx index 3c39bcc1..063d6d6f 100644 --- a/web/src/views/MemoryExtractionEngine/components/Result.tsx +++ b/web/src/views/MemoryExtractionEngine/components/Result.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 17:30:11 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-19 14:22:20 + * @Last Modified time: 2026-03-19 15:38:38 */ /** * Result Component @@ -16,7 +16,6 @@ import { useTranslation } from 'react-i18next' import { Space, Button, Progress, Form, Input, Flex } from 'antd' import { ExclamationCircleFilled, LoadingOutlined } from '@ant-design/icons' import clsx from 'clsx' -import ResultCard from './ResultCard' import type { AnyObject } from 'antd/es/_util/type'; import Card from './Card' @@ -29,6 +28,7 @@ import Markdown from '@/components/Markdown' import { groupDataByType } from '../constant' import Empty from '@/components/Empty' import NoDataIcon from '@/assets/images/empty/noData.png' +import ResultCard from '@/components/RbCard/ResultCard' /** Result metric mapping */ const resultObj = {