feat(web): memory chat ui upgrade

This commit is contained in:
zhaoying
2026-03-19 20:41:54 +08:00
parent 84c23e7c4e
commit 69c001bf84
15 changed files with 344 additions and 170 deletions

View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>对话</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-用户权限-对话" transform="translate(-317, -87)" fill="#212332" fill-rule="nonzero" stroke="#212332" stroke-width="0.3">
<g id="编组-14" transform="translate(252, 76)">
<g id="对话" transform="translate(65, 11)">
<path d="M12.5740039,11.1940039 L12.5740039,4.61400391 C12.5740039,3.95199219 12.0359961,3.41400391 11.3740039,3.41400391 L3.52597656,3.41400391 C2.86398438,3.41400391 2.32597656,3.95199219 2.32597656,4.61400391 L2.32597656,11.1959961 C2.32597656,11.8580078 2.86398438,12.3959961 3.52597656,12.3959961 L4.5,12.3959961 L4.5,13.4680078 C4.5,13.6939844 4.62599609,13.9 4.82800781,14.0019922 C4.91400391,14.0459961 5.00800781,14.0680078 5.1,14.0680078 C5.22400391,14.0680078 5.34800781,14.03 5.45199219,13.9540039 L7.6,12.3940039 L11.3740039,12.3940039 C12.0359961,12.3940039 12.5740039,11.8559961 12.5740039,11.1940039 Z M7.53599609,11.5940039 C7.40954959,11.5937486 7.28629101,11.6336699 7.18400391,11.7080078 L5.3,13.0759766 L5.3,12.1959766 C5.3,11.8659766 5.03,11.5959766 4.7,11.5959766 L3.52599609,11.5959766 C3.30599609,11.5959766 3.12599609,11.4159766 3.12599609,11.1959766 L3.12599609,4.61400391 C3.12599609,4.39400391 3.30599609,4.21400391 3.52599609,4.21400391 L11.3740234,4.21400391 C11.5940234,4.21400391 11.7740234,4.39400391 11.7740234,4.61400391 L11.7740234,11.1959961 C11.7740234,11.4159961 11.5940234,11.5959961 11.3740234,11.5959961 L7.53599609,11.5959961 L7.53599609,11.5940039 Z M16.4740234,6.03398437 L13.8419922,6.03398437 C13.6219922,6.03398437 13.4419922,6.21398437 13.4419922,6.43398437 C13.4419922,6.65398437 13.6219922,6.83398437 13.8419922,6.83398437 L16.4740234,6.83398437 C16.6940234,6.83398437 16.8740234,7.01398437 16.8740234,7.23398437 L16.8740234,13.8379883 C16.8740234,14.0579883 16.6940234,14.2379883 16.4740234,14.2379883 L14.9020117,14.2379883 C14.5720117,14.2379883 14.3020117,14.5079883 14.3020117,14.8379883 L14.3020117,15.5979883 L12.4260352,14.2659766 C12.3240234,14.1939844 12.2040234,14.1559766 12.0780273,14.1559766 L10.2160156,14.1559766 C9.99601563,14.1559766 9.81601563,13.9759766 9.81601563,13.7559766 L9.81601563,13.3759766 C9.81601563,13.1559766 9.63601563,12.9759766 9.41601562,12.9759766 C9.19601563,12.9759766 9.01601563,13.1559766 9.01601563,13.3759766 L9.01601563,13.7559766 C9.01601563,14.4179883 9.55402344,14.9559766 10.2160156,14.9559766 L12.0140234,14.9559766 L14.1540234,16.4759766 C14.2580273,16.5499805 14.3800195,16.5879884 14.5020117,16.5879884 C14.5981711,16.5879884 14.6929001,16.5647017 14.7780273,16.5199805 C14.9780273,16.4159766 15.1020117,16.2119727 15.1020117,15.9859961 L15.1020117,15.0399805 L16.4740234,15.0399805 C17.1360156,15.0399805 17.6740234,14.5019727 17.6740234,13.8399805 L17.6740234,7.23400391 C17.6740234,6.57199219 17.1360156,6.03398437 16.4740234,6.03398437 L16.4740234,6.03398437 Z" id="形状"></path>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="多模态对话" transform="translate(-28, -76)" fill-rule="nonzero">
<g id="编组-52" transform="translate(16, 64)">
<g id="对话" transform="translate(12, 12)">
<polygon id="路径" fill-opacity="0" fill="#FFFFFF" points="0 0 24 0 24 24 0 24"></polygon>
<path d="M18,7.25 L17.7499999,7.25 L17.7499999,6 C17.7477953,3.92984607 16.0701539,2.25220464 14,2.25 L6,2.25 C3.92984607,2.25220464 2.25220464,3.92984607 2.24999989,6 L2.24999989,16 C2.24999989,16.3048877 2.4342582,16.5795323 2.71652992,16.6947677 C2.99880163,16.810003 3.32274171,16.7428931 3.53599999,16.525 L5.27500001,14.75 L6.24999989,14.75 L6.24999989,16 C6.25220465,18.0701539 7.92984608,19.7477954 10,19.75 L18.725,19.75 L20.464,21.525 C20.6768829,21.7439454 21.0013711,21.8117255 21.2840975,21.6963045 C21.5668239,21.5808836 21.7511677,21.3053766 21.7499999,21 L21.7499999,11 C21.7477954,8.92984606 20.0701539,7.25220464 18,7.25 Z M4.96000001,13.25 C4.75835098,13.2498944 4.5651543,13.3309937 4.42399999,13.475 L3.75,14.163 L3.75,6 C3.75110269,4.75781635 4.75781635,3.75110269 6,3.75 L14,3.75 C15.2421836,3.75110269 16.2488973,4.75781635 16.25,6 L16.25,11 C16.2488973,12.2421836 15.2421836,13.2488973 14,13.25 L4.96000001,13.25 Z M20.25,19.163 L19.576,18.475 C19.4349106,18.3308982 19.2416718,18.2497812 19.04,18.25 L10,18.25 C8.75781636,18.2488973 7.7511027,17.2421837 7.75000001,16 L7.75000001,14.75 L14,14.75 C16.0701539,14.7477953 17.7477953,13.0701539 17.75,11 L17.75,8.74999999 L18,8.74999999 C19.2421837,8.75110268 20.2488973,9.75781634 20.25,11 L20.25,19.163 L20.25,19.163 Z" id="形状" fill="#155EEF"></path>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,16 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>深度思考</title>
<g id="V1.0版" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="红熊空间-记忆对话-默认对话" transform="translate(-584, -730)">
<g id="聊天页面" transform="translate(258, 128)">
<g id="输入框" transform="translate(16, 512)">
<g id="编组-13" transform="translate(302, 86)">
<g id="深度思考" transform="translate(8, 4)">
<g id="编组-12" transform="translate(1.5, 1.5)">
<path d="M6.5,12.6871843 C8.15972824,12.6871843 9.50520382,9.91708755 9.50520382,6.5 C9.50520382,3.08291245 8.15972824,0.312815665 6.5,0.312815665 C4.84027176,0.312815665 3.49479618,3.08291245 3.49479618,6.5 C3.49479618,9.91708755 4.84027176,12.6871843 6.5,12.6871843 Z" id="椭圆形" stroke="#212332" transform="translate(6.5, 6.5) rotate(45) translate(-6.5, -6.5)"></path>
<path d="M6.5,12.6871843 C8.15972824,12.6871843 9.50520382,9.91708755 9.50520382,6.5 C9.50520382,3.08291245 8.15972824,0.312815665 6.5,0.312815665 C4.84027176,0.312815665 3.49479618,3.08291245 3.49479618,6.5 C3.49479618,9.91708755 4.84027176,12.6871843 6.5,12.6871843 Z" id="椭圆形" stroke="#212332" transform="translate(6.5, 6.5) scale(-1, 1) rotate(45) translate(-6.5, -6.5)"></path>
<path d="M6.56835262,7.64616732 C7.20295459,7.64616732 7.71740114,7.13172077 7.71740114,6.4971188 C7.71740114,5.86251682 7.20295459,5.34807028 6.56835262,5.34807028 C5.93375065,5.34807028 5.4193041,5.86251682 5.4193041,6.4971188 C5.4193041,7.13172077 5.93375065,7.64616732 6.56835262,7.64616732 Z" id="椭圆形" fill="#212332"></path>
<defs>
<filter x="-3.4%" y="-14.2%" width="106.8%" height="131.7%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="6" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.0901960784 0 0 0 0 0.0901960784 0 0 0 0 0.0980392157 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆验证" transform="translate(-319, -742)">
<g id="编组-16" transform="translate(252, 108)">
<g id="输入框" filter="url(#filter-1)" transform="translate(11, 548)">
<g id="快速/深度" transform="translate(12, 80)">
<g id="深度思考" transform="translate(44, 6)">
<g id="编组-10" transform="translate(1, 1)">
<g id="编组-19" transform="translate(0, 0)" stroke="#171719">
<ellipse id="椭圆形" transform="translate(7, 7) rotate(45) translate(-7, -7)" cx="7" cy="7" rx="6.45619235" ry="3.44330259"></ellipse>
<ellipse id="椭圆形" transform="translate(7, 7) scale(-1, 1) rotate(45) translate(-7, -7)" cx="7" cy="7" rx="6.45619235" ry="3.44330259"></ellipse>
</g>
<path d="M5.86969152,7.53676092 C6.15546861,8.14102124 6.86991131,8.41511869 7.47874075,8.14725073 C7.79392248,8.01832584 8.04235006,7.76509185 8.16572607,7.44697405 C8.28910208,7.12885625 8.27656542,6.77385939 8.13105801,6.46528903 C7.98674675,6.15608424 7.72182693,5.92010861 7.39860254,5.81285903 C7.07537815,5.70560945 6.72237336,5.73655057 6.42260806,5.89840564 C5.82620371,6.20365055 5.58391445,6.93250061 5.86969152,7.53676092 L5.86969152,7.53676092 Z" id="路径" fill="#171719" fill-rule="nonzero"></path>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>正常</title>
<defs>
<filter x="-3.4%" y="-14.2%" width="106.8%" height="131.7%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="6" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.0901960784 0 0 0 0 0.0901960784 0 0 0 0 0.0980392157 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆验证" transform="translate(-353, -742)" fill="#171719" fill-rule="nonzero">
<g id="编组-16" transform="translate(252, 108)">
<g id="输入框" filter="url(#filter-1)" transform="translate(11, 548)">
<g id="快速/深度" transform="translate(12, 80)">
<g id="正常" transform="translate(78, 6)">
<path d="M7.99962468,2 C9.06023644,2 10.1103396,2.27993548 11.0200788,2.81896016 C11.260274,2.95967242 11.3398386,3.26938826 11.2002252,3.49944161 C11.1350138,3.61371625 11.0262186,3.69718613 10.8983165,3.73107216 C10.7704144,3.76495819 10.6341473,3.74641442 10.5201726,3.67961285 C9.75734215,3.23130772 8.88629417,2.99640503 7.99962468,2.99987592 C5.2396322,2.99987592 2.99981234,5.23935973 2.99981234,7.99925549 C2.99981234,10.7591513 5.2396322,12.9993796 7.99962468,12.9993796 C10.7603678,12.9993796 13.0001876,10.7591513 13.0001876,7.99925549 C13.0001876,7.56967366 12.9498968,7.14977044 12.8403077,6.73954585 C12.8060757,6.61118359 12.8248518,6.4745813 12.8924776,6.35999283 C12.9601034,6.24540436 13.0709978,6.1622867 13.2006005,6.12904827 C13.47082,6.05980891 13.740289,6.2198784 13.8100957,6.48939074 C13.9399512,6.97927783 14,7.48926666 14,7.99925549 C14,11.3093436 11.3098142,14 7.99962468,14 C4.68943516,13.9992555 2,11.3093436 2,7.99925549 C2,4.69065641 4.69018577,2 7.99962468,2 Z M12.3501595,4.14990772 C12.4825992,4.14990772 12.6091636,4.20414012 12.6999437,4.29978905 C12.7957173,4.39141511 12.8498172,4.51772577 12.8498172,4.6497084 C12.8498172,4.78169102 12.7957173,4.90800169 12.6999437,4.99962775 L7.6505911,10.049634 C7.55801526,10.142681 7.43257097,10.1963492 7.30080691,10.1992803 C7.16761349,10.1995402 7.04117199,10.1451763 6.95027209,10.049634 L5.00018765,8.09901973 C4.90441409,8.00739367 4.85031413,7.88108301 4.85031413,7.74910039 C4.85031413,7.61711776 4.90441409,7.4908071 5.00018765,7.39918104 C5.09096772,7.30353212 5.2175321,7.24929972 5.34997184,7.24929972 C5.48241159,7.24929972 5.60897597,7.30353212 5.69975604,7.39918104 L7.30005629,8.99987592 L12.0003753,4.29978905 C12.0911554,4.20414012 12.2177198,4.14990772 12.3501595,4.14990772 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>快速回复</title>
<defs>
<filter x="-3.4%" y="-14.2%" width="106.8%" height="131.7%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="2" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="6" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.0901960784 0 0 0 0 0.0901960784 0 0 0 0 0.0980392157 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="记忆验证" transform="translate(-285, -742)" stroke="#171719">
<g id="编组-16" transform="translate(252, 108)">
<g id="输入框" filter="url(#filter-1)" transform="translate(11, 548)">
<g id="快速/深度" transform="translate(12, 80)">
<g id="快速回复" transform="translate(10, 6)">
<path d="M7.87421377,2.54726615 L3.90048515,8.12578128 C3.64414483,8.48564439 3.72806663,8.98517595 4.08792974,9.24151627 C4.22344723,9.33804908 4.38569112,9.38992649 4.55207494,9.38992649 L6.7255258,9.38992649 C6.83598275,9.38992649 6.9255258,9.47946954 6.9255258,9.58992649 L6.9255258,12.7661445 C6.9255258,13.3184293 7.37324105,13.7661445 7.9255258,13.7661445 C8.2557012,13.7661445 8.56459514,13.6031667 8.75097255,13.3306245 L12.1441069,8.36879823 C12.3935099,8.00409291 12.3000389,7.50625978 11.9353335,7.25685682 C11.8023068,7.1658868 11.6449067,7.11721421 11.4837495,7.11721421 L9.888701,7.11721421 C9.77824405,7.11721421 9.688701,7.02767116 9.688701,6.91721421 L9.688701,3.12744764 C9.688701,2.57516289 9.24098575,2.12744764 8.688701,2.12744764 C8.36529181,2.12744764 8.0618498,2.2838535 7.87421377,2.54726615 Z" id="路径-2"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -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<ChatInputProps> = ({
return (
<div className={`rb:absolute rb:bottom-3 rb:left-0 rb:right-0 rb:w-full ${className}`}>
<Flex vertical justify="space-between" className={clsx("rb-border rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.1)] rb:rounded-xl rb:min-h-30", {
<Flex vertical justify="space-between" className={clsx("rb-border rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)] rb:rounded-3xl rb:min-h-30", {
' rb:border-[#171719]!': isFocus
})}>
{previewFileList.length > 0 && <div className="rb:overflow-x-auto rb:max-w-full"><Flex gap={14} className="rb:mx-3! rb:mt-3! rb:w-max!">
@@ -142,7 +142,8 @@ const ChatInput: FC<ChatInputProps> = ({
</Flex>
)
})}
</Flex></div>}
</Flex>
</div>}
{/* Message input form */}
<Form form={form} layout="vertical">
<Form.Item name="message" noStyle>

View File

@@ -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<ChatProps> = ({
labelFormat,
errorDesc,
fileList,
fileChange
fileChange,
className
}) => {
return (
<div className="rb:h-full rb:relative rb:pt-2">
<div className={`rb:h-full rb:relative rb:pt-2 ${className}`}>
{/* Chat content display area */}
<ChatContent
classNames={contentClassName}

View File

@@ -53,6 +53,7 @@ export interface ChatProps {
fileList?: any[];
/** Attachment update */
fileChange?: (fileList: any[]) => void;
className?: string;
}
/**

View File

@@ -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<ResultCardProps> = ({
title,
subTitle,
children,
expanded,
handleExpand,
extra,
className,
headerClassName,
bodyClassName,
isMiSans = true,
}) => {
return (
<RbCard
title={() => <Flex
align="center"
justify="space-between"
className={`${isMiSans ? 'rb:font-[MiSans-Bold] rb:font-bold' : 'rb:font-medium'} rb:cursor-pointer`}
onClick={handleExpand}
>
<Space size={4}>
{title}
{subTitle && <Tooltip title={subTitle}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
</Tooltip>}
</Space>
<Space size={4}>
{extra}
{handleExpand && <div
className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')] rb:transition-transform", {
'rb:rotate-180': !expanded,
'rb:rotate-0': expanded,
})}
></div>}
</Space>
</Flex>}
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}
</RbCard>
)
}
export default ResultCard

View File

@@ -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',

View File

@@ -1710,6 +1710,8 @@ export const zh = {
fileUrl: '文件链接',
addRemoteFile: '添加远程文件',
variableConfig: '变量配置',
chatTitle: '记忆熊空间',
},
login: {
title: '红熊记忆科学',

View File

@@ -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 (
<Flex className="rb:w-full rb:p-[-16px]!">
<div className="rb:w-86.25 rb:h-screen rb:overflow-hidden rb:border-r rb:border-[#EAECEE] rb:p-3">
<div className="rb:group rb:flex rb:items-center rb:justify-center rb:font-regular rb:cursor-pointer rb:mb-5 rb:border rb:border-[#DFE4ED] rb:hover:border-[#155EEF] rb:hover:text-[#155EEF] rb:rounded-lg rb:py-2.5"
<div className="rb:w-80 rb:h-screen rb:bg-[#F6F6F6] rb:overflow-hidden">
<Flex align="center" gap={8} className="rb:p-5!">
<div className="rb:size-6 rb:bg-cover rb:bg-[url('src/assets/images/conversation/redbear.png')]"></div>
<div className="rb:text-[16px] rb:leading-5 rb:font-[Gilroy-Extrabold] rb:font-extrabold">{t('memoryConversation.chatTitle')}</div>
</Flex>
<Flex align="center" gap={12}
className="rb:cursor-pointer rb:border rb:border-[#155EEF] rb:rounded-xl rb:p-3! rb:mx-4! rb:text-[16px] rb:font-medium rb:text-[#155EEF] rb:h-12! rb:mb-5!"
onClick={() => handleChangeHistory(null)}
>
<div
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:mr-2 rb:bg-cover rb:bg-[url('@/assets/images/conversation/conversation.svg')] rb:group-hover:bg-[url('@/assets/images/conversation/conversation_hover.svg')]"
></div>
{t('memoryConversation.startANewConversation')}
</div>
</Flex>
{historyList.length > 0 &&
<div
ref={scrollRef}
id="scrollableDiv"
className="rb:overflow-y-auto rb:h-[calc(100vh-255px)]"
className="rb:overflow-y-auto rb:h-[calc(100vh-144px)] rb:px-3!"
>
<InfiniteScroll
dataLength={historyList.length}
@@ -371,23 +376,25 @@ const Conversation: FC = () => {
{Object.entries(groupHistoryList).map(([date, items]) => (
<div key={date} className="rb:mt-6 rb:first:mt-0">
<div className="rb:leading-5 rb:text-[#5B6167] rb:mb-2 rb:pl-1 rb:font-regular">{date.replace(/\u200e|\u200f/g, '')}</div>
{items.map(item => (
<div key={item.updated_at} className="rb:mb-3">
<div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
'rb:bg-[#FFFFFF] rb:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.15)] rb:font-medium rb:hover:bg-[#FFFFFF]!': item.id === conversation_id,
})}
onClick={() => handleChangeHistory(item.id)}
>
{item.title}
</div>
</div>
))}
<Flex vertical gap={4}>
{items.map(item => (
<div key={item.updated_at} className="rb:mb-3">
<div className={clsx("rb:p-[8px_13px] rb:rounded-lg rb:leading-5 rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
'rb:bg-[#FFFFFF] rb:shadow-[0px_2px_4px_0px_rgba(0,0,0,0.15)] rb:font-medium rb:hover:bg-[#FFFFFF]!': item.id === conversation_id,
})}
onClick={() => handleChangeHistory(item.id)}
>
{item.title}
</div>
</div>
))}
</Flex>
</div>
))}
</InfiniteScroll>
</div>
}
<img src={BgImg} className="rb:absolute rb:bottom-0 rb:left-0 rb:w-86.25" />
</div>
<div className="rb:relative rb:h-screen rb:px-4 rb:flex-[1_1_auto]">

View File

@@ -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;
}

View File

@@ -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: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/conversation/deepThinking.svg')]"></div>,
value: '0',
label: 'deepThinking'
key: 'deepThinking'
},
{
icon: MemoryFunctionIcon,
checkedIcon: MemoryFunctionCheckedIcon,
icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/conversation/normalReply.svg')]"></div>,
value: '1',
label: 'normalReply'
key: 'normalReply'
},
{
icon: OnlineIcon,
checkedIcon: OnlineCheckedIcon,
icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/conversation/quickReply.svg')]"></div>,
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 }) => (
<div className="rb:p-3 rb:bg-[#F6F8FC] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<div className="rb:px-3 rb:py-2.5 rb:bg-white rb:rounded-xl">
{children}
</div>
)
@@ -120,6 +111,7 @@ const MemoryConversation: FC = () => {
const [userList, setUserList] = useState<Data[]>([])
const [search_switch, setSearchSwitch] = useState('0')
const [msg, setMsg] = useState<string>('')
const [expandedLogs, setExpandedLogs] = useState<Record<number, boolean>>({})
/** 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"
/>
</Col>
</Row>
<Row gutter={16} className="rb:h-[calc(100vh-152px)] rb:overflow-hidden">
<Row gutter={16}>
<Col span={12}>
<Card
<RbCard
title={t('memoryConversation.conversationContent')}
bodyClassName="rb:pb-[0]!"
headerType="borderless"
headerClassName="rb:min-h-[52px]! rb:font-[MiSans-Bold] rb:font-bold"
bodyClassName="rb:px-3! rb:py-0! rb:h-[calc(100%-52px)]!"
className="rb:h-[calc(100vh-124px)]!"
>
<Chat
empty={
<Empty url={ConversationEmptyIcon} className="rb:h-full" size={[140, 100]} title={t('memoryConversation.conversationContentEmpty')} isNeedSubTitle={false} />
}
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')}
>
<Flex gap={8}>
{searchSwitchList.map(item => (
<ButtonCheckbox
key={item.value}
icon={item.icon}
checkedIcon={item.checkedIcon}
checked={search_switch === item.value}
onChange={() => handleChange(item.value)}
>
{t(`memoryConversation.${item.label}`)}
</ButtonCheckbox>
))}
</Flex>
<Segmented
options={searchSwitchList.map(item => ({
...item,
icon: <Tooltip title={t(`memoryConversation.${item.key}`)}>{item.icon}</Tooltip>
}))}
shape="round"
className={styles.segmented}
onChange={handleChange}
/>
</Chat>
</Card>
</RbCard>
</Col>
<Col span={12}>
<Card
<RbCard
title={t('memoryConversation.memoryConversationAnalysis')}
bodyClassName='rb:overflow-auto'
headerType="borderless"
headerClassName="rb:min-h-[52px]! rb:font-[MiSans-Bold] rb:font-bold"
bodyClassName="rb:p-3! rb:pt-0! rb:h-[calc(100%-52px)]! rb:overflow-y-auto!"
className="rb:h-[calc(100vh-124px)]!"
>
{loading ?
<Skeleton active />
@@ -226,98 +225,98 @@ const MemoryConversation: FC = () => {
subTitle={t('memoryConversation.memoryConversationAnalysisEmptySubTitle')}
size={[270, 170]}
/>
: <Space size={12} direction="vertical" style={{width: '100%'}}>
: <Flex gap={12} vertical>
{logs.map((log, logIndex) => (
<div key={logIndex}
className={clsx(
`rb:p-[16px_24px] rb:rounded-lg`,
'rb:border rb:border-[#DFE4ED]',
{
'rb:shadow-[inset_4px_0px_0px_0px_#155EEF]': logIndex % 3 === 0,
'rb:shadow-[inset_4px_0px_0px_0px_#369F21]': logIndex % 3 === 1,
'rb:shadow-[inset_4px_0px_0px_0px_#9C6FFF]': logIndex % 3 === 2,
}
)}
<ResultCard
key={logIndex}
title={log.title}
isMiSans={false}
bodyClassName={`rb:p-3! rb:pt-0! ${!!expandedLogs[logIndex] ? 'rb:pb-3!' : 'rb:pb-0!'}`}
expanded={!!expandedLogs[logIndex]}
handleExpand={() => setExpandedLogs(prev => ({ ...prev, [logIndex]: !prev[logIndex] }))}
extra={log.type === 'verification' && <div className="rb-border rb:rounded-lg rb:py-1 rb:px-2 rb:text-[12px] rb:font-medium rb:leading-4.5 rb:text-[#FF5D34]">{log.result}</div>}
>
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-6">{log.title}</div>
{log.type === 'problem_split' && Array.isArray(log.data) && log.data.length > 0
? <Space size={12} direction="vertical" style={{width: '100%'}}>
{log.data.map(vo => (
<ContentWrapper key={vo.id}>
<>
<div className="rb:font-medium rb:text-[#212332]">{vo.id}. {vo.question}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{vo.reason}</div>
</>
</ContentWrapper>
))}
</Space>
: log.type === 'problem_extension' && log.data && Object.keys(log.data).length > 0
? <Space size={12} direction="vertical" style={{width: '100%'}}>
{Object.keys(log.data).map((key: string) => (
<ContentWrapper key={key}>
<>
<div className="rb:font-medium rb:text-[#212332]">{key}</div>
{(log.data as Record<string, string[]>)[key].map((item, index) => (
<div key={index} className="rb:mt-2 rb:text-[#5B6167] rb:text-[12px]">{item}</div>
{log.type === 'problem_split' && Array.isArray(log.data) && log.data.length > 0
? <Flex gap={12} vertical>
{log.data.map(vo => (
<ContentWrapper key={vo.id}>
<>
<div className="rb:font-medium rb:text-[#212332]">{vo.id}. {vo.question}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{vo.reason}</div>
</>
</ContentWrapper>
))}
</Flex>
: log.type === 'problem_extension' && log.data && Object.keys(log.data).length > 0
? <Flex gap={12} vertical>
{Object.keys(log.data).map((key: string) => (
<ContentWrapper key={key}>
<>
<div className="rb:font-medium rb:text-[#212332]">{key}</div>
{(log.data as Record<string, string[]>)[key].map((item, index) => (
<div key={index} className="rb:mt-2 rb:text-[#5B6167] rb:text-[12px]">{item}</div>
))}
</>
</ContentWrapper>
))}
</Flex>
: log.type === 'search_result' && log.raw_results
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332] rb:mb-2">{log.query}</div>
<div className='rb:mt-2 rb:text-[12px] rb:text-[#5B6167]'>
{typeof log.raw_results === 'string'
? <Markdown content={log.raw_results} />
: <>
{log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string }, index: number) => (
<div key={index}>{item.statement}</div>
))}
</>
</ContentWrapper>
))}
</Space>
: log.type === 'search_result' && log.raw_results
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332] rb:mb-2">{log.query}</div>
<div className='rb:mt-2 rb:text-[12px] rb:text-[#5B6167]'>
{typeof log.raw_results === 'string'
? <Markdown content={log.raw_results} />
: <>
{log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string }, index: number) => (
<div key={index}>{item.statement}</div>
))}
{log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string }, index: number) => (
<div key={index}>{item.content}</div>
))}
</>
}
</div>
</ContentWrapper>
: log.type === 'retrieval_summary' && log.summary
? <ContentWrapper><div className="rb:text-[12px] rb:text-[#5B6167]">{log.summary}</div></ContentWrapper>
: log.type === 'verification'
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332]">{log.query}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{log.reason}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{log.result}</div>
{log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string }, index: number) => (
<div key={index}>{item.content}</div>
))}
</>
}
</div>
</ContentWrapper>
: log.type === 'output_type'
? <ContentWrapper>
: log.type === 'retrieval_summary' && log.summary
? <ContentWrapper>
<div className="rb:text-[12px] rb:text-[#5B6167]">{log.summary}</div>
</ContentWrapper>
: log.type === 'verification'
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332]">{log.query}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{log.reason}</div>
<div className="rb:mt-2 rb:text-[12px] rb:text-[#5B6167]">{log.result}</div>
</ContentWrapper>
: log.type === 'output_type'
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332] rb:mb-2">{log.query}</div>
<div className="rb:text-[12px] rb:text-[#5B6167]">{log.summary}</div>
</ContentWrapper>
: log.type === 'input_summary' && log.raw_results
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332] rb:mb-2">{log.query}</div>
<div className="rb:text-[12px] rb:text-[#5B6167]">{log.summary}</div>
<div className="rb:font-medium rb:text-[12px] rb:text-[#5B6167] rb:mb-2">{log.summary}</div>
<div className='rb:mt-2 rb:text-[12px] rb:text-[#5B6167]'>
{typeof log.raw_results === 'string'
? <Markdown content={log.raw_results} />
: <>
{log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string; } , index: number) => (
<div key={index}>{item.statement}</div>
))}
{log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string; }, index: number) => (
<div key={index}>{item.content}</div>
))}
</>
}
</div>
</ContentWrapper>
: log.type === 'input_summary' && log.raw_results
? <ContentWrapper>
<div className="rb:font-medium rb:text-[#212332] rb:mb-2">{log.query}</div>
<div className="rb:font-medium rb:text-[12px] rb:text-[#5B6167] rb:mb-2">{log.summary}</div>
<div className='rb:mt-2 rb:text-[12px] rb:text-[#5B6167]'>
{typeof log.raw_results === 'string'
? <Markdown content={log.raw_results} />
: <>
{log.raw_results.reranked_results?.statements.length > 0 && log.raw_results.reranked_results?.statements.map((item: { statement: string; } , index: number) => (
<div key={index}>{item.statement}</div>
))}
{log.raw_results.reranked_results?.summaries.length > 0 && log.raw_results.reranked_results?.summaries.map((item: { content: string; }, index: number) => (
<div key={index}>{item.content}</div>
))}
</>
}
</div>
</ContentWrapper>
: null
}
</div>
: null
}
</ResultCard>
))}
</Space>}
</Card>
</Flex>
}
</RbCard>
</Col>
</Row>
</>

View File

@@ -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 = {