Merge pull request #726 from SuanmoSuanyangTechnology/fix/v0.2.9_zy
fix(web): forget memory's pending nodes support page
This commit is contained in:
@@ -154,6 +154,8 @@ export const analyticsRefresh = (end_user_id: string) => {
|
|||||||
export const getForgetStats = (end_user_id: string) => {
|
export const getForgetStats = (end_user_id: string) => {
|
||||||
return request.get(`/memory/forget-memory/stats`, { end_user_id })
|
return request.get(`/memory/forget-memory/stats`, { end_user_id })
|
||||||
}
|
}
|
||||||
|
// 获取带遗忘节点列表
|
||||||
|
export const getForgetPendingNodesUrl = '/memory/forget-memory/pending-nodes'
|
||||||
// Implicit Memory - Preferences
|
// Implicit Memory - Preferences
|
||||||
export const getImplicitPreferences = (end_user_id: string) => {
|
export const getImplicitPreferences = (end_user_id: string) => {
|
||||||
return request.get(`/memory/implicit-memory/preferences/${end_user_id}`)
|
return request.get(`/memory/implicit-memory/preferences/${end_user_id}`)
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ const ChatContent: FC<ChatContentProps> = ({
|
|||||||
const prevDataLengthRef = useRef(data.length);
|
const prevDataLengthRef = useRef(data.length);
|
||||||
const isScrolledToBottomRef = useRef(true);
|
const isScrolledToBottomRef = useRef(true);
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null)
|
const audioRef = useRef<HTMLAudioElement | null>(null)
|
||||||
const [playingIndex, setPlayingIndex] = useState<number | null>(null)
|
const [playingIndex, setPlayingIndex] = useState<string | null>(null)
|
||||||
|
|
||||||
const handlePlay = (index: number, audio_url: string, audio_status?: string) => {
|
const handlePlay = (audio_url: string, audio_status?: string) => {
|
||||||
if (audio_status !== 'completed' && !audio_status) return
|
if (audio_status !== 'completed' && typeof audio_status === 'string') return
|
||||||
if (playingIndex === index) {
|
if (playingIndex === audio_url) {
|
||||||
audioRef.current?.pause()
|
audioRef.current?.pause()
|
||||||
setPlayingIndex(null)
|
setPlayingIndex(null)
|
||||||
return
|
return
|
||||||
@@ -52,7 +52,7 @@ const ChatContent: FC<ChatContentProps> = ({
|
|||||||
const audio = new Audio(audio_url)
|
const audio = new Audio(audio_url)
|
||||||
audioRef.current = audio
|
audioRef.current = audio
|
||||||
audio.play()
|
audio.play()
|
||||||
setPlayingIndex(index)
|
setPlayingIndex(audio_url)
|
||||||
audio.onended = () => setPlayingIndex(null)
|
audio.onended = () => setPlayingIndex(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +85,10 @@ const ChatContent: FC<ChatContentProps> = ({
|
|||||||
// When data array length changes, auto-scroll to bottom
|
// When data array length changes, auto-scroll to bottom
|
||||||
// If already scrolled to bottom, will auto-scroll to bottom
|
// If already scrolled to bottom, will auto-scroll to bottom
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (playingIndex && !data.some(item => item.meta_data?.audio_url === playingIndex)) {
|
||||||
|
audioRef.current?.pause()
|
||||||
|
setPlayingIndex(null)
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (scrollContainerRef.current) {
|
if (scrollContainerRef.current) {
|
||||||
// Auto-scroll if data length changed OR user is currently at bottom
|
// Auto-scroll if data length changed OR user is currently at bottom
|
||||||
@@ -204,16 +208,16 @@ const ChatContent: FC<ChatContentProps> = ({
|
|||||||
{item.meta_data?.audio_url && <>
|
{item.meta_data?.audio_url && <>
|
||||||
<Divider className="rb:my-3!" />
|
<Divider className="rb:my-3!" />
|
||||||
<Space size={12} className="rb:pb-2 rb:pl-1">
|
<Space size={12} className="rb:pb-2 rb:pl-1">
|
||||||
{playingIndex !== index && item.meta_data?.audio_status === 'pending'
|
{playingIndex !== item.meta_data?.audio_url && item.meta_data?.audio_status === 'pending'
|
||||||
? <Spin />
|
? <Spin />
|
||||||
: playingIndex !== index
|
: playingIndex !== item.meta_data?.audio_url
|
||||||
? <SoundOutlined className={clsx("rb:cursor-pointer rb:size-5.5", {
|
? <SoundOutlined className={clsx("rb:cursor-pointer rb:size-5.5", {
|
||||||
'rb:text-[#FF5D34]': item.meta_data?.audio_status === 'error',
|
'rb:text-[#FF5D34]': item.meta_data?.audio_status === 'error',
|
||||||
'rb:hover:text-[#155EEF]!': !item.meta_data?.audio_status || !['pending', 'error'].includes(item.meta_data?.audio_status)
|
'rb:hover:text-[#155EEF]!': !item.meta_data?.audio_status || !['pending', 'error'].includes(item.meta_data?.audio_status)
|
||||||
})} onClick={() => handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)} />
|
})} onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)} />
|
||||||
: <div
|
: <div
|
||||||
className="rb:size-5.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/audio_ing.gif')]"
|
className="rb:size-5.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/audio_ing.gif')]"
|
||||||
onClick={() => handlePlay(index, item.meta_data?.audio_url!, item.meta_data?.audio_status)}
|
onClick={() => handlePlay(item.meta_data?.audio_url!, item.meta_data?.audio_status)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const Statistics: FC = () => {
|
|||||||
className: 'rb:text-[#212332]'
|
className: 'rb:text-[#212332]'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('application.createTime'),
|
title: t('application.created_at'),
|
||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'),
|
render: (createdAt: string) => formatDateTime(createdAt, 'YYYY-MM-DD HH:mm:ss'),
|
||||||
|
|||||||
@@ -220,31 +220,31 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
|
|||||||
/>
|
/>
|
||||||
<Popover content={t('workflow.clear')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('workflow.clear')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/clear.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/clear.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={clear}
|
onClick={clear}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover content={t('workflow.addvariable')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('workflow.addvariable')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/variable.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/variable.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={addvariable}
|
onClick={addvariable}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover content={t('workflow.run')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('workflow.run')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/run.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/run.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={run}
|
onClick={run}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover content={t('workflow.save')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('workflow.save')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/save.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/save.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={save}
|
onClick={save}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover content={t('common.return')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('common.return')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/return.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/return.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={goToApplication}
|
onClick={goToApplication}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const FeaturesConfig: FC<FeaturesConfigProps> = ({
|
|||||||
?
|
?
|
||||||
<Popover content={t('application.features')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
<Popover content={t('application.features')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||||
<div
|
<div
|
||||||
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('src/assets/images/workflow/features.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
className="rb:cursor-pointer rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/features.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat"
|
||||||
onClick={handleFeaturesConfig}
|
onClick={handleFeaturesConfig}
|
||||||
></div>
|
></div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ const ApplicationManagement: React.FC = () => {
|
|||||||
'rb:text-[#155EEF]': key === 'type',
|
'rb:text-[#155EEF]': key === 'type',
|
||||||
})}>
|
})}>
|
||||||
{key === 'source' && item.is_shared
|
{key === 'source' && item.is_shared
|
||||||
? t('application.shared')
|
? item.source_workspace_name
|
||||||
: key === 'source' && !item.is_shared
|
: key === 'source' && !item.is_shared
|
||||||
? t('application.configuration')
|
? t('application.configuration')
|
||||||
: key === 'created_at'
|
: key === 'created_at'
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ const Conversation: FC = () => {
|
|||||||
const [config, setConfig] = useState<Record<string, any>>({})
|
const [config, setConfig] = useState<Record<string, any>>({})
|
||||||
const [audioStatusMap, setAudioStatusMap] = useState<Record<string, string>>({})
|
const [audioStatusMap, setAudioStatusMap] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
audioPollingRef.current.forEach((timer) => clearInterval(timer))
|
||||||
|
audioPollingRef.current.clear()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shareToken = localStorage.getItem(`shareToken_${token}`)
|
const shareToken = localStorage.getItem(`shareToken_${token}`)
|
||||||
setShareToken(shareToken)
|
setShareToken(shareToken)
|
||||||
@@ -144,13 +151,29 @@ const Conversation: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
audioPollingRef.current.forEach((timer) => clearInterval(timer))
|
|
||||||
audioPollingRef.current.clear()
|
|
||||||
if (conversation_id) {
|
if (conversation_id) {
|
||||||
getConversationDetail(token as string, conversation_id)
|
getConversationDetail(token as string, conversation_id)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
const response = res as { messages: ChatItem[] }
|
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 {
|
} else {
|
||||||
if (features?.opening_statement?.statement) {
|
if (features?.opening_statement?.statement) {
|
||||||
@@ -228,6 +251,28 @@ const Conversation: FC = () => {
|
|||||||
}))
|
}))
|
||||||
}, [audioStatusMap, chatList.length])
|
}, [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 */
|
/** Send message and handle streaming response */
|
||||||
const handleSend = (msg?: string) => {
|
const handleSend = (msg?: string) => {
|
||||||
if (!token || !shareToken) return
|
if (!token || !shareToken) return
|
||||||
@@ -287,35 +332,8 @@ const Conversation: FC = () => {
|
|||||||
const { file_id } = item.data as { file_id?: string }
|
const { file_id } = item.data as { file_id?: string }
|
||||||
const idToPoll = file_id || audio_url || ''
|
const idToPoll = file_id || audio_url || ''
|
||||||
const fileId = audio_url.split('/').pop()
|
const fileId = audio_url.split('/').pop()
|
||||||
if (fileId && idToPoll && !audioPollingRef.current.has(idToPoll)) {
|
if (fileId && idToPoll) {
|
||||||
|
startAudioPolling(audio_url, 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)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
getHistory(true)
|
getHistory(true)
|
||||||
@@ -327,6 +345,10 @@ const Conversation: FC = () => {
|
|||||||
updateAssistantMessage(content, audio_url, undefined, citations)
|
updateAssistantMessage(content, audio_url, undefined, citations)
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
getHistory(true)
|
||||||
|
if (currentConversationId && currentConversationId !== conversation_id) {
|
||||||
|
setConversationId(currentConversationId)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -116,13 +116,13 @@ const History: React.FC = () => {
|
|||||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4.5">{formatDateTime(item.created_at, 'YYYY/MM/DD HH:mm')}</div>
|
<div className="rb:text-[12px] rb:text-[#5B6167] rb:leading-4.5">{formatDateTime(item.created_at, 'YYYY/MM/DD HH:mm')}</div>
|
||||||
|
|
||||||
<Space size={8}>
|
<Space size={8}>
|
||||||
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('src/assets/images/prompt/eye.svg')] rb:hover:bg-[url('src/assets/images/prompt/eye_bg.svg')]"
|
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('@/assets/images/prompt/eye.svg')] rb:hover:bg-[url('@/assets/images/prompt/eye_bg.svg')]"
|
||||||
onClick={() => handleClick('detail', item)}
|
onClick={() => handleClick('detail', item)}
|
||||||
></div>
|
></div>
|
||||||
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('src/assets/images/prompt/edit.svg')] rb:hover:bg-[url('src/assets/images/prompt/edit_bg.svg')]"
|
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('@/assets/images/prompt/edit.svg')] rb:hover:bg-[url('@/assets/images/prompt/edit_bg.svg')]"
|
||||||
onClick={() => handleClick('edit', item)}
|
onClick={() => handleClick('edit', item)}
|
||||||
></div>
|
></div>
|
||||||
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('src/assets/images/prompt/delete.svg')] rb:hover:bg-[url('src/assets/images/prompt/delete_hover.svg')]"
|
<div className="rb:size-4.5 rb:bg-cover rb:bg-[url('@/assets/images/prompt/delete.svg')] rb:hover:bg-[url('@/assets/images/prompt/delete_hover.svg')]"
|
||||||
onClick={() => handleClick('delete', item)}
|
onClick={() => handleClick('delete', item)}
|
||||||
></div>
|
></div>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Row, Col, Progress, App, Table } from 'antd'
|
|||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import {
|
import {
|
||||||
getForgetStats,
|
getForgetStats,
|
||||||
|
getForgetPendingNodesUrl,
|
||||||
} from '@/api/memory'
|
} from '@/api/memory'
|
||||||
import type { ForgetData } from '../types'
|
import type { ForgetData } from '../types'
|
||||||
import ActivationMetricsPieCard from '../components/ActivationMetricsPieCard'
|
import ActivationMetricsPieCard from '../components/ActivationMetricsPieCard'
|
||||||
@@ -19,6 +20,7 @@ import RecentTrendsLineCard from '../components/RecentTrendsLineCard'
|
|||||||
import { formatDateTime } from '@/utils/format'
|
import { formatDateTime } from '@/utils/format'
|
||||||
import StatusTag from '@/components/StatusTag'
|
import StatusTag from '@/components/StatusTag'
|
||||||
import ForgetRefreshModal from '../components/ForgetRefreshModal';
|
import ForgetRefreshModal from '../components/ForgetRefreshModal';
|
||||||
|
import RbTable from '@/components/Table'
|
||||||
|
|
||||||
/** Maps node type keys to StatusTag colour presets for the pending-nodes table. */
|
/** Maps node type keys to StatusTag colour presets for the pending-nodes table. */
|
||||||
const statusTagColors: Record<string, 'success' | 'purple' | 'default' | 'warning' | 'error' | 'lightBlue'> = {
|
const statusTagColors: Record<string, 'success' | 'purple' | 'default' | 'warning' | 'error' | 'lightBlue'> = {
|
||||||
@@ -191,7 +193,9 @@ const ForgetDetail = forwardRef((_props, ref) => {
|
|||||||
bodyClassName="rb:p-3! rb:py-0! rb:h-[calc(100%-54px)]"
|
bodyClassName="rb:p-3! rb:py-0! rb:h-[calc(100%-54px)]"
|
||||||
className="rb:h-full!"
|
className="rb:h-full!"
|
||||||
>
|
>
|
||||||
<Table
|
<RbTable
|
||||||
|
apiUrl={getForgetPendingNodesUrl}
|
||||||
|
apiParams={{ end_user_id: id }}
|
||||||
rowKey='node_id'
|
rowKey='node_id'
|
||||||
dataSource={data.pending_nodes ?? []}
|
dataSource={data.pending_nodes ?? []}
|
||||||
columns={[
|
columns={[
|
||||||
@@ -225,11 +229,6 @@ const ForgetDetail = forwardRef((_props, ref) => {
|
|||||||
render: (activation_value) => <span className="rb:text-[#5B6167]">{activation_value}</span>
|
render: (activation_value) => <span className="rb:text-[#5B6167]">{activation_value}</span>
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
pagination={{
|
|
||||||
pageSize: 5,
|
|
||||||
showQuickJumper: true,
|
|
||||||
className: 'rb:mt-5! rb:mb-5.75!'
|
|
||||||
}}
|
|
||||||
className="table-header-has-bg"
|
className="table-header-has-bg"
|
||||||
/>
|
/>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|||||||
Reference in New Issue
Block a user