513 lines
18 KiB
TypeScript
513 lines
18 KiB
TypeScript
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||
|
|
import axios from 'axios';
|
|||
|
|
|
|||
|
|
import { useLucentPrompt } from '../../../components/lucent/LucentPromptProvider';
|
|||
|
|
import { APP_ENDPOINTS } from '../../../config/env';
|
|||
|
|
import { useAppStore } from '../../../store/appStore';
|
|||
|
|
import type { BotState } from '../../../types/bot';
|
|||
|
|
import {
|
|||
|
|
normalizePlatformPageSize,
|
|||
|
|
readCachedPlatformPageSize,
|
|||
|
|
writeCachedPlatformPageSize,
|
|||
|
|
} from '../../../utils/platformPageSize';
|
|||
|
|
import type {
|
|||
|
|
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);
|
|||
|
|
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(() => {
|
|||
|
|
const overviewBots = overview?.bots || [];
|
|||
|
|
if (overviewBots.length > 0) {
|
|||
|
|
return overviewBots.map((bot) => ({
|
|||
|
|
...(activeBots[bot.id] || { logs: [], messages: [], events: [] }),
|
|||
|
|
...bot,
|
|||
|
|
})) as BotState[];
|
|||
|
|
}
|
|||
|
|
return Object.values(activeBots);
|
|||
|
|
}, [activeBots, overview?.bots]);
|
|||
|
|
|
|||
|
|
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]);
|
|||
|
|
|
|||
|
|
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();
|
|||
|
|
void loadBots();
|
|||
|
|
}, [loadBots, loadOverview]);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (!pageSizeReady) return;
|
|||
|
|
void loadUsage(1);
|
|||
|
|
}, [loadUsage, pageSizeReady, usagePageSize]);
|
|||
|
|
|
|||
|
|
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;
|
|||
|
|
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 () => {
|
|||
|
|
const jobs: Promise<unknown>[] = [loadOverview(), loadBots(), loadUsage(usagePage)];
|
|||
|
|
if (selectedBotId) jobs.push(loadSelectedBotUsageSummary(selectedBotId));
|
|||
|
|
await Promise.allSettled(jobs);
|
|||
|
|
}, [loadBots, loadOverview, loadSelectedBotUsageSummary, loadUsage, selectedBotId, usagePage]);
|
|||
|
|
|
|||
|
|
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,
|
|||
|
|
usageAnalyticsMax,
|
|||
|
|
usageAnalyticsSeries,
|
|||
|
|
usageAnalyticsTicks,
|
|||
|
|
usageData,
|
|||
|
|
usageLoading,
|
|||
|
|
usagePage,
|
|||
|
|
usageSummary,
|
|||
|
|
};
|
|||
|
|
}
|