feat(web): memory manage & memory detail ui upgrade

This commit is contained in:
zhaoying
2026-03-19 14:37:36 +08:00
parent ba65b06582
commit 84c23e7c4e
34 changed files with 1107 additions and 744 deletions

View File

@@ -0,0 +1,14 @@
<?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="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆管理-记忆提取引擎-2" transform="translate(-806, -166)">
<g id="文本" transform="translate(252, 154)">
<g id="下拉" transform="translate(562, 20) scale(1, -1) translate(-562, -20)translate(554, 12)">
<rect id="矩形" opacity="0.236839658" fill-rule="nonzero" x="0" y="0" width="16" height="16"></rect>
<polyline id="路径" stroke="#5B6167" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" points="12 6 8 10 4 6"></polyline>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 920 B

View File

@@ -0,0 +1,20 @@
<?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="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆管理-记忆提取引擎-2" transform="translate(-1288, -508)" fill-rule="nonzero">
<g id="编组-26" transform="translate(846, 102)">
<g id="编组-48" transform="translate(12, 362)">
<g id="1" transform="translate(0, 32)">
<g id="编组-31" transform="translate(422, 8)">
<g id="完成" transform="translate(8, 4)">
<rect id="矩形" opacity="0.205054874" x="0" y="0" width="16" height="16"></rect>
<path d="M8,1 C4.13401843,1 1,4.13399462 1,8 C1,11.8660054 4.13401843,15 8,15 C11.8660404,15 15,11.8660054 15,8 C15,4.13400935 11.8660697,1 8,1 L8,1 Z M11.8488905,6.3221368 L7.34274977,10.9336514 C7.13545231,11.145815 6.79915605,11.145815 6.59188798,10.9336514 L4.15112422,8.43567652 C3.9436945,8.22349821 3.9436945,7.87942608 4.15112422,7.66718889 C4.35840699,7.45493698 4.69471795,7.45493698 4.90198602,7.66718889 L6.96733357,9.78080221 L11.0978817,5.55359028 C11.3053114,5.34135309 11.6416371,5.34135309 11.8489052,5.55359028 C12.0563349,5.76581275 12.0563349,6.10989961 11.8489052,6.3221368 L11.8488905,6.3221368 Z" id="形状" fill="#369F21"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>保存</title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g id="记忆管理-记忆提取引擎" transform="translate(-1269, -122)" stroke="#171719">
<g id="编组-26" transform="translate(846, 102)">
<g id="副操作" transform="translate(414, 11)">
<g id="保存" transform="translate(9, 9)">
<g id="编组-12" transform="translate(1.925, 1.75)">
<path d="M1.5,0 L7.33055996,0 L7.33055996,0 L10.15,2.91453026 L10.15,9 C10.15,9.82842712 9.47842712,10.5 8.65,10.5 L1.5,10.5 C0.671572875,10.5 0,9.82842712 0,9 L0,1.5 C0,0.671572875 0.671572875,0 1.5,0 Z" id="矩形"></path>
<polyline id="路径-12" stroke-linecap="round" points="2.45 0 2.45 3.5 6.825 3.5"></polyline>
<rect id="矩形" stroke-linecap="round" x="2.45" y="6.125" width="5.25" height="4.375"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -0,0 +1,18 @@
<?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="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆管理-记忆提取引擎" transform="translate(-1303, -376)" fill-rule="nonzero">
<g id="编组-26" transform="translate(846, 102)">
<g id="1" transform="translate(12, 262)">
<g id="编组-31" transform="translate(437, 8)">
<g id="时间戳" transform="translate(8, 4)">
<rect id="矩形" fill="#000000" opacity="0" x="0" y="0" width="16" height="16"></rect>
<path d="M8.33332812,1.5 C11.9233281,1.5 14.8333281,4.41 14.8333281,8 C14.8333281,11.59 11.9233281,14.5 8.33332812,14.5 C4.74332813,14.5 1.83332812,11.59 1.83332812,8 C1.83332812,4.41 4.74332813,1.5 8.33332812,1.5 Z M8.33332812,2.5 C5.29567188,2.5 2.83332812,4.96234375 2.83332812,8 C2.83332812,11.0376562 5.29567188,13.5 8.33332812,13.5 C11.3709844,13.5 13.8333281,11.0376562 13.8333281,8 C13.8333281,4.96234375 11.371,2.5 8.33332812,2.5 Z M8.83332812,4.322 L8.83332812,7.775 L11.3833281,10.0336719 L10.7206719,10.782 L7.83332812,8.22532813 L7.83332812,4.322 L8.83332812,4.322 Z" id="形状" fill="#FF5D34"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>配置管理</title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆管理-记忆提取引擎" transform="translate(-1344, -122)" fill="#FFFFFF" fill-rule="nonzero" stroke="#FFFFFF" stroke-width="0.3">
<g id="编组-26" transform="translate(846, 102)">
<g id="副操作备份" transform="translate(490, 11)">
<g id="配置管理" transform="translate(8, 9)">
<path d="M7.42879146,7.0538149 L7.42879146,1.42856378 C7.42879146,1.19188803 7.23488102,1 6.99819188,1 C6.76150275,1 6.56757891,1.19188803 6.56757891,1.42856378 L6.56757891,7.0546319 C5.83214429,7.24550203 5.28435198,7.91628644 5.28435198,8.71456315 C5.28435198,9.51404527 5.83108621,10.1856467 6.57135586,10.3753516 C6.56898461,10.3931451 6.56772319,10.4110689 6.56757891,10.4290192 L6.56757891,12.5718916 C6.56757891,12.8085807 6.76150275,13.0004822 6.99817849,13.0004822 C7.23485423,13.0004822 7.42879146,12.8085807 7.42879146,12.5718916 L7.42879146,10.4290325 C7.42845218,10.411363 7.42702981,10.393731 7.42453235,10.3762355 C8.16639582,10.1875486 8.71563466,9.51502299 8.71563466,8.7146703 C8.71578195,7.91518818 8.16904773,7.24362695 7.42879146,7.0538283 L7.42879146,7.0538149 Z M7.60587973,9.32065717 C7.31671051,9.609836 6.8635355,9.65447605 6.52350043,9.42727705 C6.18346536,9.20007806 6.05127565,8.76431872 6.20777383,8.38649396 C6.36427201,8.0086692 6.76587074,7.79400949 7.1669665,7.87379309 C7.56806226,7.95357669 7.85694345,8.30558254 7.85694345,8.71453636 C7.85758337,8.9420223 7.76717752,9.16030816 7.60587973,9.32072414 L7.60587973,9.32065717 Z M3.13814456,2.76797625 C3.14055877,2.75020331 3.14186492,2.73229739 3.14205545,2.71436225 L3.14205545,1.42859056 C3.14205545,1.19210844 2.95034881,1.0004018 2.71386669,1.0004018 C2.47738457,1.0004018 2.28567793,1.19210844 2.28567793,1.42859056 L2.28567793,2.71436225 C2.28534183,2.73252066 2.28614702,2.75068225 2.28808875,2.76873968 C1.54783249,2.95840439 1,3.62996562 1,4.4295415 C1,5.22911737 1.54792625,5.90038395 2.28818251,6.09028975 C2.28629436,6.10780474 2.28547584,6.12541856 2.28573151,6.14303317 L2.28573151,12.5719184 C2.28573151,12.8084005 2.47743814,13.0001071 2.71392026,13.0001071 C2.95040239,13.0001071 3.14210902,12.8084005 3.14210902,12.5719184 L3.14210902,6.14305996 C3.14191505,6.12566525 3.14067166,6.1082981 3.13838564,6.09105317 C3.88019554,5.90235278 4.42884504,5.22982722 4.42884504,4.4295281 C4.42884504,3.62922898 3.88004822,2.9565561 3.13814456,2.76796286 L3.13814456,2.76797625 Z M3.31996071,5.03568909 C3.03078814,5.32485849 2.57761807,5.36949099 2.23758857,5.14229141 C1.89755906,4.91509182 1.76537191,4.47933761 1.92186671,4.10151611 C2.07836152,3.7236946 2.47995285,3.50903305 2.88104582,3.58880862 C3.28213879,3.6685842 3.57103772,4.02057858 3.57103772,4.4295281 C3.571667,4.65700012 3.48126198,4.87526953 3.31997411,5.0356757 L3.31996071,5.03568909 Z M13,4.4295415 C13,3.62894772 12.4502255,2.95656949 11.7081745,2.76797625 C11.710058,2.75017021 11.7108363,2.73226456 11.7105049,2.71436225 L11.7105049,1.42859056 C11.7105049,1.19210844 11.5187983,1.0004018 11.2823162,1.0004018 C11.0458341,1.0004018 10.8541274,1.19210844 10.8541274,1.42859056 L10.8541274,2.71436225 C10.8543217,2.73255453 10.8556637,2.75071642 10.8581455,2.76873968 C10.1178758,2.95840439 9.57080673,3.62996562 9.57080673,4.4295415 C9.57080673,5.22911737 10.1175276,5.90062503 10.8577972,6.09032993 C10.8554298,6.1080924 10.8541684,6.12598481 10.8540203,6.14390375 L10.8540203,12.5719184 C10.8540203,12.8084005 11.0457269,13.0001071 11.282209,13.0001071 C11.5186911,13.0001071 11.7103978,12.8084005 11.7103978,12.5719184 L11.7103978,6.14389035 C11.7107305,6.12626252 11.709988,6.1086308 11.7081745,6.09109335 C12.450279,5.90245993 13,5.2300817 13,4.4295281 L13,4.4295415 Z M11.8917452,5.03563552 C11.6025705,5.32476645 11.1494259,5.36936451 10.8094336,5.14215559 C10.4694413,4.91494666 10.3372926,4.47921076 10.4938036,4.10142382 C10.6503147,3.72363689 11.0518954,3.50901645 11.4529577,3.58881444 C11.85402,3.66861244 12.1428487,4.02060432 12.1428487,4.4295281 C12.1434533,4.65698737 12.0530346,4.87523566 11.8917452,5.03562213 L11.8917452,5.03563552 Z" id="形状"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,13 +1,15 @@
<?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="18px" height="18px" viewBox="0 0 18 18" 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(-272, -458)" stroke="#212332" stroke-width="1.5">
<g id="个人信息" transform="translate(256, 80)">
<g id="关于我" transform="translate(0, 361)">
<g id="" transform="translate(16, 17)">
<circle id="椭圆形" cx="10" cy="6" r="3"></circle>
<path d="M10,17 C13.8659932,17 17,16.078564 17,14.5 C17,12.921436 13.8659932,12 10,12 C6.13400675,12 3,12.9530326 3,14.5 C3,16.0469674 6.13400675,17 10,17 Z" id="椭圆形"></path>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆库-个人记忆RAG" transform="translate(-268, -153)" stroke="#171719" stroke-width="1.2">
<g id="编组-13" transform="translate(252, 64)">
<g id="编组-21" transform="translate(12, 88)">
<g id="编组-32" transform="translate(4, 0)">
<g id="" transform="translate(0, 1)">
<circle id="椭圆形" cx="9" cy="5.4" r="2.7"></circle>
<path d="M9,15.3 C12.4793939,15.3 15.3,14.4707076 15.3,13.05 C15.3,11.6292924 12.4793939,10.8 9,10.8 C5.52060608,10.8 2.7,11.6577293 2.7,13.05 C2.7,14.4422707 5.52060608,15.3 9,15.3 Z" id="椭圆形"></path>
</g>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title></title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="记忆库-个人记忆RAG" transform="translate(-268, -153)" stroke="#171719" stroke-width="1.2">
<g id="编组-13" transform="translate(252, 64)">
<g id="编组-21" transform="translate(12, 88)">
<g id="编组-32" transform="translate(4, 0)">
<g id="我" transform="translate(0, 1)">
<circle id="椭圆形" cx="9" cy="5.4" r="2.7"></circle>
<path d="M9,15.3 C12.4793939,15.3 15.3,14.4707076 15.3,13.05 C15.3,11.6292924 12.4793939,10.8 9,10.8 C5.52060608,10.8 2.7,11.6577293 2.7,13.05 C2.7,14.4422707 5.52060608,15.3 9,15.3 Z" id="椭圆形"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,12 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="个人记忆" transform="translate(-40, -317)" fill="#171719" fill-rule="nonzero">
<g id="编组" transform="translate(12, 12)">
<g id="编组-11" transform="translate(16, 104)">
<g id="热点洞察" transform="translate(12, 201)">
<path d="M3.9387755,13.4 C5.06122447,13.4 5.87755102,14.3 5.87755102,15.3 C5.87755102,15.7 5.77551019,16.1 5.57142857,16.4 C6.89795919,18.6 9.44897958,20.1 12.2040816,20.1 C12.8163265,20.1 13.4285714,20 14.0408163,19.9 C14.5510204,19.8 15.0612245,20.1 15.2653061,20.6 C15.3673469,21.1 15.0612245,21.6 14.5510204,21.8 C13.8367347,21.9 13.0204082,22 12.2040816,22 C8.63265306,22 5.46938774,20.1 3.73469387,17.2 C2.7142857,17.1 2,16.3 2,15.3 C2,14.3 2.91836735,13.4 3.9387755,13.4 L3.9387755,13.4 Z M19.3469388,5.9 C21.0816327,7.7 22,9.99999999 22,12.4 C22,14.1 21.5918367,15.7 20.7755102,17.1 C20.9795918,17.4 21.0816327,17.7 21.0816327,18.1 C21.0816327,19.2 20.1632653,20 19.1428572,20 C18.122449,20 17.1020408,19.2 17.1020408,18.2 C17.1020408,17.2 17.9183673,16.4 18.9387755,16.3 L19.0408163,16.3 L19.1428572,16 C19.7551021,14.9 20.0612245,13.7 20.0612245,12.5 C20.0612245,10.5 19.244898,8.7 17.9183674,7.30000001 C17.5102041,6.9 17.6122449,6.3 17.9183674,6 C18.3265306,5.50000001 18.9387755,5.6 19.3469388,5.9 L19.3469388,5.9 Z M12.2040816,8.7 C14.3469388,8.7 16.0816327,10.4 16.0816327,12.5 C16.0816327,14.6 14.3469388,16.3 12.2040816,16.3 C10.0612245,16.3 8.32653061,14.6 8.32653061,12.5 C8.32653061,10.4 10.0612245,8.7 12.2040816,8.7 Z M12.2040816,10.6 C11.0816327,10.6 10.2653061,11.5 10.2653061,12.5 C10.2653061,13.5 11.1836735,14.4 12.2040816,14.4 C13.2244898,14.4 14.1428571,13.5 14.1428571,12.5 C14.1428571,11.5 13.3265306,10.6 12.2040816,10.6 Z M14.1428571,2 C15.2653061,2 16.0816327,2.9 16.0816327,3.90000001 C16.0816327,4.90000001 15.1632653,5.80000001 14.1428571,5.80000001 C13.4285714,5.80000001 12.8163265,5.40000001 12.5102041,4.90000001 L12.2040816,4.90000001 C8.63265306,4.90000001 5.57142857,7.2 4.65306122,10.5 C4.55102039,11 3.9387755,11.3 3.42857142,11.2 C3.02040815,11 2.7142857,10.5 2.81632652,10 C3.9387755,5.90000002 7.81632654,3 12.2040816,3 L12.5102041,3 C12.8163265,2.40000001 13.4285714,2 14.1428571,2 L14.1428571,2 Z" id="形状"></path>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 26</title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
<g id="记忆库-个人记忆RAG" transform="translate(-268, -357)" stroke="#171719" stroke-width="1.2">
<g id="编组-13" transform="translate(252, 64)">
<g id="编组-31" transform="translate(12, 292)">
<g id="编组-26" transform="translate(4, 1)">
<g id="编组-24" transform="translate(2, 2)">
<g id="编组-23" transform="translate(7, 0)">
<path d="M3.80487741,4.75801529 C4.57304648,4.31981911 5.09090909,3.49311348 5.09090909,2.54545455 C5.09090909,1.13963882 3.95127027,0 2.54545455,0 C1.13963882,0 0,1.13963882 0,2.54545455 L0,11.4545455" id="路径" stroke-linejoin="round"></path>
<path d="M0,11.4545455 C0,12.8603612 1.13963882,14 2.54545455,14 C3.95127027,14 5.09090909,12.8603612 5.09090909,11.4545455" id="路径" stroke-linejoin="round"></path>
<path d="M5.43716946,6.89920585 C6.34272849,6.61654964 7,5.77139545 7,4.77272727 C7,3.65162269 6.17168669,2.72398105 5.09366357,2.56840585" id="路径" stroke-linejoin="round"></path>
<path d="M5.08556316,11.8284839 C6.21217524,11.3387175 7,10.2159074 7,8.90909091 C7,8.05692399 6.66499617,7.2830014 6.11950096,6.7118356" id="路径" stroke-linejoin="round"></path>
<path d="M6.05374225e-07,3.30502525 C0.598221325,3.16656842 1.19644204,3.53131423 1.79466276,4.39926267" id="路径-73"></path>
<path d="M0,6.36955959 C0,7.05675687 0.699825901,9.10572809 3.14599655,8.05286405" id="路径-74"></path>
</g>
<g id="编组-23" transform="translate(3.5, 7) scale(-1, 1) translate(-3.5, -7)">
<path d="M3.80487741,4.75801529 C4.57304648,4.31981911 5.09090909,3.49311348 5.09090909,2.54545455 C5.09090909,1.13963882 3.95127027,0 2.54545455,0 C1.13963882,0 0,1.13963882 0,2.54545455 L0,11.4545455" id="路径" stroke-linejoin="round"></path>
<path d="M0,11.4545455 C0,12.8603612 1.13963882,14 2.54545455,14 C3.95127027,14 5.09090909,12.8603612 5.09090909,11.4545455" id="路径" stroke-linejoin="round"></path>
<path d="M5.43716946,6.89920585 C6.34272849,6.61654964 7,5.77139545 7,4.77272727 C7,3.65162269 6.17168669,2.72398105 5.09366357,2.56840585" id="路径" stroke-linejoin="round"></path>
<path d="M5.08556316,11.8284839 C6.21217524,11.3387175 7,10.2159074 7,8.90909091 C7,8.05692399 6.66499617,7.2830014 6.11950096,6.7118356" id="路径" stroke-linejoin="round"></path>
<path d="M6.05374225e-07,3.30502525 C0.598221325,3.16656842 1.19644204,3.53131423 1.79466276,4.39926267" id="路径-73"></path>
<path d="M0,6.36955959 C0,7.05675687 0.699825901,9.10572809 3.14599655,8.05286405" id="路径-74"></path>
</g>
</g>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,49 @@
/*
* @Author: ZhaoYing
* @Date: 2026-03-19 14:05:09
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 14:05:09
*/
import { type FC } from 'react'
import { Flex } from 'antd';
import clsx from 'clsx'
/** A single tab item with a display label and unique key */
interface Tab {
label: string
key: string
}
/** Props for the BtnTabs component */
interface BtnTabsProps {
/** List of tab items to render */
items: Tab[]
/** Key of the currently active tab */
activeKey: string
/** Callback fired when a tab is clicked */
onChange: (key: string) => void;
/** Optional extra class name for the container */
className?: string;
}
/** Button-style tab switcher — renders tabs as pill-shaped buttons with active highlight */
const BtnTabs: FC<BtnTabsProps> = ({ items, activeKey, onChange, className }) => {
return (
<Flex align="center" gap={8} className={className || ''}>
{items.map((tab) => (
<div
key={tab.key}
onClick={() => onChange(tab.key)}
className={clsx('rb:px-2 rb:py-1 rb:rounded-[13px] rb:text-[12px] rb:leading-4.5 rb:cursor-pointer', {
'rb:bg-[#F6F6F6]': activeKey !== tab.key,
'rb:bg-[#171719] rb:text-white': activeKey === tab.key,
})}
>
{tab.label}
</div>
))}
</Flex>
)
}
export default BtnTabs

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-03-07 16:49:59
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-07 17:14:57
* @Last Modified time: 2026-03-18 10:12:23
*/
import { useEffect, useState, type FC } from 'react';
import { Select, Flex, Space } from 'antd';
@@ -19,11 +19,13 @@ interface ModelSelectProps extends SelectProps {
/** Extra query params passed to getModelList */
params?: Query;
placeholder?: string;
fontClassName?: string;
}
const ModelSelect: FC<ModelSelectProps> = ({
params,
placeholder,
fontClassName,
...props
}) => {
const { t } = useTranslation();
@@ -48,7 +50,7 @@ const ModelSelect: FC<ModelSelectProps> = ({
return (
<Flex align="center" gap={8}>
{logo && <img src={logo} className="rb:size-5 rb:rounded-md" alt="" />}
<div className="rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{item.name}</div>
<div className={`rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap ${fontClassName}`}>{item.name}</div>
</Flex>
);
};

View File

@@ -1548,7 +1548,7 @@ export const en = {
Meaning: 'Meaning',
exampleMemoryExtractionResults: 'Example Memory Extraction Results',
exampleMemoryExtractionResultsSubTitle: '(from a technology conference)',
exampleMemoryExtractionResultsSubTitle: 'from a technology conference',
extractTheNumberOfEntities: 'Extract the number of entities',
extractTheNumberOfEntitiesDesc: 'Merge after deduplication: {{num}} (exact: {{exact}}, fuzzy: {{fuzzy}}, LLM: {{llm}})',
@@ -1670,7 +1670,12 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
disagreementCase: 'Disagreement Case',
Pruned: 'Pruned',
pruning: 'Pruning',
pruning_desc: 'Text pruning {{count}} fragments'
pruning_desc: 'Text pruning {{count}} fragments',
processData: 'Process Data',
finalResult: 'Final Result',
chunking: 'Chunking',
dataStatistics: 'Data Statistics',
},
memoryConversation: {
searchPlaceholder: 'Enter user ID...',

View File

@@ -1546,7 +1546,7 @@ export const zh = {
Meaning: '含义',
exampleMemoryExtractionResults: '示例记忆提取结果',
exampleMemoryExtractionResultsSubTitle: '来自技术会议',
exampleMemoryExtractionResultsSubTitle: '来自技术会议',
extractTheNumberOfEntities: '提取实体数量',
extractTheNumberOfEntitiesDesc: '去重后合并:{{num}}(精确:{{exact}},模糊:{{fuzzy}}LLM{{llm}}',
@@ -1666,7 +1666,12 @@ export const zh = {
disagreementCase: '不一致案例',
Pruned: '已剪枝',
pruning: '剪枝',
pruning_desc: '文本剪枝{{count}}个片段'
pruning_desc: '文本剪枝{{count}}个片段',
processData: '处理数据',
finalResult: '最终结果',
chunking: '分块',
dataStatistics: '数据统计',
},
memoryConversation: {
chatEmpty:'有什么我可以帮您的吗?',

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:00:20
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-04 10:03:35
* @Last Modified time: 2026-03-16 15:43:42
*/
/**
* Line Chart Component
@@ -84,7 +84,7 @@ const SeriesConfig = {
/**
* Chart color palette
*/
const Colors = ['#155EEF', '#4DA8FF', '#FFB048']
const Colors = ['#155EEF', '#4DA8FF', '#369F21']
/**
@@ -228,8 +228,8 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
grid: {
left: 4,
right: '2%',
bottom: 60,
top: 32,
bottom: 48,
top: 8,
containLabel: true
},
xAxis: {
@@ -243,7 +243,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
show: true,
},
axisTick: {
show: true
show: false
},
axisLabel: {
color: '#5B6167'
@@ -268,7 +268,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
...initialData || []
]
}}
style={{ height: '450px', width: '100%' }}
style={{ height: '400px', width: '100%' }}
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:00:12
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:00:12
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 15:41:54
*/
/**
* Forgetting Engine Configuration Page
@@ -11,16 +11,17 @@
*/
import React, { useState, useEffect } from 'react';
import { Row, Col, Form, Slider, Button, Space, message } from 'antd';
import { Row, Col, Form, Button, Space, message, Flex, Tooltip } from 'antd';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import RbCard from '@/components/RbCard/Card';
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
import LineChart from './components/LineChart'
import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory'
import type { ConfigForm } from './types'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import RbSlider from '@/components/RbSlider';
import DescWrapper from '@/components/FormItem/DescWrapper'
/**
* Configuration field definitions
@@ -154,16 +155,18 @@ const ForgettingEngine: React.FC = () => {
}
return (
<Row gutter={[16, 16]}>
<Col span={9}>
<RbCard
title={
<div className="rb:flex rb:items-center">
<img src={strategyImpactSimulator} className="rb:w-5 rb:h-5 rb:mr-2" />
{t('forgettingEngine.forgettingEngineConfigParams')}
</div>
}
className='rb:h-full!'
<Row gutter={12}>
<Col span={12}>
<RbCard
title={t('forgettingEngine.forgettingEngineConfigParams')}
extra={<Space>
<Button block onClick={handleReset}>{t('common.reset')}</Button>
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
</Space>}
headerType="borderless"
headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold"
className="rb:h-[calc(100vh-76px)]!"
bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-3! rb:pt-0!"
>
<Form
form={form}
@@ -174,7 +177,7 @@ const ForgettingEngine: React.FC = () => {
lambda_mem: 0.03,
}}
>
<Space size={24} direction="vertical" style={{ width: '100%' }}>
<Flex vertical gap={12}>
{configList.map(config => {
if (config.type === 'button') {
return (
@@ -182,51 +185,53 @@ const ForgettingEngine: React.FC = () => {
title={t(`forgettingEngine.${config.key}`)}
name={config.name}
desc={config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
className="rb:mb-2"
className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3!"
/>
)
}
return (
<div key={config.key}>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
<div key={config.key} className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3">
<Flex align="center" gap={4} className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
{t(`forgettingEngine.${config.key}`)}
</div>
{!config.hiddenDesc && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ">
{t(`forgettingEngine.${config.key}Desc`)}
</div>}
{!config.hiddenDesc && <Tooltip title={t(`forgettingEngine.${config.key}Desc`)}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
</Tooltip>}
</Flex>
<Form.Item
name={config.name}
extra={<DescWrapper
desc={<>
<span className="rb:text-[12px]">{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span> | <span>{t(`forgettingEngine.type`)}: {config.type}</span>
</>}
/>}
className="rb:mb-0!"
>
{config.type === 'decimal'
? <Slider tooltip={{ open: false }} max={config.range?.[1] || 1} min={config.range?.[0] || 0} step={config.step ?? 0.01} style={{ margin: '0' }} />
? <RbSlider
max={config.range?.[1] || 1}
min={config.range?.[0] || 0}
step={config.step ?? 0.01}
isInput={true}
prefix={<span className="rb:text-[#5B6167]">{t('emotionEngine.currentValue')}:</span>}
inputClassName="rb:w-[155px]!"
/>
: null
}
</Form.Item>
<div className="rb:flex rb:text-[12px] rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-5 rb:-mt-6.5">
<Space size={4}>
{config.range && <span>{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span>}
{config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
</Space>
<>{t('forgettingEngine.CurrentValue')}: {values?.[config.name] || 0}</>
</div>
</div>
)
})}
<Row gutter={16}>
<Col span={12}>
<Button block onClick={handleReset}>{t('common.reset')}</Button>
</Col>
<Col span={12}>
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
</Col>
</Row>
</Space>
</Flex>
</Form>
</RbCard>
</Col>
<Col span={15}>
<Col span={12}>
<RbCard
title={t('forgettingEngine.forgettingCurve')}
headerType="borderless"
headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold"
bodyClassName="rb:p-3! rb:pt-0!"
>
<LineChart
config={values}

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:30:51
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:30:51
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 14:06:38
*/
/**
* Card Component
@@ -10,11 +10,10 @@
*/
import { type FC, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import clsx from 'clsx';
import { Flex, Space, Tooltip } from 'antd';
import RbCard from '@/components/RbCard/Card'
import down from '@/assets/images/userMemory/down.svg'
/**
* Component props
@@ -29,6 +28,7 @@ interface CardProps {
className?: string;
headerClassName?: string;
bodyClassName?: string;
extra?: ReactNode;
}
const Card: FC<CardProps> = ({
@@ -41,27 +41,33 @@ const Card: FC<CardProps> = ({
className,
headerClassName,
bodyClassName,
extra,
}) => {
const { t } = useTranslation()
return (
<RbCard
title={title}
subTitle={subTitle}
headerType="borderless"
extra={type && handleExpand && (
<div
className="rb:flex rb:items-center rb:text-[14px] rb:text-[#5B6167] rb:cursor-pointer rb:font-regular rb:leading-5"
onClick={() => handleExpand(type)}
>
{expanded ? t('common.foldUp') : t('common.expanded')}
<img src={down} className={clsx("rb:w-4 rb:h-4 rb:ml-1", {
title={() => <Flex
align="center"
justify="space-between"
className="rb:font-[MiSans-Bold] rb:font-bold rb:cursor-pointer"
onClick={type && handleExpand ? () => handleExpand(type) : undefined}
>
<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>
{handleExpand && <div
className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')]", {
'rb:rotate-180': !expanded,
})} />
</div>
)}
})}
></div>}
</Flex>}
headerType="borderless"
className={className}
headerClassName={headerClassName}
bodyClassName={bodyClassName}
headerClassName={`rb:h-[50px]! rb:pb-[12px]! rb:pt-[16px]! rb:leading-[22px]! rb:font-[MiSans-Bold] rb:font-bold rb:text-[16px] ${headerClassName}`}
bodyClassName={`rb:px-3! rb:py-0! ${expanded ? 'rb:pb-3!' : 'rb:pb-0!'} ${bodyClassName}`}
extra={extra}
>
{(expanded || !(type && handleExpand)) && children}
</RbCard>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:30:11
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-02 11:41:12
* @Last Modified time: 2026-03-19 14:22:20
*/
/**
* Result Component
@@ -13,13 +13,13 @@
import { type FC, useState } from 'react'
import { useParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { Space, Button, Progress, Form, Input } from 'antd'
import { ExclamationCircleFilled, CheckCircleFilled, ClockCircleOutlined, LoadingOutlined } from '@ant-design/icons'
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'
import RbCard from '@/components/RbCard/Card'
import RbAlert from '@/components/RbAlert'
import type { TestResult, OntologyCoverage } from '../types'
import { pilotRunMemoryExtractionConfig } from '@/api/memory'
@@ -27,6 +27,8 @@ import { type SSEMessage } from '@/utils/stream'
import Tag, { type TagProps } from '@/components/Tag'
import Markdown from '@/components/Markdown'
import { groupDataByType } from '../constant'
import Empty from '@/components/Empty'
import NoDataIcon from '@/assets/images/empty/noData.png'
/** Result metric mapping */
const resultObj = {
@@ -56,7 +58,7 @@ interface ModuleItem {
const tagColors: {
[key: string]: TagProps['color']
} = {
pending: 'default',
pending: 'warning',
processing: 'processing',
completed: 'success',
failed: 'error'
@@ -67,29 +69,55 @@ const initObj = {
status: 'pending',
result: null
}
const initialExpanded = {
text_preprocessing: false,
knowledge_extraction: false,
creating_nodes_edges: false,
deduplication: false,
dataStatistics: false,
entityDeduplicationImpact: false,
disambiguation: false,
coreEntities: false,
triplet_samples: false,
ontologyCoverage: false,
}
const Result: FC<ResultProps> = ({ loading, handleSave }) => {
const { t } = useTranslation();
const { id } = useParams()
const [runLoading, setRunLoading] = useState(false)
const [activeTab, setActiveTab] = useState('processData')
const [testResult, setTestResult] = useState<TestResult>({} as TestResult)
const [coreEntitiesTab, setCoreEntitiesTab] = useState<string | null>(null)
const [textPreprocessing, setTextPreprocessing] = useState<ModuleItem>(initObj as ModuleItem)
const [textPreprocessingTab, setTextPreprocessingTab] = useState('chunking')
const [knowledgeExtraction, setKnowledgeExtraction] = useState<ModuleItem>(initObj as ModuleItem)
const [creatingNodesEdges, setCreatingNodesEdges] = useState<ModuleItem>(initObj as ModuleItem)
const [deduplication, setDeduplication] = useState<ModuleItem>(initObj as ModuleItem)
const [ontologyCoverage, setOntologyCoverage] = useState<OntologyCoverage>({} as OntologyCoverage)
const [expandedCards, setExpandedCards] = useState<Record<string, boolean>>(initialExpanded)
const toggleCard = (key: string) => {
console.log('toggleCard', key)
setExpandedCards(prev => ({ ...prev, [key]: !prev[key] }))
}
console.log('expandedCards', expandedCards)
const [runForm] = Form.useForm()
const customText = Form.useWatch(['custom_text'], runForm)
/** Run pilot test */
const handleRun = () => {
if(!id) return
setActiveTab('processData')
setCoreEntitiesTab(null)
setTextPreprocessing({...initObj} as ModuleItem)
setTextPreprocessingTab('chunking')
setKnowledgeExtraction({...initObj} as ModuleItem)
setCreatingNodesEdges({...initObj} as ModuleItem)
setDeduplication({...initObj} as ModuleItem)
setTestResult({} as TestResult)
setExpandedCards(initialExpanded)
const handleStreamMessage = (list: SSEMessage[]) => {
list.forEach((data: AnyObject) => {
@@ -100,6 +128,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
status: 'processing',
start_at: data.data.time
}))
toggleCard('text_preprocessing')
break
case 'text_preprocessing_result': // Text preprocessing in progress
setTextPreprocessing(prev => ({
@@ -121,6 +150,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
status: 'processing',
start_at: data.data.time
}))
toggleCard('knowledge_extraction')
break
case 'knowledge_extraction_result': // Knowledge extraction in progress
setKnowledgeExtraction(prev => ({
@@ -142,6 +172,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
status: 'processing',
start_at: data.data.time
}))
toggleCard('creating_nodes_edges')
break
case 'creating_nodes_edges_result': // Creating nodes and edges in progress
setCreatingNodesEdges(prev => ({
@@ -163,6 +194,7 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
status: 'processing',
start_at: data.data.time
}))
toggleCard('deduplication')
break
case 'dedup_disambiguation_result': // Deduplication and disambiguation in progress
setDeduplication(prev => ({
@@ -183,6 +215,15 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
case 'result': // Result
setTestResult(data.data?.extracted_result)
setOntologyCoverage(data.data?.ontology_coverage)
setExpandedCards(prev => ({
...prev,
dataStatistics: true,
entityDeduplicationImpact: true,
disambiguation: true,
coreEntities: true,
triplet_samples: true,
ontologyCoverage: true,
}))
break
}
})
@@ -203,9 +244,10 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
/** Format status tag */
const formatTag = (status: string) => {
return (
<Tag color={tagColors[status]}>
{status === 'pending' && <ClockCircleOutlined className="rb:mr-1" />}
<Tag color={tagColors[status]} className="rb:flex! rb:items-center rb:gap-1 rb:bg-white! rb:border-white!">
{status === 'pending' && <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/memory/clock_orange.svg')]"></div>}
{status === 'processing' && <LoadingOutlined spin className="rb:mr-1" />}
{status === 'completed' && <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/check_green.svg')]"></div>}
{t(`memoryExtractionEngine.status.${status}`)}
</Tag>
)
@@ -213,294 +255,411 @@ const Result: FC<ResultProps> = ({ loading, handleSave }) => {
/** Format processing time */
const formatTime = (data: ModuleItem, color?: string) => {
if (typeof data.end_at === 'number' && typeof data.start_at === 'number') {
return <div className={`rb:mt-3 rb:text-[${color ?? '#155EEF'}]`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
return <div className={`rb:text-[${color ?? '#155EEF'}] rb:mb-0.5`}>{t('memoryExtractionEngine.time')}{data.end_at - data.start_at}ms</div>
}
return null
}
/** Convert first character to lowercase */
const lowercaseFirst = (str: string) => str.charAt(0).toLowerCase() + str.slice(1)
return (
<Card
title={t('memoryExtractionEngine.exampleMemoryExtractionResults')}
subTitle={t('memoryExtractionEngine.exampleMemoryExtractionResultsSubTitle')}
className="rb:min-h-[calc(100vh-330px)]!"
headerClassName="rb:pb-0! rb:pt-4!"
bodyClassName="rb:min-h-[calc(100vh-388px)] rb:p-[16px_20px]!"
bodyClassName="rb:h-[calc(100vh-163px)]! rb:overflow-y-auto rb:p-[16px_20px]!"
extra={<Space size={8}>
<Button
icon={<div className="rb:size-3.5 rb:bg-cover rb:bg-[url('@/assets/images/common/save.svg')]"></div>}
loading={loading}
onClick={handleSave}
>{t('common.save')}</Button>
<Button
type="primary"
icon={<div className="rb:size-3.5 rb:bg-cover rb:bg-[url('@/assets/images/memory/debug.svg')]"></div>}
loading={runLoading}
onClick={handleRun}
>{t('memoryExtractionEngine.debug')}</Button>
</Space>}
>
<Form form={runForm} layout="vertical">
{/* <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3!">
{t('memoryExtractionEngine.warning')}
</RbAlert> */}
<Form form={runForm} layout="vertical" className="rb:bg-[#F6F6F6]! rb:rounded-xl rb:py-2! rb:mb-4!">
<Flex align="center" justify="space-between" className="rb:px-3! rb:mb-2!">
<div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('memoryExtractionEngine.custom_text')}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4.5">{customText?.length || 0}</div>
</Flex>
<Form.Item
name="custom_text"
label={t('memoryExtractionEngine.custom_text')}
noStyle
>
<Input.TextArea placeholder={t('common.pleaseEnter')} />
<Input.TextArea placeholder={t('common.pleaseEnter')} variant="borderless" />
</Form.Item>
</Form>
<div className="rb:min-h-[calc(100vh-480px)] rb:overflow-y-auto">
{runLoading
? <>
<RbAlert color="blue" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{runLoading
? <>
<RbAlert color="blue">
<div className="rb:w-full">
{t('memoryExtractionEngine.processing')}
</RbAlert>
{/* Overall Progress */}
<div className="rb:mb-2">
<div className="rb:flex rb:items-center rb:justify-between rb:text-[12px] rb:leading-4 rb:font-regular">
{t('memoryExtractionEngine.overallProgress')}
<span className="rb:text-[#155eef]">{`${completedNum}/4`}</span>
</div>
<Progress percent={completedNum * 100/4} showInfo={false} />
{/* Overall Progress */}
<Flex gap={13} align="center">
<Progress percent={completedNum * 100 / 4} showInfo={false} className="rb:flex-1!" />
<div className="rb:text-[12px] rb:leading-4 rb:font-regular">
{t('memoryExtractionEngine.overallProgress')}{`${(completedNum*100/4).toFixed(0)}%`}
</div>
</Flex>
</div>
</>
: !testResult || Object.keys(testResult).length === 0
? <RbAlert color="orange" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{t('memoryExtractionEngine.warning')}
</RbAlert>
: <RbAlert color="green" icon={<ExclamationCircleFilled />} className="rb:mb-3.5">
{t('memoryExtractionEngine.success')}
</RbAlert>
}
<Space size={16} direction="vertical" style={{ width: '100%' }}>
{/* Text Preprocessing */}
<RbCard
title={t(`memoryExtractionEngine.text_preprocessing`)}
extra={formatTag(textPreprocessing.status)}
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
{textPreprocessing.data.map((vo, index) => {
if (vo.deleted_messages) {
return <div key={index} className="rb:mb-3 rb:pb-1 rb:border-b rb:border-b-[#EBEBEB]">
<div className="rb:font-medium rb:text-[12px] rb:mb-2">{t('memoryExtractionEngine.Pruned')}</div>
{vo.deleted_messages.map((msg: any, idx: number) => (
<div key={idx} className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
<Markdown content={'-' + t('memoryExtractionEngine.pruning') + (idx + 1) + ': ' + msg.content} />
</div>
))}
</div>
}
return (
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
<Markdown content={'-' + t('memoryExtractionEngine.fragment') + vo.chunk_index + ': ' + (vo.content.startsWith('\n') ? vo.content : '\n' + vo.content)} />
</div>
)
</RbAlert>
</>
: !testResult || Object.keys(testResult).length === 0
? <RbAlert color="orange" icon={<ExclamationCircleFilled />}>
{t('memoryExtractionEngine.warning')}
</RbAlert>
: <RbAlert color="green" icon={<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/check_green.svg')]"></div>}>
{t('memoryExtractionEngine.success')}
</RbAlert>
}
<Space size={24} className="rb:mt-4! rb:mb-3!">
{['processData', 'finalResult'].map(tab => (
<div
className={clsx('rb:font-[MiSans-Bold] rb:font-bold rb:leading-5 rb:cursor-pointer', {
'rb:text-[#212332]': activeTab === tab,
'rb:text-[#A8A9AA]': activeTab !== tab,
})}
{formatTime(textPreprocessing)}
{textPreprocessing.result &&
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
onClick={() => setActiveTab(tab)}
>{t(`memoryExtractionEngine.${tab}`)}</div>
))}
</Space>
{activeTab === 'processData' && <Flex vertical gap={12} className="rb:pb-3!">
{/* Text Preprocessing */}
<ResultCard
title={t(`memoryExtractionEngine.text_preprocessing`)}
extra={formatTag(textPreprocessing.status)}
expanded={expandedCards['text_preprocessing']}
handleExpand={() => toggleCard('text_preprocessing')}
>
{expandedCards['text_preprocessing'] && textPreprocessing.data?.length > 0 &&
<Space size={10} className="rb:px-1! rb:mb-3!">
{(['chunking', ...(textPreprocessing.data.some(vo => vo.deleted_messages) ? ['pruning'] : [])] as string[]).map(type => (
<div
key={type}
className={clsx("rb:rounded-[13px] rb:py-0.5 rb:px-3 rb:leading-5 rb:cursor-pointer", {
'rb:bg-white': textPreprocessingTab !== type,
'rb:bg-[#171719] rb:text-white': textPreprocessingTab === type
})}
onClick={() => setTextPreprocessingTab(type)}
>
{t(`memoryExtractionEngine.${type}`)}
</div>
))}
</Space>
}
{expandedCards['text_preprocessing'] && textPreprocessing.result &&
<RbAlert color="blue" className="rb:mb-2!">
<div>
<div>{formatTime(textPreprocessing)}</div>
{t('memoryExtractionEngine.pruning_desc', { count: textPreprocessing.result.pruning.deleted_count || 0 })},
{t('memoryExtractionEngine.text_preprocessing_desc', { count: textPreprocessing.result.total_chunks })},
{t('memoryExtractionEngine.chunkerStrategy')}: {t(`memoryExtractionEngine.${lowercaseFirst(textPreprocessing.result.chunker_strategy)}`)}
</RbAlert>
}
</RbCard>
{/* Knowledge Extraction */}
<RbCard
title={t(`memoryExtractionEngine.knowledge_extraction`)}
extra={formatTag(knowledgeExtraction.status)}
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
{knowledgeExtraction.data.map((vo, index) =>
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{vo.statement}</div>
)}
{formatTime(knowledgeExtraction)}
{knowledgeExtraction.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{t('memoryExtractionEngine.knowledge_extraction_desc', {
entities: knowledgeExtraction.result.entities_count,
statements: knowledgeExtraction.result.statements_count,
temporal_ranges_count: knowledgeExtraction.result.temporal_ranges_count,
triplets: knowledgeExtraction.result.triplets_count
})}
</RbAlert>}
</RbCard>
{/* Creating Entity Relationships */}
<RbCard
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
extra={formatTag(creatingNodesEdges.status)}
headerType="borderL"
headerClassName="rb:before:bg-[#9C6FFF]!"
>
{creatingNodesEdges.data?.map((vo, index) => (
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
{vo?.result_type === 'entity_nodes_creation'
? <>{vo.type_display_name}: {vo.entity_names.join(', ')}</>
: <>{vo?.relationship_text}</>
}
</div>
))}
{formatTime(creatingNodesEdges, '#9C6FFF')}
{creatingNodesEdges.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{t('memoryExtractionEngine.creating_nodes_edges_desc', {num: creatingNodesEdges.result.entity_entity_edges_count})}
</RbAlert>}
</RbCard>
{/* Deduplication and Disambiguation */}
<RbCard
title={t(`memoryExtractionEngine.deduplication`)}
extra={formatTag(deduplication.status)}
headerType="borderL"
headerClassName="rb:before:bg-[#9C6FFF]!"
>
{Object.keys(deduplicationData).length > 0 && Object.keys(deduplicationData).map(key => {
return deduplicationData[key].map((vo, index) => (
<div key={index} className="rb:mb-3 rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">
{vo.message}
</div>
))
})}
{formatTime(deduplication, '#9C6FFF')}
{deduplication.result && <RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}<br />
</RbAlert>}
</RbCard>
{testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 &&
<RbCard>
<div className="rb:grid rb:grid-cols-2 rb:gap-[40px_57px]">
{Object.keys(resultObj).map((key, index) => {
const keys = (resultObj as Record<string, string>)[key].split('.')
return (
<div key={index}>
<div className="rb:text-[24px] rb:leading-7.5 rb:font-extrabold">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:font-regular">{t(`memoryExtractionEngine.${key}`)}</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#369F21] rb:leading-3.5 rb:font-regular">
{key === 'extractTheNumberOfEntities' && testResult.dedup
? t(`memoryExtractionEngine.${key}Desc`, {
num: testResult.dedup.total_merged_count,
exact: testResult.dedup.breakdown.exact,
fuzzy: testResult.dedup.breakdown.fuzzy,
llm: testResult.dedup.breakdown.llm,
})
: key === 'numberOfEntityDisambiguation' && testResult.disambiguation
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count })
: key === 'numberOfRelationalTriples' && testResult.triplets
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count })
:t(`memoryExtractionEngine.${key}Desc`)
}
</div>
</div>
)})}
</div>
</RbCard>
</RbAlert>
}
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.entityDeduplicationImpact')}
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-medium rb:leading-4">{t('memoryExtractionEngine.identifyDuplicates')}</div>
{testResult.dedup.impact.map((item, index) => (
<div key={index} className="rb:pl-2 rb:mt-2 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">
-{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
{expandedCards['text_preprocessing'] && textPreprocessing.data.map((vo, index) => {
if (vo.deleted_messages && textPreprocessingTab === 'pruning') {
return <div key={index} className="rb:mb-3 rb:pb-1 rb:border-b rb:border-b-[#EBEBEB]">
<div className="rb:font-medium rb:text-[12px] rb:mb-2">{t('memoryExtractionEngine.Pruned')}</div>
{vo.deleted_messages.map((msg: any, idx: number) => (
<div key={idx} className="rb:leading-5">
<div className="rb:font-medium">-{t('memoryExtractionEngine.pruning')}{idx}:</div>
<Markdown content={msg.content} />
</div>
))}
</div>
}
if (textPreprocessingTab === 'chunking') {
return (
<div key={index} className="rb:leading-5">
<div className="rb:font-medium">-{t('memoryExtractionEngine.fragment')}{vo.chunk_index}:</div>
<Markdown content={vo.content.startsWith('\n') ? vo.content : '\n' + vo.content} className="rb:text-[#212332]" />
</div>
))}
)
}
return null
})}
</ResultCard>
{/* Knowledge Extraction */}
<ResultCard
title={t(`memoryExtractionEngine.knowledge_extraction`)}
extra={formatTag(knowledgeExtraction.status)}
expanded={expandedCards['knowledge_extraction']}
handleExpand={() => toggleCard('knowledge_extraction')}
>
{knowledgeExtraction.result &&
<RbAlert color="blue" className="rb:mb-2!">
<div>
<div>{formatTime(knowledgeExtraction)}</div>
{t('memoryExtractionEngine.knowledge_extraction_desc', {
entities: knowledgeExtraction.result.entities_count,
statements: knowledgeExtraction.result.statements_count,
temporal_ranges_count: knowledgeExtraction.result.temporal_ranges_count,
triplets: knowledgeExtraction.result.triplets_count
})}
</div>
</RbAlert>
}
{knowledgeExtraction.data?.length > 0 &&
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
{knowledgeExtraction.data.map((vo, index) =>
<li key={index} className="rb:leading-6">{vo.statement}</li>
)}
</ul>
}
</ResultCard>
{/* Creating Entity Relationships */}
<ResultCard
title={t(`memoryExtractionEngine.creating_nodes_edges`)}
extra={formatTag(creatingNodesEdges.status)}
expanded={expandedCards['creating_nodes_edges']}
handleExpand={() => toggleCard('creating_nodes_edges')}
>
{creatingNodesEdges.result &&
<RbAlert color="blue" className="rb:mb-2!">
<div>
<div>{formatTime(creatingNodesEdges)}</div>
{t('memoryExtractionEngine.creating_nodes_edges_desc', { num: creatingNodesEdges.result.entity_entity_edges_count })}
</div>
</RbAlert>
}
{creatingNodesEdges.data?.length > 0 &&
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
{creatingNodesEdges.data.map((vo, index) =>
<li key={index} className="rb:leading-6">
{vo?.result_type === 'entity_nodes_creation'
? <>{vo.type_display_name}: {vo.entity_names.join(', ')}</>
: <>{vo?.relationship_text}</>
}
</li>
)}
</ul>
}
</ResultCard>
{/* Deduplication and Disambiguation */}
<ResultCard
title={t(`memoryExtractionEngine.deduplication`)}
extra={formatTag(deduplication.status)}
expanded={expandedCards['deduplication']}
handleExpand={() => toggleCard('deduplication')}
>
{deduplication.result &&
<RbAlert color="blue" className="rb:mb-2!">
<div>
<div>{formatTime(deduplication)}</div>
{t('memoryExtractionEngine.deduplication_desc', { count: deduplication.result.summary.total_merges })}
</div>
</RbAlert>
}
{Object.keys(deduplicationData).length > 0 &&
<ul className="rb:list-disc rb:ml-4 rb:mb-3">
{Object.keys(deduplicationData).map(key => {
return deduplicationData[key].map((vo, index) => (
<li key={index} className="rb:leading-6">
{vo.message}
</li>
))
})}
</ul>
}
</ResultCard>
</Flex>}
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{activeTab === 'finalResult' && <Flex vertical gap={12} className="rb:pb-3!">
{!testResult || Object.keys(testResult).length === 0
? <Empty url={NoDataIcon} />
: null
}
{testResult && Object.keys(testResult).length > 0 && resultObj && Object.keys(resultObj).length > 0 &&
<ResultCard
title={t(`memoryExtractionEngine.dataStatistics`)}
expanded={expandedCards['dataStatistics']}
handleExpand={() => toggleCard('dataStatistics')}
>
<div className="rb:grid rb:grid-cols-2 rb:gap-2.5 rb:mb-3">
{Object.keys(resultObj).map((key, index) => {
const keys = (resultObj as Record<string, string>)[key].split('.')
return (
<div key={index} className="rb:bg-white rb:rounded-lg rb:py-2 rb:px-3">
<div className="rb:text-[24px] rb:leading-8 rb:font-bold rb:font-[MiSans-Bold] rb:mb-1">{(testResult?.[keys[0] as keyof TestResult] as any)?.[keys[1]]}</div>
<div className="rb:text-[12px] rb:leading-4 rb:mb-0.5">{t(`memoryExtractionEngine.${key}`)}</div>
<div className="rb:text-[12px] rb:text-[#369F21] rb:leading-4">
{key === 'extractTheNumberOfEntities' && testResult.dedup
? t(`memoryExtractionEngine.${key}Desc`, {
num: testResult.dedup.total_merged_count,
exact: testResult.dedup.breakdown.exact,
fuzzy: testResult.dedup.breakdown.fuzzy,
llm: testResult.dedup.breakdown.llm,
})
: key === 'numberOfEntityDisambiguation' && testResult.disambiguation
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count })
: key === 'numberOfRelationalTriples' && testResult.triplets
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count })
: t(`memoryExtractionEngine.${key}Desc`)
}
</div>
</div>
)
})}
</div>
</ResultCard>
}
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
<ResultCard
title={t('memoryExtractionEngine.entityDeduplicationImpact')}
expanded={expandedCards['entityDeduplicationImpact']}
handleExpand={() => toggleCard('entityDeduplicationImpact')}
>
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
<RbAlert color="blue" className="rb:mb-2!">
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
</RbAlert>
</RbCard>
}
<div className="rb:font-medium rb:leading-5 rb:mb-2">{t('memoryExtractionEngine.identifyDuplicates')}:</div>
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven')}
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
>
<ul className="rb:list-disc rb:ml-4">
{testResult.dedup.impact.map((item, index) => (
<li key={index} className="rb:leading-6">
{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
</li>
))}
</ul>
</div>
</ResultCard>
}
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
<ResultCard
title={t('memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven')}
expanded={expandedCards['disambiguation']}
handleExpand={() => toggleCard('disambiguation')}
>
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
<RbAlert color="blue" className="rb:mb-2!">
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
</RbAlert>
{testResult.disambiguation.effects.map((item, index) => (
<div key={index} className={clsx("rb:text-[12px] rb:text-[#5B6167] rb:leading-4", {
'rb:mt-4': index > 0,
'rb:mt-5': index > 0,
})}>
<div className="rb:font-medium rb:mb-2">{t('memoryExtractionEngine.disagreementCase')} {index +1}:</div>
-{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) <span className="rb:text-[#369F21]">{item.result}</span>
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('memoryExtractionEngine.disagreementCase')} {index + 1}:</div>
<ul className="rb:list-disc rb:ml-4">
<li key={index} className="rb:leading-6">
{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) {item.result}
</li>
</ul>
</div>
))}
</div>
</ResultCard>
}
<RbAlert color="blue" icon={<CheckCircleFilled />} className="rb:mt-3">
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
</RbAlert>
</RbCard>
}
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
<ResultCard
title={t('memoryExtractionEngine.coreEntitiesAfterDedup')}
expanded={expandedCards['coreEntities']}
handleExpand={() => toggleCard('coreEntities')}
>
<Flex gap={10} wrap className="rb:px-1! rb:mb-3! rb:gap-y-2!">
{testResult.core_entities.map((item, index) => (
<div
key={item.type}
className={clsx("rb:rounded-[13px] rb:py-0.5 rb:px-3 rb:leading-5 rb:cursor-pointer", {
'rb:bg-white': !((coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)),
'rb:bg-[#171719] rb:text-white': (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)
})}
onClick={() => setCoreEntitiesTab(item.type)}
>
{item.type}({item.count})
</div>
))}
</Flex>
<div className="rb:bg-white rb:rounded-lg rb:py-2.5 rb:px-3 rb:mb-3">
{testResult.core_entities.filter((item, index) => (coreEntitiesTab && item.type === coreEntitiesTab) || (!coreEntitiesTab && index === 0)).map((item, idx) => (
<div key={idx} className="rb:leading-5">
<div className="rb:text-[#155EEF] rb:font-medium rb:mb-2">{item.type}({item.count})</div>
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.coreEntitiesAfterDedup')}
headerType="borderL"
headerClassName="rb:before:bg-[#369F21]!"
>
<div className="rb:grid rb:grid-cols-2 rb:gap-6">
{testResult.core_entities.map((item, idx) => (
<div key={idx} className="rb:text-[12px]">
<div className="rb:text-[#369F21] rb:font-medium">{item.type}({item.count})</div>
<ul className="rb:list-disc rb:ml-4">
{item.entities.map((entity, index) => (
<li key={index} className="rb:leading-6">
{entity}
</li>
))}
</ul>
</div>
))}
</div>
</ResultCard>
}
<div>
{item.entities.map((entity, index) => (
<div key={index} className="rb:text-[#5B6167] rb:font-regular rb:leading-4">
-{entity}
</div>
))}
</div>
</div>
))}
</div>
</RbCard>
}
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
<RbCard
title={t('memoryExtractionEngine.extractRelationalTriples')}
headerType="borderL"
headerClassName="rb:before:bg-[#9C6FFF]!"
>
<Space size={8} direction="vertical" className="rb:w-full">
{testResult.triplet_samples.map((item, index) => (
<div key={index} className="rb:text-[12px]">
-({item.subject}, <span className="rb:text-[#9C6FFF] rb:font-medium">{item.predicate}</span>, {item.object})
</div>
))}
</Space>
<RbAlert color="purple" icon={<CheckCircleFilled />} className="rb:mt-3">
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
<ResultCard
title={t('memoryExtractionEngine.extractRelationalTriples')}
expanded={expandedCards['triplet_samples']}
handleExpand={() => toggleCard('triplet_samples')}
>
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3">
<RbAlert color="blue"className="rb:mb-2!">
{t('memoryExtractionEngine.extractRelationalTriplesDesc', { count: testResult.triplet_samples.length })}
</RbAlert>
</RbCard>
}
{ontologyCoverage && Object.keys(ontologyCoverage).length > 0 &&
<RbCard
title={<>{t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})</>}
headerType="borderL"
headerClassName="rb:before:bg-[#369F21]!"
>
<div className="rb:grid rb:grid-cols-2 rb:gap-3">
<ul className="rb:list-disc rb:ml-4">
{testResult.triplet_samples.map((item, index) => (
<li key={index} className="rb:leading-6">
({item.subject}, <span className="rb:text-[#155EEF] rb:font-medium">{item.predicate}</span>, {item.object})
</li>
))}
</ul>
</div>
</ResultCard>
}
{ontologyCoverage && Object.keys(ontologyCoverage).length > 0 &&
<ResultCard
title={<>{t('memoryExtractionEngine.ontologyCoverage')}({ontologyCoverage.total_entities})</>}
expanded={expandedCards['ontologyCoverage']}
handleExpand={() => toggleCard('ontologyCoverage')}
>
<div className="rb:bg-white rb:rounded-xl rb:p-3 rb:mb-3 rb:leading-5">
<div className="rb:grid rb:grid-cols-1 rb:gap-3">
{(['scene_type_distribution', 'general_type_distribution', 'unmatched'] as const).map((key, idx) => {
if (!ontologyCoverage[key]) return null
return (
<div key={idx} className="rb:text-[12px]">
<div className="rb:text-[#369F21] rb:font-medium">{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})</div>
<div>{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}</div>
<div>
<div key={idx}>
<div className="rb:text-[#155EEF] rb:font-medium rb:mb-1">{t(`memoryExtractionEngine.${key}`)}({ontologyCoverage[key].type_count})</div>
<div className="rb:text-[#212332] rb:mb-1">{t('memoryExtractionEngine.entity_total', { num: ontologyCoverage[key].entity_total })}</div>
<ul className="rb:list-disc rb:ml-4">
{ontologyCoverage[key].types.map((type, index) => {
if (!type.type || type.type === '') return null
return (
<div key={index} className="rb:text-[#5B6167] rb:font-regular rb:leading-4">
-{type.type}({type.count})
</div>
<li key={index} className="rb:leading-6 rb:text-[#5B6167]">
{type.type}({type.count})
</li>
)
})}
</div>
</ul>
</div>
)
})}
</div>
</RbCard>
}
</Space>
</div>
<div className="rb:grid rb:grid-cols-2 rb:gap-4 rb:mt-5">
<Button block loading={loading} onClick={handleSave}>{t('common.save')}</Button>
<Button block type="primary" loading={runLoading} onClick={handleRun}>{t('memoryExtractionEngine.debug')}</Button>
</div>
</div>
</ResultCard>
}
</Flex>}
</Card>
)
}

View File

@@ -0,0 +1,78 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:30:51
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 14:23:58
*/
/**
* 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;
}
const ResultCard: FC<ResultCardProps> = ({
title,
subTitle,
children,
expanded,
handleExpand,
extra,
className,
headerClassName,
bodyClassName,
}) => {
return (
<RbCard
title={() => <Flex
align="center"
justify="space-between"
className="rb:font-[MiSans-Bold] rb:font-bold 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) && children}
</RbCard>
)
}
export default ResultCard

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:30:02
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-06 13:50:05
* @Last Modified time: 2026-03-18 17:55:32
*/
/**
* Memory Extraction Engine Configuration Page
@@ -13,18 +13,19 @@
import { type FC, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import { Row, Col, Space, Select, InputNumber, Slider, App, Form, Input } from 'antd'
import { Row, Col, Space, Select, InputNumber, App, Form, Input, Flex, Tooltip } from 'antd'
import clsx from 'clsx'
import Card from './components/Card'
import type { ConfigForm, Variable } from './types'
import { getMemoryExtractionConfig, updateMemoryExtractionConfig } from '@/api/memory'
import Markdown from '@/components/Markdown'
import { getModelListUrl } from '@/api/models';
import { configList } from './constant'
import Result from './components/Result'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import CustomSelect from '@/components/CustomSelect'
import ModelSelect from '@/components/ModelSelect'
import RbSlider from '@/components/RbSlider';
import DescWrapper from '@/components/FormItem/DescWrapper'
/** Available configuration section keys */
const keys = [
@@ -35,7 +36,7 @@ const keys = [
/**
* Configuration description component
*/
const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boolean; }> = ({ config, className, onlyMeaning = false}) => {
const Desc: FC<{ config: Variable, className?: string; onlyMeaning?: boolean; }> = ({ config, className, onlyMeaning = false}) => {
const { t } = useTranslation();
return (
<div className={className}>
@@ -44,7 +45,6 @@ const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boole
{config.control && <span className="rb:font-regular">{t('memoryExtractionEngine.control')}: {t(`memoryExtractionEngine.${config.control}`)}</span>}
{config.type && <span className="rb:font-regular">{t('memoryExtractionEngine.type')}: {config.type}</span>}
</Space>}
{config.meaning && <div className={clsx("rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}</div>}
</div>
)
}
@@ -122,163 +122,139 @@ const MemoryExtractionEngine: FC = () => {
return (
<>
<div className="rb:text-[24px] rb:font-semibold rb:leading-8 rb:mb-2">{t('memoryExtractionEngine.title')}</div>
<div className="rb:text-[#5B6167] rb:leading-5 rb:mb-6">{t('memoryExtractionEngine.subTitle')}</div>
<Flex align="center" gap={4} className="rb:font-[MiSans-Bold] rb:text-[16px] rb:font-bold rb:leading-5.5 rb:mb-4!">
{t('memoryExtractionEngine.title')}
<Tooltip title={t('memoryExtractionEngine.subTitle')}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
</Tooltip>
</Flex>
<Row gutter={[16, 16]}>
<Row gutter={12}>
<Col span={12}>
<Form form={modelForm}>
<Form.Item
label={t('memoryExtractionEngine.model')}
name="llm_id"
>
<CustomSelect
url={getModelListUrl}
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
valueKey="id"
labelKey="name"
hasAll={false}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Col>
</Row>
<Card
type="example"
title={t('memoryExtractionEngine.example')}
expanded={expandedKeys.includes('example')}
handleExpand={handleExpand}
>
{expandedKeys.includes('example') &&
<div className="rb:text-[14px] rb:text-[#5B6167] rb:font-regular rb:leading-5">
<Markdown content={t('memoryExtractionEngine.exampleText')} />
</div>
}
</Card>
<Row gutter={[16, 16]} className="rb:mt-4">
<Col span={14}>
<Form
form={form}
>
<Space direction="vertical" size={16} style={{ width: '100%' }}>
{configList.map((item, index) => (
<Card
type={item.type}
title={t(`memoryExtractionEngine.${item.type}`)}
key={index}
expanded={expandedKeys.includes(item.type)}
handleExpand={handleExpand}
>
<Space size={20} direction="vertical" style={{width: '100%'}}>
{item.data.map(vo => (
<div
key={vo.title}
className={clsx(
`rb:p-[16px_24px] rb:rounded-lg`,
'rb:border rb:border-[#DFE4ED]',
{
'rb:shadow-[inset_4px_0px_0px_0px_#155EEF]': index % 2 === 0,
'rb:shadow-[inset_4px_0px_0px_0px_#369F21]': index % 2 !== 0,
}
)}
>
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5">{t(`memoryExtractionEngine.${vo.title}`)}</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`memoryExtractionEngine.${vo.title}SubTitle`)}</div>
<Flex vertical gap={12} className="rb:h-[calc(100vh-114px)]! rb:overflow-y-auto">
<Form form={modelForm}>
<Form.Item
name="llm_id"
noStyle
>
<ModelSelect
params={{ type: 'llm,chat' }}
className="rb:w-full! rb:h-10! rb:bg-white rb:rounded-xl"
variant="borderless"
placeholder={t('memoryExtractionEngine.model')}
allowClear={false}
fontClassName="rb:font-medium!"
/>
</Form.Item>
</Form>
{vo.list.map(config => (
<div key={config.label}>
{config.control === 'button' &&
<SwitchFormItem
title={<>-{t(`memoryExtractionEngine.${config.label}`)}</>}
name={config.variableName}
desc={<ConfigDesc config={config} className="rb:ml-2" />}
className="rb:mt-6"
/>
}
{config.control === 'select' &&
<>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
-{t(`memoryExtractionEngine.${config.label}`)}
</div>
<div className="rb:pl-2">
<div className="rb:bg-white rb:rounded-xl rb:py-2.5 rb:px-4">
<Flex
align="center"
justify="space-between"
className="rb:font-[MiSans-Bold] rb:font-bold rb:cursor-pointer"
onClick={() => handleExpand('example')}
>
{t('memoryExtractionEngine.example')}
<div className={clsx("rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')]", {
'rb:rotate-180': !expandedKeys.includes('example'),
'rb:rotate-0': expandedKeys.includes('example'),
})}></div>
</Flex>
{expandedKeys.includes('example') &&
<div className="rb:text-[14px] rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:mt-2.5 rb:mb-1.5">
<Markdown content={t('memoryExtractionEngine.exampleText')} />
</div>
}
</div>
<Form form={form}>
<Flex vertical gap={16}>
{configList.map((item, index) => (
<Card
type={item.type}
title={t(`memoryExtractionEngine.${item.type}`)}
key={index}
expanded={expandedKeys.includes(item.type)}
handleExpand={handleExpand}
>
<Flex gap={16} vertical>
{item.data.map(vo => (
<Flex
key={vo.title}
vertical
gap={10}
className="rb:bg-[#F6F6F6] rb:rounded-xl rb:p-3! rb:pt-2.5!"
>
<Space size={4} className="rb:text-[#212332] rb:font-medium rb:leading-5">
{t(`memoryExtractionEngine.${vo.title}`)}
<Tooltip title={t(`memoryExtractionEngine.${vo.title}SubTitle`)}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
</Tooltip>
</Space>
{vo.list.map(config => (
<div key={config.label} className="rb:bg-white rb:rounded-xl rb:p-3 rb:pr-2.5">
{config.control === 'button'
? <SwitchFormItem
title={t(`memoryExtractionEngine.${config.label}`)}
name={config.variableName}
desc={<DescWrapper desc={<Desc config={config} />} />}
className="rb:mt-6"
/>
: <>
{config.meaning
? <Space size={4} className="rb:text-[#212332] rb:font-medium rb:leading-5">
{t(`memoryExtractionEngine.${config.label}`)}
<Tooltip title={<>{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}</>}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/question.svg')]"></div>
</Tooltip>
</Space>
: <div className="rb:text-[#212332] rb:font-medium rb:leading-5">
{t(`memoryExtractionEngine.${config.label}`)}
</div>
}
{config.control !== 'text' && <DescWrapper desc={<Desc config={config} />} />}
<Form.Item
name={config.variableName}
className="rb:mb-0! rb:mt-2!"
>
<Select
disabled={config.variableName === 'iteration_period' && iterationPeriodDisabled}
options={config.options ? config.options.map(item => ({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []}
/>
{config.control === 'select'
? <Select
disabled={config.variableName === 'iteration_period' && iterationPeriodDisabled}
options={config.options ? config.options.map(item => ({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []}
/>
: config.control === 'slider'
? <RbSlider
min={config.min || 0}
max={config.max || 1}
step={config.step || 0.01}
isInput={true}
prefix={<span className="rb:text-[#5B6167]">{t('emotionEngine.currentValue')}:</span>}
inputClassName="rb:w-[155px]!"
/>
: config.control === 'inputNumber'
? <InputNumber min={config.min || 0} style={{ width: '100%' }} placeholder={t('common.pleaseEnter')} />
: config.control === 'text'
? <Input placeholder={t('common.pleaseEnter')} disabled />
: null
}
</Form.Item>
<ConfigDesc config={config} className="rb:-mt-4!" />
</div>
</>
}
{config.control === 'slider' &&
<>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
-{t(`memoryExtractionEngine.${config.label}`)}
</div>
<div className="rb:pl-2">
<ConfigDesc config={config} className="rb:mb-2.5" />
<Form.Item
name={config.variableName}
>
<Slider
style={{ margin: '0' }}
min={config.min || 0}
max={config.max || 1}
step={config.step || 0.01}
/>
</Form.Item>
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-5 rb:-mt-6.5">
{config.min || 0}
<span>{t('memoryExtractionEngine.CurrentValue')}: {values?.[config.variableName as keyof ConfigForm]}</span>
</div>
</div>
</>
}
{config.control === 'inputNumber' &&
<>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
-{t(`memoryExtractionEngine.${config.label}`)}
</div>
<div className="rb:pl-2">
<Form.Item
name={config.variableName}
>
<InputNumber min={config.min || 0} style={{ width: '100%' }} placeholder={t('common.pleaseEnter')} />
</Form.Item>
<ConfigDesc config={config} className="rb:-mt-4!" />
</div>
</>
}
{config.control === 'text' &&
<>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
-{t(`memoryExtractionEngine.${config.label}`)}
</div>
<div className="rb:pl-2">
<Form.Item
name={config.variableName}
>
<Input placeholder={t('common.pleaseEnter')} disabled />
</Form.Item>
<ConfigDesc config={config} onlyMeaning={true} className="rb:-mt-4!" />
</div>
</>
}
</div>
))}
</div>
))}
</Space>
</Card>
))}
</Space>
</Form>
</>
}
</div>
))}
</Flex>
))}
</Flex>
</Card>
))}
</Flex>
</Form>
</Flex>
</Col>
<Col span={10}>
<Col span={12}>
<Result
loading={loading}
handleSave={handleSave}

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:46:47
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:46:47
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 15:54:45
*/
/**
* Self Reflection Engine Configuration Page
@@ -11,12 +11,11 @@
*/
import React, { useState, useEffect } from 'react';
import { Row, Col, Form, App, Button, Space, Select } from 'antd';
import { Row, Col, Form, App, Button, Space, Select, Flex } from 'antd';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import RbCard from '@/components/RbCard/Card';
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
import { getMemoryReflectionConfig, updateMemoryReflectionConfig, pilotRunMemoryReflectionConfig } from '@/api/memory'
import type { ConfigForm, Result, ReflexionData, MemoryVerify, QualityAssessment } from './types'
import CustomSelect from '@/components/CustomSelect';
@@ -24,6 +23,8 @@ import { getModelListUrl } from '@/api/models'
import Tag from '@/components/Tag'
import { useI18n } from '@/store/locale';
import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import LabelWrapper from '@/components/FormItem/LabelWrapper'
import DescWrapper from '@/components/FormItem/DescWrapper'
/** Configuration list */
const configList = [
@@ -172,13 +173,16 @@ const SelfReflectionEngine: React.FC = () => {
return (
<Row gutter={[16, 16]}>
<Col span={12}>
<RbCard
title={
<div className="rb:flex rb:items-center">
<img src={strategyImpactSimulator} className="rb:w-5 rb:h-5 rb:mr-2" />
{t('reflectionEngine.reflectionEngineConfig')}
</div>
}
<RbCard
title={t('reflectionEngine.reflectionEngineConfig')}
extra={<Space>
<Button block onClick={handleReset}>{t('common.reset')}</Button>
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
</Space>}
headerType="borderless"
headerClassName="rb:min-h-[54px]! rb:font-[MiSans-Bold] rb:font-bold"
className="rb:h-[calc(100vh-76px)]!"
bodyClassName="rb:h-[calc(100%-54px)] rb:overflow-y-auto! rb:p-4! rb:pt-0!"
>
<Form
form={form}
@@ -189,74 +193,68 @@ const SelfReflectionEngine: React.FC = () => {
lambda_mem: 0.03,
}}
>
{configList.map(config => {
if (config.type === 'customSelect') {
return (
<div key={config.key}>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
{t(`reflectionEngine.${config.key}`)}
<Flex vertical gap={24}>
{configList.map(config => {
if (config.type === 'customSelect') {
return (
<div key={config.key}>
<LabelWrapper title={t(`reflectionEngine.${config.key}`)} className="rb:mb-3">
<DescWrapper desc={t(`reflectionEngine.${config.key}_desc`)} className="rb:mt-1" />
</LabelWrapper>
<Form.Item
name={config.key}
className="rb:mb-0!"
>
<CustomSelect
url={config.url as string}
params={config.params}
valueKey='id'
labelKey='name'
hasAll={false}
placeholder={t('common.pleaseSelect')}
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
</Form.Item>
</div>
<Form.Item
name={config.key}
extra={t(`reflectionEngine.${config.key}_desc`)}
>
<CustomSelect
url={config.url as string}
params={config.params}
valueKey='id'
labelKey='name'
hasAll={false}
placeholder={t('common.pleaseSelect')}
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
</Form.Item>
</div>
)
}
if (config.type === 'select') {
return (
<div key={config.key}>
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mb-2">
{t(`reflectionEngine.${config.key}`)}
)
}
if (config.type === 'select') {
return (
<div key={config.key}>
<LabelWrapper title={t(`reflectionEngine.${config.key}`)} className="rb:mb-3">
<DescWrapper desc={t(`reflectionEngine.${config.key}_desc`)} className="rb:mt-1" />
</LabelWrapper>
<Form.Item
name={config.key}
className="rb:mb-0!"
>
<Select
options={config.options?.map(vo => ({
...vo,
label: t(`reflectionEngine.${vo.label}`),
}))}
placeholder={t('common.pleaseSelect')}
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
</Form.Item>
</div>
<Form.Item
name={config.key}
extra={t(`reflectionEngine.${config.key}_desc`)}
>
<Select
options={config.options?.map(vo => ({
...vo,
label: t(`reflectionEngine.${vo.label}`),
}))}
placeholder={t('common.pleaseSelect')}
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
</Form.Item>
</div>
)
}
)
}
return (
<SwitchFormItem
title={t(`reflectionEngine.${config.key}`)}
name={config.key}
desc={<>
{(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>}
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div>
</>}
className="rb:mb-6"
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
)
})}
<Row gutter={16} className="rb:mt-3">
<Col span={12}>
<Button block onClick={handleReset}>{t('common.reset')}</Button>
</Col>
<Col span={12}>
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
</Col>
</Row>
return (
<SwitchFormItem
title={t(`reflectionEngine.${config.key}`)}
name={config.key}
desc={<>
{(config as any).hasSubTitle && <div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_subTitle`)}</div>}
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t(`reflectionEngine.${config.key}_desc`)}</div>
</>}
className="rb:mb-6"
disabled={!values?.reflection_enabled && config.key !== 'reflection_enabled'}
/>
)
})}
</Flex>
</Form>
</RbCard>
</Col>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:53:44
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 15:01:27
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 15:23:18
*/
/**
* User Memory Page
@@ -104,7 +104,7 @@ export default function UserMemory() {
title={<Flex gap={4}>
<div className="rb:size-6 rb:text-center rb:font-semibold rb:leading-6 rb:rounded-md rb:text-white rb:bg-[#155EEF]">{name[0]}</div>
<Tooltip title={name || '-'}><div className={`rb:w-full rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap`}>{name || '-'}</div></Tooltip>
<Tooltip title={name || '-'}><div className={`rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap`}>{name || '-'}</div></Tooltip>
</Flex>}
headerType="border"
headerClassName="rb:h-[48px]! rb:mx-4!"

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:57:11
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:57:11
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 11:38:17
*/
/**
* RAG User Memory Detail View
@@ -12,83 +12,55 @@
import { type FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import clsx from 'clsx'
import { Row, Col, Skeleton } from 'antd'
import { Row, Col, Skeleton, Flex } from 'antd'
import { useParams } from 'react-router-dom'
import aboutUs from '@/assets/images/userMemory/aboutUs.svg'
import down from '@/assets/images/userMemory/down.svg'
import interestDistribution from '@/assets/images/userMemory/interestDistribution.svg'
import memoryInsight from '@/assets/images/userMemory/memoryInsight.svg'
import RbCard from '@/components/RbCard/Card'
import type { Data } from './types'
import {
getChunkSummaryTag,
getUserProfile,
getTotalRagMemoryCountByUser,
getChunkInsight,
} from '@/api/memory'
import Empty from '@/components/Empty'
import ConversationMemory from './components/ConversationMemory'
/** Tag color palette */
const tagColors = ['21, 94, 239', '156, 111, 255', '255, 93, 52', '54, 159, 33']
/**
* Title component props
*/
interface TitleProps {
type: string;
title: string
icon: string
t: (key: string) => string;
expanded: boolean;
onClick: (type: string) => void;
}
/** Collapsible section title */
const Title: FC<TitleProps> = ({ type, title, icon, t, expanded, onClick }) => (
<div className="rb:flex rb:items-center rb:justify-between rb:py-4.25 rb:border-b rb:border-[#DFE4ED] rb:text-[16px] rb:font-semibold rb:leading-5.5">
<span className="rb:flex rb:items-center">
<img src={icon} className="rb:w-5 rb:h-5 rb:mr-2" />
{title}
</span>
<span className="rb:flex rb:items-center rb:cursor-pointer rb:text-[#5B6167] rb:text-[14px] rb:font-regular rb:leading-5" onClick={() => onClick(type)}>
{t(`userMemory.${expanded ? 'foldUp' : 'expanded'}`)}
<img src={down} className={clsx("rb:w-4 rb:h-4 rb:ml-1", {
'rb:rotate-180': !expanded,
})} />
</span>
</div>
const Title: FC<TitleProps> = ({ title, icon }) => (
<Flex align="center" gap={4} className="rb:font-medium rb:leading-5 rb:mb-2.25!">
<img src={icon} className="rb:size-4.5 rb:ml-0.5" />
{title}
</Flex>
)
const Rag: FC = () => {
const { t } = useTranslation()
const { id } = useParams()
const [data, setData] = useState<Data | null>(null)
const [expanded, setExpanded] = useState<string[]>(['aboutUs', 'memoryInsight',])
const [summary, setSummary] = useState<string | null>('')
const [loading, setLoading] = useState<Record<string, boolean>>({
detail: true,
summary: true,
insight: true,
})
const [memory, setMemory] = useState<number | null>(null)
const [insight, setInsight] = useState<string | null>('')
const [tags, setTags] = useState<{ tag: string; frequency: number }[]>([])
const [personas, setPersonas] = useState<string[]>([])
useEffect(() => {
if (!id) return
getMemory()
getSummary()
getDetail()
getInsightReport()
}, [id])
/** Toggle section expansion */
const handleTitleClick = (key: string) => {
setExpanded(expanded.includes(key) ? expanded.filter((item) => item !== key) : [...expanded, key])
}
/** Fetch user memory detail */
const getDetail = () => {
if (!id) return
@@ -100,13 +72,6 @@ const Rag: FC = () => {
setLoading(prev => ({ ...prev, detail: false }))
})
}
/** Fetch memory count */
const getMemory = () => {
if (!id) return
getTotalRagMemoryCountByUser(id).then((res) => {
setMemory(res as number || 0)
})
}
/** Fetch user summary */
const getSummary = () => {
if (!id) return
@@ -114,8 +79,6 @@ const Rag: FC = () => {
getChunkSummaryTag(id).then((res) => {
const response = res as { summary?: string; tags?: { tag: string; frequency: number }[]; personas?: string[] }
setSummary(response.summary || null)
setTags(response.tags || [])
setPersonas(response.personas || [])
})
.finally(() => {
setLoading(prev => ({ ...prev, summary: false }))
@@ -134,82 +97,51 @@ const Rag: FC = () => {
}
const name = loading.detail ? '' : data?.name && data?.name !== '' ? data.name : id
return (
<Row gutter={[16, 16]} className="rb:pb-6">
<Row gutter={[16, 16]}>
<Col span={8}>
<RbCard>
<div className="rb:flex rb:items-center">
<div className="rb:flex-[0_0_auto] rb:w-20 rb:h-20 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-20 rb:rounded-lg rb:text-[#FBFDFF] rb:bg-[#155EEF]">{name?.[0]}</div>
<div className="rb:text-[24px] rb:font-semibold rb:leading-8 rb:ml-4">
{name}<br/>
<div className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 rb:mt-2">{personas?.join(' | ')}</div>
<RbCard
bodyClassName="rb:p-3! rb:pt-4! rb:h-[calc(100vh-76px)]"
>
<Flex align="center" gap={12} className="rb:mb-6!">
<div className="rb:size-12 rb:text-center rb:font-semibold rb:text-[28px] rb:leading-12 rb:rounded-xl rb:text-white rb:bg-[#155EEF]">{name?.[0]}</div>
<div className="rb:text-[16px] rb:font-semibold rb:leading-6 rb:line-clamp-2 rb:flex-1">
{name}
</div>
</div>
<div className="rb:flex rb:gap-2 rb:mb-2 rb:flex-wrap rb:mt-6.25">
{tags?.map((tag, tagIndex) => (
<span key={tag.tag} className="rb:rounded-[11px] rb:p-[0_8px] rb:leading-5.5 rb:border"
style={{
backgroundColor: `rgba(${tagColors[tagIndex % tagColors.length]}, 0.08)`,
borderColor: `rgba(${tagColors[tagIndex % tagColors.length]}, 0.3)`,
color: `rgba(${tagColors[tagIndex % tagColors.length]}, 1)`,
}}
>
{tag.tag}({tag.frequency})
</span>
))}
</div>
{/* Total Memory */}
<div className="rb:font-regular rb:text-[12px] rb:text-[#5B6167] rb:leading-4 rb:mb-6.25">
{t('userMemory.totalNumOfMemories')}
<div className="rb:font-extrabold rb:text-[24px] rb:text-[#212332] rb:leading-7.5 rb:mt-2">{memory || 0}</div>
</div>
</Flex>
{/* About Me */}
<>
<Title
type="aboutUs"
title={t('userMemory.aboutMe')}
icon={aboutUs}
t={t}
expanded={expanded.includes('aboutUs')}
onClick={handleTitleClick}
/>
{expanded.includes('aboutUs') && (
<>
{loading.summary
? <Skeleton className="rb:mt-4" />
: summary
? <div className="rb:font-regular rb:leading-5.5 rb:pt-4">
{summary || '-'}
</div>
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
}
</>
)}
<div className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2.5 rb:px-3 rb:mb-4">
{loading.summary
? <Skeleton />
: summary
? <div className="rb:leading-5 rb:text-[#5B6167]">
{summary || '-'}
</div>
: <Empty size={88} />
}
</div>
</>
{/* Memory Insights */}
<>
<Title
type="memoryInsight"
title={t('userMemory.memoryInsight')}
icon={interestDistribution}
t={t}
expanded={expanded.includes('memoryInsight')}
onClick={handleTitleClick}
icon={memoryInsight}
/>
{expanded.includes('memoryInsight') && (
<>
{loading.insight
? <Skeleton className="rb:mt-4" />
: insight
? <div className="rb:font-regular rb:leading-5.5 rb:pt-4">
{insight || '-'}
</div>
: <Empty size={88} className="rb:mt-12 rb:mb-20.25" />
}
</>
)}
<div className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-2.5 rb:px-3">
{loading.insight
? <Skeleton />
: insight
? <div className="rb:leading-5 rb:text-[#5B6167]">
{insight || '-'}
</div>
: <Empty size={88} />
}
</div>
</>
</RbCard>
</Col>

View File

@@ -1,12 +1,13 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 18:33:44
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 18:33:44
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 11:55:42
*/
import { type FC, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import { Flex } from 'antd';
import Empty from '@/components/Empty'
import Loading from '@/components/Empty/Loading'
@@ -63,12 +64,12 @@ const EmotionLine: FC<EmotionLineProps> = ({ chartData, loading }) => {
}
return (
<>
<div>{t('userMemory.emotionLine')}</div>
<Flex vertical gap={16} className="rb-border rb:rounded-xl rb:p-4! rb:h-78">
<div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('userMemory.emotionLine')}</div>
{loading
? <Loading size={249} />
: !chartData || chartData.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
? <Empty size={120} className="rb:flex-1" />
: <ReactEcharts
ref={chartRef}
option={{
@@ -175,12 +176,12 @@ const EmotionLine: FC<EmotionLineProps> = ({ chartData, loading }) => {
},
series: getSeries()
}}
style={{ height: '265px', width: '100%', minWidth: '100%' }}
style={{ height: '242px', width: '100%', minWidth: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
}
</>
</Flex>
)
}

View File

@@ -77,7 +77,7 @@ const Habits = forwardRef<{ handleRefresh: () => void; }>((_props, ref) => {
title={() => (<Space size={4}>
{t('implicitDetail.habits')}
<Tooltip title={t('implicitDetail.habitsSubTitle')}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div>
</Tooltip>
</Space>)}
headerType="borderless"

View File

@@ -1,12 +1,13 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 18:32:57
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 18:32:57
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 11:56:49
*/
import { type FC, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react'
import { Flex } from 'antd'
import Empty from '@/components/Empty'
import Loading from '@/components/Empty/Loading'
@@ -41,12 +42,12 @@ const InteractionBar: FC<InteractionBarProps> = ({ chartData, loading }) => {
}, [chartData, t])
return (
<>
<div>{t('userMemory.interaction')}</div>
<Flex vertical gap={16} className="rb-border rb:rounded-xl rb:p-4! rb:h-78">
<div className="rb:text-[#212332] rb:font-medium rb:leading-5">{t('userMemory.emotionLine')}</div>
{loading
? <Loading size={249} />
: !chartData || chartData.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
? <Empty size={120} className="rb:flex-1" />
: <ReactEcharts
option={{
color: Colors,
@@ -128,10 +129,10 @@ const InteractionBar: FC<InteractionBarProps> = ({ chartData, loading }) => {
},
series
}}
style={{ height: '265px', width: '100%' }}
style={{ height: '242px', width: '100%', minWidth: '100%' }}
/>
}
</>
</Flex>
)
}

View File

@@ -1,7 +1,7 @@
import { useState, forwardRef, useImperativeHandle, useMemo, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useSearchParams } from 'react-router-dom'
import { Row, Col, Tabs, Space, Skeleton } from 'antd'
import { useSearchParams, useNavigate } from 'react-router-dom'
import { Row, Col, Flex, Space, Skeleton, Button } from 'antd'
import { getRelationshipEvolution, getTimelineMemories } from '@/api/memory'
import type { Node, GraphDetailRef } from '../types'
@@ -11,7 +11,8 @@ import { formatDateTime } from '@/utils/format'
import Tag from '@/components/Tag'
import InteractionBar from '../components/InteractionBar'
import Empty from '@/components/Empty'
import PageHeader from '../components/PageHeader'
import PageHeader from '@/components/Layout/PageHeader'
import BtnTabs from '@/components/BtnTabs'
export interface Emotion {
emotion_intensity: number;
@@ -36,6 +37,7 @@ interface Timeline {
const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
const { t } = useTranslation()
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const [vo, setVo] = useState<Node | null>(null)
const [loading, setLoading] = useState(false)
@@ -97,54 +99,75 @@ const GraphDetail = forwardRef<GraphDetailRef>((_props, ref) => {
return (
<>
<PageHeader
name={vo?.name}
source="node"
title={vo?.name}
extra={
<Space size={12}>
<Button
className="rb:px-2! rb:gap-0.5!"
icon={<div className="rb:size-4 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/common/return.svg')]"></div>}
onClick={() => navigate(-1)}
>
{t('common.return')}
</Button>
</Space>
}
/>
<div className="rb:h-full rb:max-w-266 rb:mx-auto">
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3">{t('userMemory.relationshipEvolution')}</div>
<RbCard>
<Row gutter={16}>
<Col span={12}>
<Row gutter={12} className="rb:p-3! rb:pr-0! rb:h-[calc(100vh-64px)] rb:w-full! rb:flex-nowrap! rb:overflow-hidden!">
<Col flex="480px">
<RbCard
title={t('userMemory.relationshipEvolution')}
headerType="borderless"
headerClassName="rb:min-h-[56px]! rb:font-[MiSans-Bold] rb:font-bold"
bodyClassName="rb:p-3! rb:pt-0! rb:h-[calc(100%-56px)] rb:overflow-y-auto!"
className="rb:h-[calc(100vh-88px)]!"
>
<Flex vertical gap={16}>
<EmotionLine chartData={emotionData} loading={loading} />
</Col>
<Col span={12}>
<InteractionBar chartData={interactionData} loading={loading} />
</Col>
</Row>
</RbCard>
<div className="rb:text-[16px] rb:font-medium rb:leading-5.5 rb:mb-3 rb:mt-6">{t('userMemory.timelineMemories')}</div>
<RbCard>
<Tabs
activeKey={activeTab}
items={['timelines_memory', 'Statement', 'MemorySummary'].map(key => ({
label: t(`userMemory.${key}`),
key
}))}
onChange={(key: string) => setActiveTab(key)}
/>
{timelineLoading
? <Skeleton active />
: !activeContent || activeContent.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
: <Space size={16} direction="vertical" className="rb:w-full">
{activeContent.map((vo, index) => (
<RbCard
key={index}
headerType="borderL"
headerClassName="rb:before:bg-[#155EEF]!"
title={vo.text}
>
<div className="rb:text-[#A8A9AA] rb:text-[12px] rb:leading-4">{formatDateTime(vo.created_at)}</div>
<Tag className="rb:mt-2">{vo.type}</Tag>
</RbCard>
))}
</Space>
}
</RbCard>
</div>
</Flex>
</RbCard>
</Col>
<Col className="rb:w-[calc(100%-480px)]!">
<RbCard
title={t('userMemory.timelineMemories')}
headerType="borderless"
headerClassName="rb:min-h-[53px]! rb:font-[MiSans-Bold] rb:font-bold"
bodyClassName="rb:p-3! rb:pt-0!"
className="rb:w-full!"
>
<BtnTabs
className="rb:mb-4!"
activeKey={activeTab}
items={['timelines_memory', 'Statement', 'MemorySummary'].map(key => ({
label: t(`userMemory.${key}`),
key
}))}
onChange={(key: string) => setActiveTab(key)}
/>
<div className="rb:h-[calc(100vh-193px)] rb:overflow-y-auto">
{timelineLoading
? <Skeleton active />
: !activeContent || activeContent.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
: <Flex gap={12} vertical>
{activeContent.map((vo, index) => (
<div
key={index}
className="rb-border rb:rounded-xl rb:p-3"
>
<Flex align="center" justify="space-between">
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-4.5">{formatDateTime(vo.created_at)}</div>
<Tag>{vo.type}</Tag>
</Flex>
<div className="rb:mt-3 rb:leading-5 rb:break-all">{vo.text}</div>
</div>
))}
</Flex>
}
</div>
</RbCard>
</Col>
</Row>
</>
)
})

View File

@@ -110,7 +110,7 @@ const ShortTermDetail: FC = () => {
title={() => (<Space size={4}>
{t('shortTermDetail.shortTermTitle')}
<Tooltip title={t('shortTermDetail.shortTermSubTitle')}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div>
</Tooltip>
</Space>)}
headerType="borderless"
@@ -194,7 +194,7 @@ const ShortTermDetail: FC = () => {
title={() => (<Space size={4}>
{t('shortTermDetail.longTermTitle')}
<Tooltip title={t('shortTermDetail.longTermTitleSubTitle')}>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/userMemory/question.svg')]"></div>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/userMemory/question.svg')]"></div>
</Tooltip>
</Space>)}
headerType="borderless"

View File

@@ -178,7 +178,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => {
'rb:border-[#d1d5db] rb:bg-[#FCFCFD] rb:text-[#374151]': !data.isSelected
})}
>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/workflow/node_plus.png')]"></div>
<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/workflow/node_plus.png')]"></div>
{data.label}
</Flex>
</Popover>

View File

@@ -27,7 +27,7 @@ const NodeTools: FC<{ node: Node }> = ({
<Dropdown
menu={{
items: [
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex>},
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex>},
// { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') }
],
onClick: handleClick

View File

@@ -248,7 +248,7 @@ const CaseList: FC<CaseListProps> = ({
<Form.Item name={[caseField.name, 'logical_operator']} noStyle >
<Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={() => handleChangeLogicalOperator(caseIndex)}>
{logicalOperator}
<div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/workflow/refresh_active.svg')]"></div>
<div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div>
</Space>
</Form.Item>
</div>

View File

@@ -114,7 +114,7 @@ const ConditionList: FC<CaseListProps> = ({
<Form.Item name={[parentName, 'logical_operator']} noStyle >
<Space size={2} className="rb:cursor-pointer rb:text-[#155EEF] rb:leading-4.5 rb:font-medium rb-border rb:py-px! rb:px-1! rb:rounded-sm" onClick={handleChangeLogicalOperator}>
{logicalOperator}
<div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/workflow/refresh_active.svg')]"></div>
<div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/workflow/refresh_active.svg')]"></div>
</Space>
</Form.Item>
</div>

View File

@@ -434,7 +434,7 @@ const Properties: FC<PropertiesProps> = ({
<Dropdown
menu={{
items: [
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex> },
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex> },
// { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') }
],
onClick: handleClick
@@ -834,7 +834,7 @@ const Properties: FC<PropertiesProps> = ({
<Flex align="center" className="rb:font-medium rb:cursor-pointer" onClick={handleToggle}>
{t('workflow.config.output')}
<div
className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/caret_right_outlined.svg')]", {
className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/common/caret_right_outlined.svg')]", {
'rb:rotate-90': !outputCollapsed
})}
></div>