2026-03-31 04:31:47 +00:00
import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
import axios from 'axios' ;
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider' ;
import { APP_ENDPOINTS } from '../../../config/env' ;
2026-03-31 06:56:31 +00:00
import { sortBotsByCreatedAtDesc } from '../../dashboard/utils' ;
2026-03-31 04:31:47 +00:00
import { useAppStore } from '../../../store/appStore' ;
import type { BotState } from '../../../types/bot' ;
import {
normalizePlatformPageSize ,
readCachedPlatformPageSize ,
writeCachedPlatformPageSize ,
} from '../../../utils/platformPageSize' ;
import type {
2026-04-02 05:00:15 +00:00
BotActivityStatsItem ,
2026-03-31 04:31:47 +00:00
PlatformBotResourceSnapshot ,
PlatformOverviewResponse ,
PlatformSettings ,
PlatformUsageAnalyticsSeriesItem ,
PlatformUsageResponse ,
} from '../types' ;
import {
buildBotPanelHref ,
buildPlatformUsageAnalyticsSeries ,
buildPlatformUsageAnalyticsTicks ,
clampPlatformPercent ,
getPlatformChartCeiling ,
} from '../utils' ;
interface UsePlatformDashboardOptions {
compactMode : boolean ;
}
export function usePlatformDashboard ( { compactMode } : UsePlatformDashboardOptions ) {
const { activeBots , setBots , updateBotStatus , locale } = useAppStore ( ) ;
const { notify , confirm } = useLucentPrompt ( ) ;
const isZh = locale === 'zh' ;
const [ overview , setOverview ] = useState < PlatformOverviewResponse | null > ( null ) ;
const [ loading , setLoading ] = useState ( false ) ;
const [ selectedBotId , setSelectedBotId ] = useState ( '' ) ;
const [ search , setSearch ] = useState ( '' ) ;
const [ operatingBotId , setOperatingBotId ] = useState ( '' ) ;
const [ showImageFactory , setShowImageFactory ] = useState ( false ) ;
const [ showTemplateManager , setShowTemplateManager ] = useState ( false ) ;
const [ showPlatformSettings , setShowPlatformSettings ] = useState ( false ) ;
const [ showBotLastActionModal , setShowBotLastActionModal ] = useState ( false ) ;
const [ showResourceModal , setShowResourceModal ] = useState ( false ) ;
const [ selectedBotDetail , setSelectedBotDetail ] = useState < BotState | null > ( null ) ;
const [ selectedBotUsageSummary , setSelectedBotUsageSummary ] = useState < PlatformUsageResponse [ ' summary ' ] | null > ( null ) ;
const [ resourceBotId , setResourceBotId ] = useState ( '' ) ;
const [ resourceSnapshot , setResourceSnapshot ] = useState < PlatformBotResourceSnapshot | null > ( null ) ;
const [ resourceLoading , setResourceLoading ] = useState ( false ) ;
const [ resourceError , setResourceError ] = useState ( '' ) ;
const [ usageData , setUsageData ] = useState < PlatformUsageResponse | null > ( null ) ;
const [ usageLoading , setUsageLoading ] = useState ( false ) ;
2026-04-02 05:00:15 +00:00
const [ activityStatsData , setActivityStatsData ] = useState < BotActivityStatsItem [ ] | null > ( null ) ;
const [ activityLoading , setActivityLoading ] = useState ( false ) ;
2026-03-31 04:31:47 +00:00
const [ usagePage , setUsagePage ] = useState ( 1 ) ;
const [ usagePageSize , setUsagePageSize ] = useState ( ( ) = > readCachedPlatformPageSize ( 10 ) ) ;
const [ pageSizeReady , setPageSizeReady ] = useState ( true ) ;
const [ botListPage , setBotListPage ] = useState ( 1 ) ;
const [ botListPageSize , setBotListPageSize ] = useState ( ( ) = > readCachedPlatformPageSize ( 10 ) ) ;
const [ showCompactBotSheet , setShowCompactBotSheet ] = useState ( false ) ;
const [ compactSheetClosing , setCompactSheetClosing ] = useState ( false ) ;
const [ compactSheetMounted , setCompactSheetMounted ] = useState ( false ) ;
const compactSheetTimerRef = useRef < number | null > ( null ) ;
const botList = useMemo ( ( ) = > {
2026-03-31 06:56:31 +00:00
return sortBotsByCreatedAtDesc ( Object . values ( activeBots ) ) as BotState [ ] ;
} , [ activeBots ] ) ;
2026-03-31 04:31:47 +00:00
const filteredBots = useMemo ( ( ) = > {
const keyword = search . trim ( ) . toLowerCase ( ) ;
if ( ! keyword ) return botList ;
return botList . filter ( ( bot ) = > ` ${ bot . name } ${ bot . id } ` . toLowerCase ( ) . includes ( keyword ) ) ;
} , [ botList , search ] ) ;
const botListPageCount = useMemo (
( ) = > Math . max ( 1 , Math . ceil ( filteredBots . length / botListPageSize ) ) ,
[ filteredBots . length , botListPageSize ] ,
) ;
const pagedBots = useMemo ( ( ) = > {
const page = Math . min ( Math . max ( 1 , botListPage ) , botListPageCount ) ;
const start = ( page - 1 ) * botListPageSize ;
return filteredBots . slice ( start , start + botListPageSize ) ;
} , [ filteredBots , botListPage , botListPageCount , botListPageSize ] ) ;
const selectedBot = useMemo (
( ) = > ( selectedBotId ? botList . find ( ( bot ) = > bot . id === selectedBotId ) : undefined ) ,
[ botList , selectedBotId ] ,
) ;
const loadBots = useCallback ( async ( ) = > {
const res = await axios . get < BotState [ ] > ( ` ${ APP_ENDPOINTS . apiBase } /bots ` ) ;
setBots ( res . data ) ;
} , [ setBots ] ) ;
const loadOverview = useCallback ( async ( ) = > {
setLoading ( true ) ;
try {
const res = await axios . get < PlatformOverviewResponse > ( ` ${ APP_ENDPOINTS . apiBase } /platform/overview ` ) ;
setOverview ( res . data ) ;
const normalizedPageSize = normalizePlatformPageSize (
res . data ? . settings ? . page_size ,
readCachedPlatformPageSize ( 10 ) ,
) ;
writeCachedPlatformPageSize ( normalizedPageSize ) ;
setUsagePageSize ( normalizedPageSize ) ;
setBotListPageSize ( normalizedPageSize ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '读取平台总览失败。' : 'Failed to load platform overview.' ) , { tone : 'error' } ) ;
} finally {
setPageSizeReady ( true ) ;
setLoading ( false ) ;
}
} , [ isZh , notify ] ) ;
const loadUsage = useCallback ( async ( page = 1 ) = > {
setUsageLoading ( true ) ;
try {
const res = await axios . get < PlatformUsageResponse > ( ` ${ APP_ENDPOINTS . apiBase } /platform/usage ` , {
params : {
limit : usagePageSize ,
offset : Math.max ( 0 , page - 1 ) * usagePageSize ,
} ,
} ) ;
setUsageData ( res . data ) ;
setUsagePage ( page ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '读取用量统计失败。' : 'Failed to load usage analytics.' ) , { tone : 'error' } ) ;
} finally {
setUsageLoading ( false ) ;
}
} , [ isZh , notify , usagePageSize ] ) ;
2026-04-02 05:00:15 +00:00
const loadActivityStats = useCallback ( async ( ) = > {
setActivityLoading ( true ) ;
try {
const res = await axios . get < { items : BotActivityStatsItem [ ] } > ( ` ${ APP_ENDPOINTS . apiBase } /platform/activity-stats ` ) ;
setActivityStatsData ( Array . isArray ( res . data ? . items ) ? res . data . items : [ ] ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '读取 Bot 活跃度统计失败。' : 'Failed to load bot activity analytics.' ) , { tone : 'error' } ) ;
} finally {
setActivityLoading ( false ) ;
}
} , [ isZh , notify ] ) ;
2026-03-31 04:31:47 +00:00
const loadSelectedBotUsageSummary = useCallback ( async ( botId : string ) = > {
if ( ! botId ) {
setSelectedBotUsageSummary ( null ) ;
return ;
}
try {
const res = await axios . get < PlatformUsageResponse > ( ` ${ APP_ENDPOINTS . apiBase } /platform/usage ` , {
params : {
bot_id : botId ,
limit : 1 ,
offset : 0 ,
} ,
} ) ;
setSelectedBotUsageSummary ( res . data ? . summary || null ) ;
} catch {
setSelectedBotUsageSummary ( null ) ;
}
} , [ ] ) ;
const loadResourceSnapshot = useCallback ( async ( botId : string ) = > {
if ( ! botId ) return ;
setResourceLoading ( true ) ;
setResourceError ( '' ) ;
try {
const res = await axios . get < PlatformBotResourceSnapshot > ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ encodeURIComponent ( botId ) } /resources ` ) ;
setResourceSnapshot ( res . data ) ;
} catch ( error : any ) {
const msg = error ? . response ? . data ? . detail || ( isZh ? '读取资源监控失败。' : 'Failed to load resource metrics.' ) ;
setResourceError ( String ( msg ) ) ;
} finally {
setResourceLoading ( false ) ;
}
} , [ isZh ] ) ;
useEffect ( ( ) = > {
void loadOverview ( ) ;
2026-03-31 06:56:31 +00:00
} , [ loadOverview ] ) ;
2026-03-31 04:31:47 +00:00
useEffect ( ( ) = > {
if ( ! pageSizeReady ) return ;
void loadUsage ( 1 ) ;
} , [ loadUsage , pageSizeReady , usagePageSize ] ) ;
2026-04-02 05:00:15 +00:00
useEffect ( ( ) = > {
void loadActivityStats ( ) ;
} , [ loadActivityStats ] ) ;
2026-03-31 04:31:47 +00:00
useEffect ( ( ) = > {
setBotListPage ( 1 ) ;
} , [ search , botListPageSize ] ) ;
useEffect ( ( ) = > {
setBotListPage ( ( prev ) = > Math . min ( Math . max ( prev , 1 ) , botListPageCount ) ) ;
} , [ botListPageCount ] ) ;
useEffect ( ( ) = > {
if ( ! selectedBotId && filteredBots [ 0 ] ? . id ) setSelectedBotId ( filteredBots [ 0 ] . id ) ;
} , [ filteredBots , selectedBotId ] ) ;
useEffect ( ( ) = > {
if ( ! compactMode ) {
setShowCompactBotSheet ( false ) ;
setCompactSheetClosing ( false ) ;
setCompactSheetMounted ( false ) ;
return ;
}
if ( selectedBotId && showCompactBotSheet ) return ;
if ( ! selectedBotId ) setShowCompactBotSheet ( false ) ;
} , [ compactMode , selectedBotId , showCompactBotSheet ] ) ;
useEffect ( ( ) = > {
if ( ! selectedBotId ) {
setSelectedBotDetail ( null ) ;
setSelectedBotUsageSummary ( null ) ;
return ;
}
let alive = true ;
void ( async ( ) = > {
try {
const res = await axios . get < BotState > ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ encodeURIComponent ( selectedBotId ) } ` ) ;
if ( alive ) {
setSelectedBotDetail ( res . data ) ;
}
} catch {
if ( alive ) {
setSelectedBotDetail ( null ) ;
}
}
} ) ( ) ;
void loadSelectedBotUsageSummary ( selectedBotId ) ;
return ( ) = > {
alive = false ;
} ;
} , [ loadSelectedBotUsageSummary , selectedBotId ] ) ;
const resourceBot = useMemo (
( ) = > ( resourceBotId ? botList . find ( ( bot ) = > bot . id === resourceBotId ) : undefined ) ,
[ botList , resourceBotId ] ,
) ;
const selectedBotInfo = useMemo ( ( ) = > {
if ( selectedBotDetail && selectedBotDetail . id === selectedBotId ) {
return {
. . . selectedBot ,
. . . selectedBotDetail ,
logs : ( selectedBotDetail . logs && selectedBotDetail . logs . length > 0 )
? selectedBotDetail . logs
: ( selectedBot ? . logs || [ ] ) ,
messages : ( selectedBotDetail . messages && selectedBotDetail . messages . length > 0 )
? selectedBotDetail . messages
: ( selectedBot ? . messages || [ ] ) ,
events : ( selectedBotDetail . events && selectedBotDetail . events . length > 0 )
? selectedBotDetail . events
: ( selectedBot ? . events || [ ] ) ,
} as BotState ;
}
return selectedBot ;
} , [ selectedBot , selectedBotDetail , selectedBotId ] ) ;
const lastActionPreview = useMemo (
( ) = > selectedBotInfo ? . last_action ? . trim ( ) || '' ,
[ selectedBotInfo ? . last_action ] ,
) ;
const overviewBots = overview ? . summary . bots ;
const overviewImages = overview ? . summary . images ;
const overviewResources = overview ? . summary . resources ;
2026-04-02 05:00:15 +00:00
const activityStats = activityStatsData || overview ? . activity_stats ;
2026-03-31 04:31:47 +00:00
const usageSummary = usageData ? . summary || overview ? . usage . summary ;
const usageAnalytics = usageData ? . analytics || overview ? . usage . analytics || null ;
const memoryPercent =
overviewResources && overviewResources . live_memory_limit_bytes > 0
? clampPlatformPercent ( ( overviewResources . live_memory_used_bytes / overviewResources . live_memory_limit_bytes ) * 100 )
: 0 ;
const storagePercent =
overviewResources && overviewResources . workspace_limit_bytes > 0
? clampPlatformPercent ( ( overviewResources . workspace_used_bytes / overviewResources . workspace_limit_bytes ) * 100 )
: 0 ;
const usageAnalyticsSeries = useMemo < PlatformUsageAnalyticsSeriesItem [ ] > (
( ) = > buildPlatformUsageAnalyticsSeries ( usageAnalytics , isZh ) ,
[ isZh , usageAnalytics ] ,
) ;
const usageAnalyticsMax = useMemo ( ( ) = > {
const maxDailyRequests = usageAnalyticsSeries . reduce (
( max , item ) = > Math . max ( max , . . . item . daily_counts . map ( ( count ) = > Number ( count || 0 ) ) ) ,
0 ,
) ;
return getPlatformChartCeiling ( maxDailyRequests ) ;
} , [ usageAnalyticsSeries ] ) ;
const usageAnalyticsTicks = useMemo ( ( ) = > buildPlatformUsageAnalyticsTicks ( usageAnalyticsMax ) , [ usageAnalyticsMax ] ) ;
const refreshAll = useCallback ( async ( ) = > {
2026-04-02 05:00:15 +00:00
const jobs : Promise < unknown > [ ] = [ loadOverview ( ) , loadBots ( ) , loadUsage ( usagePage ) , loadActivityStats ( ) ] ;
2026-03-31 04:31:47 +00:00
if ( selectedBotId ) jobs . push ( loadSelectedBotUsageSummary ( selectedBotId ) ) ;
await Promise . allSettled ( jobs ) ;
2026-04-02 05:00:15 +00:00
} , [ loadActivityStats , loadBots , loadOverview , loadSelectedBotUsageSummary , loadUsage , selectedBotId , usagePage ] ) ;
2026-03-31 04:31:47 +00:00
const toggleBot = useCallback ( async ( bot : BotState ) = > {
setOperatingBotId ( bot . id ) ;
try {
if ( bot . docker_status === 'RUNNING' ) {
await axios . post ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ bot . id } /stop ` ) ;
updateBotStatus ( bot . id , 'STOPPED' ) ;
} else {
await axios . post ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ bot . id } /start ` ) ;
updateBotStatus ( bot . id , 'RUNNING' ) ;
}
await refreshAll ( ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? 'Bot 操作失败。' : 'Bot action failed.' ) , { tone : 'error' } ) ;
} finally {
setOperatingBotId ( '' ) ;
}
} , [ isZh , notify , refreshAll , updateBotStatus ] ) ;
const setBotEnabled = useCallback ( async ( bot : BotState , enabled : boolean ) = > {
setOperatingBotId ( bot . id ) ;
try {
await axios . post ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ bot . id } / ${ enabled ? 'enable' : 'disable' } ` ) ;
await refreshAll ( ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '更新 Bot 状态失败。' : 'Failed to update bot status.' ) , { tone : 'error' } ) ;
} finally {
setOperatingBotId ( '' ) ;
}
} , [ isZh , notify , refreshAll ] ) ;
const removeBot = useCallback ( async ( bot : BotState ) = > {
const targetId = String ( bot . id || '' ) . trim ( ) ;
if ( ! targetId ) return ;
const ok = await confirm ( {
title : isZh ? '删除 Bot' : 'Delete Bot' ,
message : isZh ? ` 确认删除 Bot ${ targetId } ?将删除对应 workspace。 ` : ` Delete Bot ${ targetId } ? Its workspace will also be removed. ` ,
tone : 'warning' ,
} ) ;
if ( ! ok ) return ;
setOperatingBotId ( targetId ) ;
try {
await axios . delete ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ encodeURIComponent ( targetId ) } ` , {
params : { delete_workspace : true } ,
} ) ;
if ( selectedBotId === targetId ) {
setSelectedBotId ( '' ) ;
setSelectedBotDetail ( null ) ;
setShowBotLastActionModal ( false ) ;
}
await refreshAll ( ) ;
notify ( isZh ? 'Bot 已删除。' : 'Bot deleted.' , { tone : 'success' } ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '删除 Bot 失败。' : 'Failed to delete bot.' ) , { tone : 'error' } ) ;
} finally {
setOperatingBotId ( '' ) ;
}
} , [ confirm , isZh , notify , refreshAll , selectedBotId ] ) ;
const clearDashboardDirectSession = useCallback ( async ( bot : BotState ) = > {
const targetId = String ( bot . id || '' ) . trim ( ) ;
if ( ! targetId ) return ;
const ok = await confirm ( {
title : isZh ? '清除面板 Session' : 'Clear Dashboard Session' ,
message : isZh
? ` 确认清空 Bot ${ targetId } 的 dashboard_direct.jsonl 内容? \ n \ n这会重置面板对话上下文; 若 Bot 正在运行,还会同步切到新会话。 `
: ` Clear dashboard_direct.jsonl for Bot ${ targetId } ? \ n \ nThis resets the dashboard conversation context. If the bot is running, it will also switch to a fresh session. ` ,
tone : 'warning' ,
confirmText : isZh ? '清除' : 'Clear' ,
} ) ;
if ( ! ok ) return ;
setOperatingBotId ( targetId ) ;
try {
await axios . post ( ` ${ APP_ENDPOINTS . apiBase } /bots/ ${ encodeURIComponent ( targetId ) } /sessions/dashboard-direct/clear ` ) ;
notify ( isZh ? '面板 Session 已清空。' : 'Dashboard session cleared.' , { tone : 'success' } ) ;
await refreshAll ( ) ;
} catch ( error : any ) {
notify ( error ? . response ? . data ? . detail || ( isZh ? '清空面板 Session 失败。' : 'Failed to clear dashboard session.' ) , { tone : 'error' } ) ;
} finally {
setOperatingBotId ( '' ) ;
}
} , [ confirm , isZh , notify , refreshAll ] ) ;
const openResourceMonitor = useCallback ( ( botId : string ) = > {
setResourceBotId ( botId ) ;
setShowResourceModal ( true ) ;
void loadResourceSnapshot ( botId ) ;
} , [ loadResourceSnapshot ] ) ;
useEffect ( ( ) = > {
if ( compactMode && showCompactBotSheet && selectedBotInfo ) {
if ( compactSheetTimerRef . current ) {
window . clearTimeout ( compactSheetTimerRef . current ) ;
compactSheetTimerRef . current = null ;
}
setCompactSheetMounted ( true ) ;
setCompactSheetClosing ( false ) ;
return ;
}
if ( ! compactSheetMounted ) return ;
setCompactSheetClosing ( true ) ;
compactSheetTimerRef . current = window . setTimeout ( ( ) = > {
setCompactSheetMounted ( false ) ;
setCompactSheetClosing ( false ) ;
compactSheetTimerRef . current = null ;
} , 240 ) ;
return ( ) = > {
if ( compactSheetTimerRef . current ) {
window . clearTimeout ( compactSheetTimerRef . current ) ;
compactSheetTimerRef . current = null ;
}
} ;
} , [ compactMode , compactSheetMounted , selectedBotInfo , showCompactBotSheet ] ) ;
useEffect ( ( ) = > {
if ( ! showResourceModal || ! resourceBotId ) return ;
let stopped = false ;
const tick = async ( ) = > {
if ( stopped ) return ;
await loadResourceSnapshot ( resourceBotId ) ;
} ;
const timer = window . setInterval ( ( ) = > {
void tick ( ) ;
} , 2000 ) ;
return ( ) = > {
stopped = true ;
window . clearInterval ( timer ) ;
} ;
} , [ loadResourceSnapshot , resourceBotId , showResourceModal ] ) ;
const handleSelectBot = useCallback ( ( botId : string ) = > {
setSelectedBotId ( botId ) ;
if ( compactMode ) setShowCompactBotSheet ( true ) ;
} , [ compactMode ] ) ;
const closeCompactBotSheet = useCallback ( ( ) = > setShowCompactBotSheet ( false ) , [ ] ) ;
const openBotPanel = useCallback ( ( botId : string ) = > {
if ( ! botId || typeof window === 'undefined' ) return ;
window . open ( buildBotPanelHref ( botId ) , '_blank' , 'noopener,noreferrer' ) ;
} , [ ] ) ;
const handlePlatformSettingsSaved = useCallback ( ( settings : PlatformSettings ) = > {
setOverview ( ( prev ) = > ( prev ? { . . . prev , settings } : prev ) ) ;
const normalizedPageSize = normalizePlatformPageSize ( settings . page_size , 10 ) ;
writeCachedPlatformPageSize ( normalizedPageSize ) ;
setUsagePageSize ( normalizedPageSize ) ;
setBotListPageSize ( normalizedPageSize ) ;
} , [ ] ) ;
const closeResourceModal = useCallback ( ( ) = > setShowResourceModal ( false ) , [ ] ) ;
return {
botList ,
botListPage ,
botListPageCount ,
botListPageSize ,
closeCompactBotSheet ,
closeResourceModal ,
clearDashboardDirectSession ,
compactSheetClosing ,
compactSheetMounted ,
filteredBots ,
handlePlatformSettingsSaved ,
handleSelectBot ,
isZh ,
lastActionPreview ,
loadResourceSnapshot ,
loading ,
memoryPercent ,
openBotPanel ,
openResourceMonitor ,
operatingBotId ,
overview ,
overviewBots ,
overviewImages ,
overviewResources ,
pageSizeReady ,
pagedBots ,
refreshAll ,
removeBot ,
resourceBot ,
resourceBotId ,
resourceError ,
resourceLoading ,
resourceSnapshot ,
search ,
selectedBotId ,
selectedBotInfo ,
selectedBotUsageSummary ,
setBotEnabled ,
setBotListPage ,
setSearch ,
setShowBotLastActionModal ,
setShowImageFactory ,
setShowPlatformSettings ,
setShowTemplateManager ,
showBotLastActionModal ,
showCompactBotSheet ,
showImageFactory ,
showPlatformSettings ,
showResourceModal ,
showTemplateManager ,
storagePercent ,
toggleBot ,
usageAnalytics ,
2026-04-02 04:14:08 +00:00
activityStats ,
2026-04-02 05:00:15 +00:00
activityLoading ,
2026-03-31 04:31:47 +00:00
usageAnalyticsMax ,
usageAnalyticsSeries ,
usageAnalyticsTicks ,
usageData ,
usageLoading ,
usagePage ,
usageSummary ,
} ;
}