v0.1.4-p4
parent
d196e57804
commit
ad59bc3794
|
|
@ -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.
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
1147
frontend/yarn.lock
1147
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue