v0.1.4-p4

main
mula.liu 2026-04-01 15:19:14 +08:00
parent d196e57804
commit ad59bc3794
10 changed files with 6627 additions and 981 deletions

View File

@ -1,22 +0,0 @@
import os
def fix_sf(path):
with open(path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if "from services.bot_service import _skills_root" in line: continue
if "from services.bot_service import _workspace_root" in line: continue
new_lines.append(line)
# Add corrected imports at the top
if path.endswith("skill_service.py"):
new_lines.insert(20, "from services.bot_service import _skills_root, _workspace_root\n")
elif path.endswith("workspace_service.py"):
new_lines.insert(20, "from services.bot_service import _workspace_root\n")
with open(path, 'w') as f:
f.writelines(new_lines)
# Wait, if _skills_root is defined in skill_service.py, it shouldn't be imported from bot_service.
# Let's check where it IS defined.

5989
frontend/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,6 @@ import { getAppRouteMeta, navigateToRoute, readCompactModeFromUrl, useAppRoute,
import './components/ui/SharedUi.css'; import './components/ui/SharedUi.css';
import './App.css'; import './App.css';
import './App.h5.css'; import './App.h5.css';
import './modules/platform/PlatformDashboardPage.css';
const defaultLoadingTitle = 'Dashboard Nanobot'; const defaultLoadingTitle = 'Dashboard Nanobot';

View File

@ -127,21 +127,22 @@
padding: 10px 10px 10px 14px; padding: 10px 10px 10px 14px;
margin-bottom: 10px; margin-bottom: 10px;
cursor: pointer; cursor: pointer;
transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
} }
.ops-bot-card:hover { .ops-bot-card:hover {
border-color: color-mix(in oklab, var(--brand) 58%, var(--line) 42%); border-color: color-mix(in oklab, var(--brand) 58%, var(--line) 42%);
box-shadow: 0 8px 18px color-mix(in oklab, var(--brand) 14%, transparent); box-shadow: 0 8px 24px color-mix(in oklab, var(--brand) 12%, transparent);
transform: translateY(-1px);
} }
.ops-bot-card.is-active { .ops-bot-card.is-active {
border-color: color-mix(in oklab, var(--brand) 80%, var(--line) 20%); border-color: var(--brand);
background: color-mix(in oklab, var(--brand) 4%, var(--panel-soft));
box-shadow: box-shadow:
0 0 0 2px color-mix(in oklab, var(--brand) 70%, transparent), 0 0 0 2px color-mix(in oklab, var(--brand) 30%, transparent),
0 16px 30px color-mix(in oklab, var(--brand) 28%, transparent), 0 12px 28px color-mix(in oklab, var(--brand) 18%, transparent);
inset 0 0 0 1px color-mix(in oklab, var(--brand) 84%, transparent);
transform: translateY(0);
z-index: 2; z-index: 2;
} }
@ -150,20 +151,27 @@
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 12px; border-radius: 12px;
border: 2px solid color-mix(in oklab, var(--brand) 78%, transparent); border: 2px solid var(--brand);
pointer-events: none; pointer-events: none;
opacity: 0.8;
} }
.ops-bot-card.state-running { .ops-bot-card.state-running {
background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 14%, var(--panel-soft) 86%), color-mix(in oklab, var(--ok) 8%, var(--panel) 92%)); background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 8%, var(--panel-soft) 92%), color-mix(in oklab, var(--ok) 4%, var(--panel) 96%));
}
.ops-bot-card.state-running.is-active {
background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 12%, var(--brand) 4%), color-mix(in oklab, var(--ok) 6%, var(--panel) 94%));
} }
.ops-bot-card.state-stopped { .ops-bot-card.state-stopped {
background: linear-gradient(145deg, color-mix(in oklab, #b79aa2 14%, var(--panel-soft) 86%), color-mix(in oklab, #b79aa2 7%, var(--panel) 93%)); background: linear-gradient(145deg, color-mix(in oklab, var(--err) 8%, var(--panel-soft) 92%), color-mix(in oklab, var(--err) 4%, var(--panel) 96%));
} }
.ops-bot-card.state-disabled { .ops-bot-card.state-disabled {
background: linear-gradient(145deg, color-mix(in oklab, #9ca3b5 14%, var(--panel-soft) 86%), color-mix(in oklab, #9ca3b5 7%, var(--panel) 93%)); opacity: 0.72;
filter: grayscale(0.2);
background: var(--panel-soft);
} }
.ops-bot-top { .ops-bot-top {
@ -220,7 +228,7 @@
border-radius: 999px; border-radius: 999px;
border: 1px solid color-mix(in oklab, var(--line) 82%, transparent); border: 1px solid color-mix(in oklab, var(--line) 82%, transparent);
background: color-mix(in oklab, #9ca3b5 42%, var(--panel-soft) 58%); background: color-mix(in oklab, #9ca3b5 42%, var(--panel-soft) 58%);
transition: background 0.2s ease, border-color 0.2s ease; transition: all 0.2s ease;
} }
.ops-bot-enable-switch-track::after { .ops-bot-enable-switch-track::after {
@ -232,7 +240,7 @@
height: 14px; height: 14px;
border-radius: 999px; border-radius: 999px;
background: color-mix(in oklab, var(--text) 75%, #fff 25%); background: color-mix(in oklab, var(--text) 75%, #fff 25%);
transition: transform 0.2s ease, background 0.2s ease; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.ops-bot-enable-switch input:checked + .ops-bot-enable-switch-track { .ops-bot-enable-switch input:checked + .ops-bot-enable-switch-track {
@ -253,24 +261,27 @@
.ops-bot-strip { .ops-bot-strip {
position: absolute; position: absolute;
left: 0; left: 0;
top: 8px; top: 0;
bottom: 8px; bottom: 0;
width: 3px; width: 4px;
border-radius: 999px;
background: color-mix(in oklab, var(--line) 80%, transparent); background: color-mix(in oklab, var(--line) 80%, transparent);
opacity: 0.7; opacity: 0.8;
transition: all 0.25s ease;
} }
.ops-bot-strip.is-running { .ops-bot-strip.is-running {
background: linear-gradient(180deg, color-mix(in oklab, var(--ok) 80%, #9be8c6 20%), color-mix(in oklab, var(--ok) 54%, transparent)); background: var(--ok);
box-shadow: 2px 0 8px color-mix(in oklab, var(--ok) 40%, transparent);
} }
.ops-bot-strip.is-stopped { .ops-bot-strip.is-stopped {
background: linear-gradient(180deg, color-mix(in oklab, var(--err) 74%, #e7b1ba 26%), color-mix(in oklab, var(--err) 54%, transparent)); background: var(--err);
opacity: 0.6;
} }
.ops-bot-strip.is-disabled { .ops-bot-strip.is-disabled {
background: linear-gradient(180deg, color-mix(in oklab, #9ca3b5 82%, #d4d9e2 18%), color-mix(in oklab, #9ca3b5 52%, transparent)); background: #9ca3b5;
opacity: 0.4;
} }
.ops-bot-icon-btn { .ops-bot-icon-btn {
@ -281,6 +292,7 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
} }
.ops-bot-icon-btn svg { .ops-bot-icon-btn svg {
@ -377,42 +389,45 @@
} }
.ops-control-pending { .ops-control-pending {
display: inline-flex; display: flex;
align-items: center; align-items: center;
gap: 6px; justify-content: center;
width: 100%;
height: 100%;
} }
.ops-control-dots { .ops-control-dots {
display: inline-flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
gap: 3px; gap: 3px;
} }
.ops-control-dots i { .ops-control-dots i {
width: 4px; width: 5px;
height: 4px; height: 5px;
border-radius: 999px; border-radius: 50%;
display: block; display: block;
background: currentColor; background: white;
opacity: 0.35; opacity: 1;
animation: ops-control-dot 1.2s infinite ease-in-out; animation: ops-control-dot 1s infinite ease-in-out;
} }
.ops-control-dots i:nth-child(2) { .ops-control-dots i:nth-child(2) {
animation-delay: 0.2s; animation-delay: 0.15s;
} }
.ops-control-dots i:nth-child(3) { .ops-control-dots i:nth-child(3) {
animation-delay: 0.4s; animation-delay: 0.3s;
} }
@keyframes ops-control-dot { @keyframes ops-control-dot {
0%, 80%, 100% { 0%, 100% {
transform: translateY(0); transform: scale(0.8);
opacity: 0.35; opacity: 0.4;
} }
40% { 50% {
transform: translateY(-2px); transform: scale(1.2);
opacity: 1; opacity: 1;
} }
} }

View File

@ -220,7 +220,7 @@ export function BotListPanel({
const isEnabling = controlState === 'enabling'; const isEnabling = controlState === 'enabling';
const isDisabling = controlState === 'disabling'; const isDisabling = controlState === 'disabling';
const isRunning = String(bot.docker_status || '').toUpperCase() === 'RUNNING'; const isRunning = String(bot.docker_status || '').toUpperCase() === 'RUNNING';
const showActionPending = isStarting || isStopping; const showActionPending = isStarting || isStopping || isEnabling || isDisabling;
return ( return (
<div <div
key={bot.id} key={bot.id}

View File

@ -51,7 +51,7 @@ export function useDashboardRuntimeControl({
notify, notify,
confirm, confirm,
}: UseDashboardRuntimeControlOptions) { }: UseDashboardRuntimeControlOptions) {
const CONTROL_MIN_VISIBLE_MS = 450; const CONTROL_MIN_VISIBLE_MS = 1200;
const [showResourceModal, setShowResourceModal] = useState(false); const [showResourceModal, setShowResourceModal] = useState(false);
const [resourceBotId, setResourceBotId] = useState(''); const [resourceBotId, setResourceBotId] = useState('');
const [resourceSnapshot, setResourceSnapshot] = useState<BotResourceSnapshot | null>(null); const [resourceSnapshot, setResourceSnapshot] = useState<BotResourceSnapshot | null>(null);
@ -248,11 +248,23 @@ export function useDashboardRuntimeControl({
} }
}, []); }, []);
const setControlState = useCallback((id: string, state: BotControlState) => {
setControlStateByBot((prev) => ({ ...prev, [id]: state }));
}, []);
const clearControlState = useCallback((id: string) => {
setControlStateByBot((prev) => {
const next = { ...prev };
delete next[id];
return next;
});
}, []);
const stopBot = useCallback(async (id: string, status: string) => { const stopBot = useCallback(async (id: string, status: string) => {
if (status !== 'RUNNING') return; if (status !== 'RUNNING') return;
const startedAt = Date.now(); const startedAt = Date.now();
setOperatingBotId(id); setOperatingBotId(id);
setControlStateByBot((prev) => ({ ...prev, [id]: 'stopping' })); setControlState(id, 'stopping');
try { try {
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`); await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`);
updateBotStatus(id, 'STOPPED'); updateBotStatus(id, 'STOPPED');
@ -263,19 +275,15 @@ export function useDashboardRuntimeControl({
await ensureControlVisible(startedAt); await ensureControlVisible(startedAt);
} finally { } finally {
setOperatingBotId(null); setOperatingBotId(null);
setControlStateByBot((prev) => { clearControlState(id);
const next = { ...prev };
delete next[id];
return next;
});
} }
}, [ensureControlVisible, notify, refresh, t.stopFail, updateBotStatus]); }, [clearControlState, ensureControlVisible, notify, refresh, setControlState, t.stopFail, updateBotStatus]);
const startBot = useCallback(async (id: string, status: string) => { const startBot = useCallback(async (id: string, status: string) => {
if (status === 'RUNNING') return; if (status === 'RUNNING') return;
const startedAt = Date.now(); const startedAt = Date.now();
setOperatingBotId(id); setOperatingBotId(id);
setControlStateByBot((prev) => ({ ...prev, [id]: 'starting' })); setControlState(id, 'starting');
try { try {
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`); await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
updateBotStatus(id, 'RUNNING'); updateBotStatus(id, 'RUNNING');
@ -287,13 +295,9 @@ export function useDashboardRuntimeControl({
await ensureControlVisible(startedAt); await ensureControlVisible(startedAt);
} finally { } finally {
setOperatingBotId(null); setOperatingBotId(null);
setControlStateByBot((prev) => { clearControlState(id);
const next = { ...prev };
delete next[id];
return next;
});
} }
}, [ensureControlVisible, notify, refresh, t.startFail, updateBotStatus]); }, [clearControlState, ensureControlVisible, notify, refresh, setControlState, t.startFail, updateBotStatus]);
const restartBot = useCallback(async (id: string, status: string) => { const restartBot = useCallback(async (id: string, status: string) => {
const normalized = String(status || '').toUpperCase(); const normalized = String(status || '').toUpperCase();
@ -307,11 +311,11 @@ export function useDashboardRuntimeControl({
setOperatingBotId(id); setOperatingBotId(id);
try { try {
if (normalized === 'RUNNING') { if (normalized === 'RUNNING') {
setControlStateByBot((prev) => ({ ...prev, [id]: 'stopping' })); setControlState(id, 'stopping');
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`); await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`);
updateBotStatus(id, 'STOPPED'); updateBotStatus(id, 'STOPPED');
} }
setControlStateByBot((prev) => ({ ...prev, [id]: 'starting' })); setControlState(id, 'starting');
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`); await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
updateBotStatus(id, 'RUNNING'); updateBotStatus(id, 'RUNNING');
await refresh(); await refresh();
@ -322,17 +326,13 @@ export function useDashboardRuntimeControl({
await ensureControlVisible(startedAt); await ensureControlVisible(startedAt);
} finally { } finally {
setOperatingBotId(null); setOperatingBotId(null);
setControlStateByBot((prev) => { clearControlState(id);
const next = { ...prev };
delete next[id];
return next;
});
} }
}, [confirm, ensureControlVisible, notify, refresh, t, updateBotStatus]); }, [clearControlState, confirm, ensureControlVisible, notify, refresh, setControlState, t, updateBotStatus]);
const setBotEnabled = useCallback(async (id: string, enabled: boolean) => { const setBotEnabled = useCallback(async (id: string, enabled: boolean) => {
setOperatingBotId(id); setOperatingBotId(id);
setControlStateByBot((prev) => ({ ...prev, [id]: enabled ? 'enabling' : 'disabling' })); setControlState(id, enabled ? 'enabling' : 'disabling');
try { try {
if (enabled) { if (enabled) {
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/enable`); await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/enable`);
@ -351,13 +351,9 @@ export function useDashboardRuntimeControl({
notify(error?.response?.data?.detail || (enabled ? t.enableFail : t.disableFail), { tone: 'error' }); notify(error?.response?.data?.detail || (enabled ? t.enableFail : t.disableFail), { tone: 'error' });
} finally { } finally {
setOperatingBotId(null); setOperatingBotId(null);
setControlStateByBot((prev) => { clearControlState(id);
const next = { ...prev };
delete next[id];
return next;
});
} }
}, [confirm, notify, refresh, t]); }, [clearControlState, confirm, notify, refresh, setControlState, t]);
useEffect(() => { useEffect(() => {
void loadImageOptions(); void loadImageOptions();

View File

@ -60,59 +60,80 @@
gap: 10px; gap: 10px;
padding: 14px 14px 14px 18px; padding: 14px 14px 14px 18px;
border-radius: 16px; border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid var(--line);
background: rgba(8, 13, 22, 0.72); background: linear-gradient(145deg, color-mix(in oklab, var(--panel-soft) 86%, var(--panel) 14%), color-mix(in oklab, var(--panel-soft) 94%, transparent 6%));
cursor: pointer; cursor: pointer;
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
} }
.platform-bot-card:hover, .platform-bot-card:hover {
.platform-bot-card.is-selected { border-color: color-mix(in oklab, var(--brand) 58%, var(--line) 42%);
box-shadow: 0 8px 24px color-mix(in oklab, var(--brand) 12%, transparent);
transform: translateY(-1px); transform: translateY(-1px);
border-color: rgba(97, 174, 255, 0.45);
box-shadow: 0 16px 40px rgba(8, 25, 60, 0.18);
} }
.platform-bot-card.is-selected { .platform-bot-card.is-selected {
border-color: var(--brand);
background: color-mix(in oklab, var(--brand) 4%, var(--panel-soft));
box-shadow: box-shadow:
0 0 0 2px color-mix(in oklab, var(--brand) 70%, transparent), 0 0 0 2px color-mix(in oklab, var(--brand) 30%, transparent),
0 16px 30px color-mix(in oklab, var(--brand) 28%, transparent), 0 12px 28px color-mix(in oklab, var(--brand) 18%, transparent);
inset 0 0 0 1px color-mix(in oklab, var(--brand) 84%, transparent); z-index: 2;
}
.platform-bot-card.is-selected::after {
content: '';
position: absolute;
inset: 0;
border-radius: 16px;
border: 2px solid var(--brand);
pointer-events: none;
opacity: 0.8;
} }
.platform-bot-card.state-running { .platform-bot-card.state-running {
background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 14%, var(--panel-soft) 86%), color-mix(in oklab, var(--ok) 8%, var(--panel) 92%)); background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 8%, var(--panel-soft) 92%), color-mix(in oklab, var(--ok) 4%, var(--panel) 96%));
}
.platform-bot-card.state-running.is-selected {
background: linear-gradient(145deg, color-mix(in oklab, var(--ok) 12%, var(--brand) 4%), color-mix(in oklab, var(--ok) 6%, var(--panel) 94%));
} }
.platform-bot-card.state-stopped { .platform-bot-card.state-stopped {
background: linear-gradient(145deg, color-mix(in oklab, #b79aa2 14%, var(--panel-soft) 86%), color-mix(in oklab, #b79aa2 7%, var(--panel) 93%)); background: linear-gradient(145deg, color-mix(in oklab, var(--err) 8%, var(--panel-soft) 92%), color-mix(in oklab, var(--err) 4%, var(--panel) 96%));
} }
.platform-bot-card.state-disabled { .platform-bot-card.state-disabled {
background: linear-gradient(145deg, color-mix(in oklab, #9ca3b5 14%, var(--panel-soft) 86%), color-mix(in oklab, #9ca3b5 7%, var(--panel) 93%)); opacity: 0.72;
filter: grayscale(0.2);
background: var(--panel-soft);
} }
.platform-bot-strip { .platform-bot-strip {
position: absolute; position: absolute;
left: 0; left: 0;
top: 8px; top: 0;
bottom: 8px; bottom: 0;
width: 3px; width: 4px;
border-radius: 999px;
background: color-mix(in oklab, var(--line) 80%, transparent); background: color-mix(in oklab, var(--line) 80%, transparent);
opacity: 0.7; opacity: 0.8;
transition: all 0.25s ease;
} }
.platform-bot-strip.is-running { .platform-bot-strip.is-running {
background: linear-gradient(180deg, color-mix(in oklab, var(--ok) 80%, #9be8c6 20%), color-mix(in oklab, var(--ok) 54%, transparent)); background: var(--ok);
box-shadow: 2px 0 8px color-mix(in oklab, var(--ok) 40%, transparent);
} }
.platform-bot-strip.is-stopped { .platform-bot-strip.is-stopped {
background: linear-gradient(180deg, color-mix(in oklab, var(--err) 74%, #e7b1ba 26%), color-mix(in oklab, var(--err) 54%, transparent)); background: var(--err);
opacity: 0.6;
} }
.platform-bot-strip.is-disabled { .platform-bot-strip.is-disabled {
background: linear-gradient(180deg, color-mix(in oklab, #9ca3b5 82%, #d4d9e2 18%), color-mix(in oklab, #9ca3b5 52%, transparent)); background: #9ca3b5;
opacity: 0.4;
} }
.platform-bot-top { .platform-bot-top {
@ -245,15 +266,66 @@
} }
.platform-bot-action-start { .platform-bot-action-start {
background: color-mix(in oklab, var(--ok) 24%, var(--panel-soft) 76%); background: color-mix(in oklab, var(--ok) 38%, var(--panel-soft) 62%);
border-color: color-mix(in oklab, var(--ok) 52%, var(--line) 48%); border-color: color-mix(in oklab, var(--ok) 60%, var(--line) 40%);
color: color-mix(in oklab, var(--text) 76%, white 24%); color: color-mix(in oklab, var(--text) 82%, white 18%);
} }
.platform-bot-action-stop { .platform-bot-action-stop {
background: color-mix(in oklab, #f5af48 30%, var(--panel-soft) 70%); background: color-mix(in oklab, #f5af48 45%, var(--panel-soft) 55%);
border-color: color-mix(in oklab, #f5af48 58%, var(--line) 42%); border-color: color-mix(in oklab, #f5af48 68%, var(--line) 32%);
color: #5e3b00; color: #4a2d00;
}
.platform-bot-icon-btn:disabled {
opacity: 1;
cursor: not-allowed;
filter: saturate(1.1);
}
.ops-control-pending {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.ops-control-dots {
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
}
.ops-control-dots i {
width: 5px;
height: 5px;
border-radius: 50%;
display: block;
background: #ffffff;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3);
opacity: 1;
animation: ops-control-dot 1s infinite ease-in-out;
}
.ops-control-dots i:nth-child(2) {
animation-delay: 0.15s;
}
.ops-control-dots i:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes ops-control-dot {
0%, 100% {
transform: scale(0.8);
opacity: 0.4;
}
50% {
transform: scale(1.2);
opacity: 1;
}
} }
.platform-bot-lock { .platform-bot-lock {

View File

@ -1,166 +0,0 @@
import { useState } from 'react';
import { PlatformSettingsModal } from './components/PlatformSettingsModal';
import { TemplateManagerModal } from './components/TemplateManagerModal';
import { PlatformBotListSection } from './components/PlatformBotListSection';
import { PlatformBotOverviewSection } from './components/PlatformBotOverviewSection';
import {
PlatformCompactBotSheet,
PlatformImageFactoryModal,
PlatformLastActionModal,
PlatformResourceMonitorModal,
} from './components/PlatformDashboardModals';
import { PlatformManagementSection } from './components/PlatformManagementSection';
import { PlatformSummaryCards } from './components/PlatformSummaryCards';
import { PlatformUsageAnalyticsSection } from './components/PlatformUsageAnalyticsSection';
import { usePlatformDashboard } from './hooks/usePlatformDashboard';
import { CreateBotWizardModal } from '../onboarding/CreateBotWizardModal';
import { navigateToDashboardSkills } from '../../utils/appRoute';
import './PlatformDashboardPage.css';
interface PlatformDashboardPageProps {
compactMode: boolean;
}
export function PlatformDashboardPage({ compactMode }: PlatformDashboardPageProps) {
const dashboard = usePlatformDashboard({ compactMode });
const [showCreateBotModal, setShowCreateBotModal] = useState(false);
return (
<>
<div className={`platform-grid ${compactMode ? 'is-compact' : ''}`}>
<PlatformBotListSection
botListPage={dashboard.botListPage}
botListPageCount={dashboard.botListPageCount}
filteredBots={dashboard.filteredBots}
isZh={dashboard.isZh}
loading={dashboard.loading}
operatingBotId={dashboard.operatingBotId}
pagedBots={dashboard.pagedBots}
pageSizeReady={dashboard.pageSizeReady}
search={dashboard.search}
selectedBotId={dashboard.selectedBotId}
onBotListPageChange={dashboard.setBotListPage}
onOpenCreateBot={() => setShowCreateBotModal(true)}
onRefresh={dashboard.refreshAll}
onSearchChange={dashboard.setSearch}
onSelectBot={dashboard.handleSelectBot}
onSetBotEnabled={dashboard.setBotEnabled}
onToggleBot={dashboard.toggleBot}
/>
{!compactMode ? (
<section className="platform-main">
<PlatformSummaryCards
isZh={dashboard.isZh}
overview={dashboard.overview}
overviewBots={dashboard.overviewBots}
overviewImages={dashboard.overviewImages}
overviewResources={dashboard.overviewResources}
/>
<div className="platform-main-grid">
<PlatformBotOverviewSection
isZh={dashboard.isZh}
lastActionPreview={dashboard.lastActionPreview}
operatingBotId={dashboard.operatingBotId}
selectedBotInfo={dashboard.selectedBotInfo}
selectedBotUsageSummary={dashboard.selectedBotUsageSummary}
onClearDashboardDirectSession={dashboard.clearDashboardDirectSession}
onOpenBotPanel={dashboard.openBotPanel}
onOpenLastAction={() => dashboard.setShowBotLastActionModal(true)}
onOpenResourceMonitor={dashboard.openResourceMonitor}
onRemoveBot={dashboard.removeBot}
/>
<PlatformManagementSection
isZh={dashboard.isZh}
onOpenImageFactory={() => dashboard.setShowImageFactory(true)}
onOpenPlatformSettings={() => dashboard.setShowPlatformSettings(true)}
onOpenSkillMarketplace={navigateToDashboardSkills}
onOpenTemplateManager={() => dashboard.setShowTemplateManager(true)}
/>
</div>
<PlatformUsageAnalyticsSection
isZh={dashboard.isZh}
usageAnalytics={dashboard.usageAnalytics}
usageAnalyticsMax={dashboard.usageAnalyticsMax}
usageAnalyticsSeries={dashboard.usageAnalyticsSeries}
usageAnalyticsTicks={dashboard.usageAnalyticsTicks}
usageLoading={dashboard.usageLoading}
usageSummary={dashboard.usageSummary}
/>
</section>
) : null}
</div>
<PlatformCompactBotSheet
open={compactMode && dashboard.compactSheetMounted && Boolean(dashboard.selectedBotInfo)}
closing={dashboard.compactSheetClosing}
isZh={dashboard.isZh}
onClose={dashboard.closeCompactBotSheet}
>
<PlatformBotOverviewSection
compactSheet
isZh={dashboard.isZh}
lastActionPreview={dashboard.lastActionPreview}
operatingBotId={dashboard.operatingBotId}
selectedBotInfo={dashboard.selectedBotInfo}
selectedBotUsageSummary={dashboard.selectedBotUsageSummary}
onClearDashboardDirectSession={dashboard.clearDashboardDirectSession}
onOpenBotPanel={dashboard.openBotPanel}
onOpenLastAction={() => dashboard.setShowBotLastActionModal(true)}
onOpenResourceMonitor={dashboard.openResourceMonitor}
onRemoveBot={dashboard.removeBot}
/>
</PlatformCompactBotSheet>
<PlatformImageFactoryModal
isZh={dashboard.isZh}
open={dashboard.showImageFactory}
onClose={() => dashboard.setShowImageFactory(false)}
/>
<CreateBotWizardModal
open={showCreateBotModal}
onClose={() => setShowCreateBotModal(false)}
onCreated={() => {
void dashboard.refreshAll();
}}
/>
<TemplateManagerModal
isZh={dashboard.isZh}
open={dashboard.showTemplateManager}
onClose={() => dashboard.setShowTemplateManager(false)}
/>
<PlatformSettingsModal
isZh={dashboard.isZh}
open={dashboard.showPlatformSettings}
onClose={() => dashboard.setShowPlatformSettings(false)}
onSaved={dashboard.handlePlatformSettingsSaved}
/>
<PlatformLastActionModal
botName={dashboard.selectedBotInfo?.name}
isZh={dashboard.isZh}
lastAction={dashboard.selectedBotInfo?.last_action}
open={dashboard.showBotLastActionModal && Boolean(dashboard.selectedBotInfo)}
onClose={() => dashboard.setShowBotLastActionModal(false)}
/>
<PlatformResourceMonitorModal
isZh={dashboard.isZh}
open={dashboard.showResourceModal}
resourceBot={dashboard.resourceBot}
resourceBotId={dashboard.resourceBotId}
resourceError={dashboard.resourceError}
resourceLoading={dashboard.resourceLoading}
resourceSnapshot={dashboard.resourceSnapshot}
onClose={dashboard.closeResourceModal}
onRefresh={dashboard.loadResourceSnapshot}
/>
</>
);
}

View File

@ -117,7 +117,15 @@ export function PlatformBotListSection({
tooltip={running ? (isZh ? '停止' : 'Stop') : (isZh ? '启动' : 'Start')} tooltip={running ? (isZh ? '停止' : 'Stop') : (isZh ? '启动' : 'Start')}
aria-label={running ? (isZh ? '停止' : 'Stop') : (isZh ? '启动' : 'Start')} aria-label={running ? (isZh ? '停止' : 'Stop') : (isZh ? '启动' : 'Start')}
> >
{running ? <Square size={14} /> : <Power size={14} />} {operatingBotId === bot.id ? (
<span className="ops-control-pending">
<span className="ops-control-dots" aria-hidden="true">
<i />
<i />
<i />
</span>
</span>
) : running ? <Square size={14} /> : <Power size={14} />}
</LucentIconButton> </LucentIconButton>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff