handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)}
+ onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)}
/>
}
diff --git a/web/src/components/Empty/BodyWrapper.tsx b/web/src/components/Empty/BodyWrapper.tsx
index 067b743c..5d23b55c 100644
--- a/web/src/components/Empty/BodyWrapper.tsx
+++ b/web/src/components/Empty/BodyWrapper.tsx
@@ -24,16 +24,17 @@ interface BodyWrapperProps {
/** Whether to show loading state */
loading?: boolean
/** Whether the content is empty */
- empty: boolean
+ empty: boolean;
+ className?: string;
}
-const BodyWrapper: FC
= ({ children, loading = false, empty }) => {
+const BodyWrapper: FC = ({ children, loading = false, empty, className = 'rb:max-h-[calc(100%-48px)]!' }) => {
// Show loading spinner while data is being fetched
if (loading) {
- return
+ return
}
// Show empty state when no data is available
if (!loading && empty) {
- return
+ return
}
// Render actual content when data is loaded and available
return children
diff --git a/web/src/components/SiderMenu/index.tsx b/web/src/components/SiderMenu/index.tsx
index 3bd0cea3..21f7fd36 100644
--- a/web/src/components/SiderMenu/index.tsx
+++ b/web/src/components/SiderMenu/index.tsx
@@ -128,6 +128,7 @@ const Menu: FC<{
/** Filter menus based on user role and source */
useEffect(() => {
+ if (!user) return
let menuList: MenuItem[] = []
if (user.role === 'member' && source === 'space') {
@@ -136,7 +137,7 @@ const Menu: FC<{
menuList = allMenus[source] || []
}
- const noAuthList = ['user', 'pricing'].filter(vo => !user.permissions?.includes(vo) && !user.permissions?.includes('all'))
+ const noAuthList = ['user', 'pricing'].filter(vo => (Array.isArray(user.permissions) && !user.permissions?.includes(vo) && !user.permissions?.includes('all')) || !Array.isArray(user.permissions))
if (noAuthList && !noAuthList?.includes('all')) {
const filterMenus = (list: MenuItem[]): MenuItem[] =>{
diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts
index 57e95d81..8c5f6237 100644
--- a/web/src/i18n/en.ts
+++ b/web/src/i18n/en.ts
@@ -1512,7 +1512,7 @@ export const en = {
EPISODIC_MEMORY: 'Episodic Memory',
FORGET_MEMORY: 'Forget Memory',
- endUserProfile: 'Profile',
+ endUserProfile: 'Permanent Memory',
editEndUserProfile: 'Edit',
other_name: 'Name',
position: 'Position',
diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts
index 39d63399..c50d86ed 100644
--- a/web/src/i18n/zh.ts
+++ b/web/src/i18n/zh.ts
@@ -1510,7 +1510,7 @@ export const zh = {
EPISODIC_MEMORY: '情景记忆',
FORGET_MEMORY: '遗忘记忆',
- endUserProfile: '核心档案',
+ endUserProfile: '永久记忆',
editEndUserProfile: '编辑',
other_name: '名称',
position: '职位',
diff --git a/web/src/styles/index.css b/web/src/styles/index.css
index 684a4ba6..c6e7f359 100644
--- a/web/src/styles/index.css
+++ b/web/src/styles/index.css
@@ -377,9 +377,10 @@ body {
.ant-input-filled,
.ant-select-filled:not(.ant-select-customize-input) .ant-select-selector {
background-color: #FFFFFF;
+ border-color: #FFFFFF;
}
.ant-input-filled:hover,
-.ant-select-filled:not(.ant-select-customize-input) .ant-select-selector {
+.ant-select-filled:not(.ant-select-disabled):not(.ant-select-customize-input):not(.ant-pagination-size-changer):hover .ant-select-selector {
background-color: #FFFFFF;
border-color: #171719;
}
@@ -402,7 +403,7 @@ body {
color: #FF5D34;
}
-.spin.ant-spin-nested-loading .ant-spin-container::after {
+.spin .ant-spin-nested-loading .ant-spin-container::after {
background: transparent;
}
.upload-block,
diff --git a/web/src/views/ApplicationConfig/Logs.tsx b/web/src/views/ApplicationConfig/Logs.tsx
index 88fa2607..cf56059c 100644
--- a/web/src/views/ApplicationConfig/Logs.tsx
+++ b/web/src/views/ApplicationConfig/Logs.tsx
@@ -34,16 +34,16 @@ const Statistics: FC = () => {
className: 'rb:text-[#212332]'
},
{
- title: t('user.createTime'),
+ title: t('application.created_at'),
dataIndex: 'created_at',
key: 'created_at',
render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'),
},
{
- title: t('user.lastLoginTime'),
- dataIndex: 'last_login_at',
- key: 'last_login_at',
- render: (lastLoginAt: string) => lastLoginAt ? formatDateTime(lastLoginAt, 'YYYY-MM-DD HH:mm:ss') : '-',
+ title: t('common.updated_at'),
+ dataIndex: 'updated_at',
+ key: 'updated_at',
+ render: (updatedAt: string) => updatedAt ? formatDateTime(updatedAt, 'YYYY-MM-DD HH:mm:ss') : '-',
},
{
title: t('common.operation'),
diff --git a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx
index 8e6fc875..bebf6ebd 100644
--- a/web/src/views/ApplicationConfig/components/ConfigHeader.tsx
+++ b/web/src/views/ApplicationConfig/components/ConfigHeader.tsx
@@ -220,31 +220,31 @@ const ConfigHeader: FC = ({
/>
diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx
index dba03ab2..3fb7bc93 100644
--- a/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx
+++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/index.tsx
@@ -49,7 +49,7 @@ const FeaturesConfig: FC = ({
?
diff --git a/web/src/views/ApplicationManagement/index.tsx b/web/src/views/ApplicationManagement/index.tsx
index 4d49635c..3b444a3d 100644
--- a/web/src/views/ApplicationManagement/index.tsx
+++ b/web/src/views/ApplicationManagement/index.tsx
@@ -216,7 +216,7 @@ const ApplicationManagement: React.FC = () => {
'rb:text-[#155EEF]': key === 'type',
})}>
{key === 'source' && item.is_shared
- ? t('application.shared')
+ ? item.source_workspace_name
: key === 'source' && !item.is_shared
? t('application.configuration')
: key === 'created_at'
diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx
index 7d1dc773..6069f478 100644
--- a/web/src/views/Conversation/index.tsx
+++ b/web/src/views/Conversation/index.tsx
@@ -65,6 +65,13 @@ const Conversation: FC = () => {
const [isDeepThinking, setIsDeepThinking] = useState>({})
const [thinking, setThinking] = useState(false)
+ useEffect(() => {
+ return () => {
+ audioPollingRef.current.forEach((timer) => clearInterval(timer))
+ audioPollingRef.current.clear()
+ }
+ }, [])
+
useEffect(() => {
const shareToken = localStorage.getItem(`shareToken_${token}`)
setShareToken(shareToken)
@@ -146,13 +153,29 @@ const Conversation: FC = () => {
}
useEffect(() => {
- audioPollingRef.current.forEach((timer) => clearInterval(timer))
- audioPollingRef.current.clear()
if (conversation_id) {
getConversationDetail(token as string, conversation_id)
.then(res => {
const response = res as { messages: ChatItem[] }
- setChatList(response?.messages || [])
+ const messages = response?.messages || []
+ const historyAudioUrls = new Set(messages.map(m => m.meta_data?.audio_url).filter(Boolean))
+ audioPollingRef.current.forEach((timer, key) => {
+ if (!historyAudioUrls.has(key)) {
+ clearInterval(timer)
+ audioPollingRef.current.delete(key)
+ }
+ })
+ messages.forEach(msg => {
+ if (msg.role === 'assistant' && msg.meta_data?.audio_url && msg.meta_data?.audio_status === 'pending') {
+ startAudioPolling(msg.meta_data.audio_url, msg.meta_data.audio_url)
+ }
+ })
+ setChatList(messages.map(msg => {
+ if (msg.role === 'assistant' && msg.meta_data?.audio_url && audioPollingRef.current.has(msg.meta_data.audio_url)) {
+ return { ...msg, meta_data: { ...msg.meta_data, audio_status: 'pending' } }
+ }
+ return msg
+ }))
})
} else {
if (features?.opening_statement?.statement) {
@@ -253,6 +276,28 @@ const Conversation: FC = () => {
}))
}, [audioStatusMap, chatList.length])
+ const startAudioPolling = (audioUrl: string, idToPoll: string) => {
+ if (audioPollingRef.current.has(idToPoll)) return
+ const fileId = audioUrl.split('/').pop()
+ if (!fileId) return
+ const timer = setInterval(() => {
+ getFileStatusById(fileId)
+ .then(res => {
+ const { status } = res as { status: string }
+ if (status && status !== 'pending') {
+ setAudioStatusMap(prev => ({ ...prev, [idToPoll]: status }))
+ clearInterval(audioPollingRef.current.get(idToPoll))
+ audioPollingRef.current.delete(idToPoll)
+ }
+ })
+ .catch(() => {
+ clearInterval(audioPollingRef.current.get(idToPoll))
+ audioPollingRef.current.delete(idToPoll)
+ })
+ }, 2000)
+ audioPollingRef.current.set(idToPoll, timer)
+ }
+
/** Send message and handle streaming response */
const handleSend = (msg?: string) => {
if (!token || !shareToken) return
@@ -316,35 +361,8 @@ const Conversation: FC = () => {
const { file_id } = item.data as { file_id?: string }
const idToPoll = file_id || audio_url || ''
const fileId = audio_url.split('/').pop()
- if (fileId && idToPoll && !audioPollingRef.current.has(idToPoll)) {
-
- const timer = setInterval(() => {
- getFileStatusById(fileId)
- .then(res => {
- const { status } = res as { status: string }
- if (status && status !== 'pending') {
- setAudioStatusMap(prev => ({
- ...prev,
- [idToPoll]: status
- }))
- clearInterval(audioPollingRef.current.get(idToPoll))
- audioPollingRef.current.delete(idToPoll)
- getHistory(true)
- if (currentConversationId && currentConversationId !== conversation_id) {
- setConversationId(currentConversationId)
- }
- }
- })
- .catch(() => {
- clearInterval(audioPollingRef.current.get(idToPoll))
- audioPollingRef.current.delete(idToPoll)
- getHistory(true)
- if (currentConversationId && currentConversationId !== conversation_id) {
- setConversationId(currentConversationId)
- }
- })
- }, 2000)
- audioPollingRef.current.set(idToPoll, timer)
+ if (fileId && idToPoll) {
+ startAudioPolling(audio_url, idToPoll)
}
} else {
getHistory(true)
@@ -356,6 +374,10 @@ const Conversation: FC = () => {
updateAssistantMessage(content, audio_url, undefined, citations)
}
setLoading(false)
+ getHistory(true)
+ if (currentConversationId && currentConversationId !== conversation_id) {
+ setConversationId(currentConversationId)
+ }
break
}
})
diff --git a/web/src/views/InviteRegister/index.tsx b/web/src/views/InviteRegister/index.tsx
index 42cffff1..72ae55e5 100644
--- a/web/src/views/InviteRegister/index.tsx
+++ b/web/src/views/InviteRegister/index.tsx
@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:37:12
* @Last Modified by: ZhaoYing
- * @Last Modified time: 2026-02-04 10:05:39
+ * @Last Modified time: 2026-03-27 22:22:18
*/
/**
* Invite Register Page
@@ -144,7 +144,7 @@ const InviteRegister: React.FC = () => {
}).then((res) => {
const response = res as LoginInfo;
updateLoginInfo(response);
- navigate('/');
+ navigate('/', { replace: true });
}).finally(() => {
setLoading(false);
});
diff --git a/web/src/views/Prompt/index.tsx b/web/src/views/Prompt/index.tsx
index 095a8daa..13c09042 100644
--- a/web/src/views/Prompt/index.tsx
+++ b/web/src/views/Prompt/index.tsx
@@ -243,6 +243,7 @@ const Prompt: FC = () => {
diff --git a/web/src/views/Prompt/pages/History.tsx b/web/src/views/Prompt/pages/History.tsx
index 573b4a90..19c033ed 100644
--- a/web/src/views/Prompt/pages/History.tsx
+++ b/web/src/views/Prompt/pages/History.tsx
@@ -116,13 +116,13 @@ const History: React.FC = () => {
{formatDateTime(item.created_at, 'YYYY/MM/DD HH:mm')}
- handleClick('detail', item)}
>
- handleClick('edit', item)}
>
- handleClick('delete', item)}
>
diff --git a/web/src/views/UserMemoryDetail/components/CommunityNetwork.tsx b/web/src/views/UserMemoryDetail/components/CommunityNetwork.tsx
index 33da0b04..ccfbc14d 100644
--- a/web/src/views/UserMemoryDetail/components/CommunityNetwork.tsx
+++ b/web/src/views/UserMemoryDetail/components/CommunityNetwork.tsx
@@ -65,8 +65,8 @@ const CommunityNetwork: FC<{ onSelectCommunity?: (node: RawCommunityNode) => voi
}, [id])
if (loading) {
- return
-
+ return
+
diff --git a/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx b/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
index 2510aaa9..04391107 100644
--- a/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
+++ b/web/src/views/UserMemoryDetail/pages/ForgetDetail.tsx
@@ -12,6 +12,7 @@ import { Row, Col, Progress, App, Table } from 'antd'
import RbCard from '@/components/RbCard/Card'
import {
getForgetStats,
+ getForgetPendingNodesUrl,
} from '@/api/memory'
import type { ForgetData } from '../types'
import ActivationMetricsPieCard from '../components/ActivationMetricsPieCard'
@@ -19,6 +20,7 @@ import RecentTrendsLineCard from '../components/RecentTrendsLineCard'
import { formatDateTime } from '@/utils/format'
import StatusTag from '@/components/StatusTag'
import ForgetRefreshModal from '../components/ForgetRefreshModal';
+import RbTable from '@/components/Table'
/** Maps node type keys to StatusTag colour presets for the pending-nodes table. */
const statusTagColors: Record = {
@@ -191,7 +193,9 @@ const ForgetDetail = forwardRef((_props, ref) => {
bodyClassName="rb:p-3! rb:py-0! rb:h-[calc(100%-54px)]"
className="rb:h-full!"
>
- {
render: (activation_value) => {activation_value}
},
]}
- pagination={{
- pageSize: 5,
- showQuickJumper: true,
- className: 'rb:mt-5! rb:mb-5.75!'
- }}
className="table-header-has-bg"
/>