From 0ef036621cb9d26a5dde3d33077b75cfa2ecb1be Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Tue, 3 Mar 2026 15:44:39 +0800 Subject: [PATCH] v 0.1.3 --- .../src/components/lucent/LucentTooltip.tsx | 22 +++ .../src/components/lucent/lucent-tooltip.css | 39 +++++ .../modules/dashboard/BotDashboardModule.css | 51 +++++- .../modules/dashboard/BotDashboardModule.tsx | 156 +++++++++--------- 4 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 frontend/src/components/lucent/LucentTooltip.tsx create mode 100644 frontend/src/components/lucent/lucent-tooltip.css diff --git a/frontend/src/components/lucent/LucentTooltip.tsx b/frontend/src/components/lucent/LucentTooltip.tsx new file mode 100644 index 0000000..ecb6072 --- /dev/null +++ b/frontend/src/components/lucent/LucentTooltip.tsx @@ -0,0 +1,22 @@ +import type { ReactNode } from 'react'; +import './lucent-tooltip.css'; + +interface LucentTooltipProps { + content: string; + children: ReactNode; + side?: 'top' | 'bottom'; +} + +export function LucentTooltip({ content, children, side = 'top' }: LucentTooltipProps) { + const text = String(content || '').trim(); + if (!text) return <>{children}; + return ( + + {children} + + {text} + + + ); +} + diff --git a/frontend/src/components/lucent/lucent-tooltip.css b/frontend/src/components/lucent/lucent-tooltip.css new file mode 100644 index 0000000..be8475c --- /dev/null +++ b/frontend/src/components/lucent/lucent-tooltip.css @@ -0,0 +1,39 @@ +.lucent-tooltip-wrap { + position: relative; + display: inline-flex; +} + +.lucent-tooltip-bubble { + position: absolute; + left: 50%; + transform: translateX(-50%); + border: 1px solid color-mix(in oklab, var(--line) 72%, var(--brand) 28%); + border-radius: 8px; + background: color-mix(in oklab, var(--panel) 88%, #000 12%); + color: var(--text); + font-size: 11px; + font-weight: 700; + line-height: 1.2; + padding: 5px 8px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + visibility: hidden; + transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s ease; + z-index: 40; +} + +.lucent-tooltip-wrap.side-top .lucent-tooltip-bubble { + bottom: calc(100% + 8px); +} + +.lucent-tooltip-wrap.side-bottom .lucent-tooltip-bubble { + top: calc(100% + 8px); +} + +.lucent-tooltip-wrap:hover .lucent-tooltip-bubble, +.lucent-tooltip-wrap:focus-within .lucent-tooltip-bubble { + opacity: 1; + visibility: visible; +} + diff --git a/frontend/src/modules/dashboard/BotDashboardModule.css b/frontend/src/modules/dashboard/BotDashboardModule.css index 234ba34..ce173d3 100644 --- a/frontend/src/modules/dashboard/BotDashboardModule.css +++ b/frontend/src/modules/dashboard/BotDashboardModule.css @@ -38,16 +38,20 @@ padding: 10px 10px 10px 14px; margin-bottom: 10px; cursor: pointer; - transition: border-color 0.2s ease, transform 0.2s ease; + transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; } .ops-bot-card:hover { border-color: var(--brand); + transform: translateY(-1px); } .ops-bot-card.is-active { border-color: var(--brand); - box-shadow: inset 0 0 0 1px var(--brand); + box-shadow: 0 10px 24px color-mix(in oklab, var(--brand) 22%, transparent), inset 0 0 0 1px var(--brand); + background: color-mix(in oklab, var(--panel-soft) 76%, var(--brand-soft) 24%); + transform: translateY(-1px); + z-index: 2; } .ops-bot-top { @@ -73,7 +77,7 @@ display: flex; align-items: center; justify-content: flex-end; - gap: 8px; + gap: 6px; } .ops-bot-strip { @@ -95,15 +99,50 @@ } .ops-bot-icon-btn { - width: 34px; - height: 34px; + width: 31px; + height: 31px; padding: 0; - border-radius: 9px; + border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; } +.ops-bot-icon-btn svg { + width: 13px; + height: 13px; + stroke-width: 2.2; +} + +.ops-bot-actions .ops-bot-action-monitor { + background: color-mix(in oklab, var(--panel-soft) 75%, var(--brand-soft) 25%); + border-color: color-mix(in oklab, var(--brand) 44%, var(--line) 56%); + color: color-mix(in oklab, var(--text) 76%, white 24%); +} + +.ops-bot-actions .ops-bot-action-start { + background: color-mix(in oklab, var(--ok) 24%, var(--panel-soft) 76%); + border-color: color-mix(in oklab, var(--ok) 52%, var(--line) 48%); + color: color-mix(in oklab, var(--text) 76%, white 24%); +} + +.ops-bot-actions .ops-bot-action-stop { + background: color-mix(in oklab, #f5af48 30%, var(--panel-soft) 70%); + border-color: color-mix(in oklab, #f5af48 58%, var(--line) 42%); + color: #5e3b00; +} + +.ops-bot-actions .ops-bot-action-delete { + background: color-mix(in oklab, var(--err) 20%, var(--panel-soft) 80%); + border-color: color-mix(in oklab, var(--err) 48%, var(--line) 52%); + color: color-mix(in oklab, var(--text) 72%, white 28%); +} + +.ops-bot-actions .ops-bot-icon-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + .ops-control-pending { display: inline-flex; align-items: center; diff --git a/frontend/src/modules/dashboard/BotDashboardModule.tsx b/frontend/src/modules/dashboard/BotDashboardModule.tsx index acc0194..fd9f9c9 100644 --- a/frontend/src/modules/dashboard/BotDashboardModule.tsx +++ b/frontend/src/modules/dashboard/BotDashboardModule.tsx @@ -17,6 +17,7 @@ import { pickLocale } from '../../i18n'; import { dashboardZhCn } from '../../i18n/dashboard.zh-cn'; import { dashboardEn } from '../../i18n/dashboard.en'; import { useLucentPrompt } from '../../components/lucent/LucentPromptProvider'; +import { LucentTooltip } from '../../components/lucent/LucentTooltip'; interface BotDashboardModuleProps { onOpenCreateWizard?: () => void; @@ -1779,7 +1780,11 @@ export function BotDashboardModule({ }, [workspaceAutoRefresh, selectedBotId, selectedBot?.docker_status, workspaceCurrentPath]); const saveBot = async (mode: 'params' | 'agent' | 'base') => { - if (!selectedBot) return; + const targetBotId = String(selectedBot?.id || selectedBotId || '').trim(); + if (!targetBotId) { + notify(isZh ? '未选中 Bot,无法保存。' : 'No bot selected.', { tone: 'warning' }); + return; + } setIsSaving(true); try { const payload: Record = {}; @@ -1834,7 +1839,7 @@ export function BotDashboardModule({ payload.identity_md = editForm.identity_md; } - await axios.put(`${APP_ENDPOINTS.apiBase}/bots/${selectedBot.id}`, payload); + await axios.put(`${APP_ENDPOINTS.apiBase}/bots/${targetBotId}`, payload); await refresh(); setShowBaseModal(false); setShowParamModal(false); @@ -2027,71 +2032,75 @@ export function BotDashboardModule({
{t.image}: {bot.image_tag || '-'}
- + + + {bot.docker_status === 'RUNNING' ? ( - + ) : } + + ) : ( + + + + )} + - )} - +
); @@ -2521,31 +2530,11 @@ export function BotDashboardModule({
{isZh ? '容器状态' : 'Container'}: {resourceSnapshot.docker_status}
+
{isZh ? '容器名' : 'Container Name'}: {resourceSnapshot.bot_id ? `worker_${resourceSnapshot.bot_id}` : '-'}
+
{isZh ? '基础镜像' : 'Base Image'}: {resourceBot?.image_tag || '-'}
+
Provider/Model: {resourceBot?.llm_provider || '-'} / {resourceBot?.llm_model || '-'}
{isZh ? '采样时间' : 'Collected'}: {resourceSnapshot.collected_at}
-
- {isZh ? 'CPU限制生效' : 'CPU limit'}:{' '} - - {Number(resourceSnapshot.configured.cpu_cores) === 0 - ? (isZh ? '不限' : 'UNLIMITED') - : (resourceSnapshot.enforcement.cpu_limited ? 'YES' : 'NO')} - -
-
- {isZh ? '内存限制生效' : 'Memory limit'}:{' '} - - {Number(resourceSnapshot.configured.memory_mb) === 0 - ? (isZh ? '不限' : 'UNLIMITED') - : (resourceSnapshot.enforcement.memory_limited ? 'YES' : 'NO')} - -
-
- {isZh ? '存储限制生效' : 'Storage limit'}:{' '} - - {Number(resourceSnapshot.configured.storage_gb) === 0 - ? (isZh ? '不限' : 'UNLIMITED') - : (resourceSnapshot.enforcement.storage_limited ? 'YES' : 'NO')} - -
+
{isZh ? '策略说明' : 'Policy'}: {isZh ? '资源值 0 = 不限制' : 'Value 0 = Unlimited'}
@@ -2558,6 +2547,9 @@ export function BotDashboardModule({
{isZh ? 'Docker 实际限制' : 'Docker Runtime Limits'}
+
{isZh ? 'CPU限制生效' : 'CPU limit active'}{Number(resourceSnapshot.configured.cpu_cores) === 0 ? (isZh ? '不限' : 'Unlimited') : (resourceSnapshot.enforcement.cpu_limited ? 'YES' : 'NO')}
+
{isZh ? '内存限制生效' : 'Memory limit active'}{Number(resourceSnapshot.configured.memory_mb) === 0 ? (isZh ? '不限' : 'Unlimited') : (resourceSnapshot.enforcement.memory_limited ? 'YES' : 'NO')}
+
{isZh ? '存储限制生效' : 'Storage limit active'}{Number(resourceSnapshot.configured.storage_gb) === 0 ? (isZh ? '不限' : 'Unlimited') : (resourceSnapshot.enforcement.storage_limited ? 'YES' : 'NO')}
CPU{resourceSnapshot.runtime.limits.cpu_cores ? resourceSnapshot.runtime.limits.cpu_cores.toFixed(2) : (Number(resourceSnapshot.configured.cpu_cores) === 0 ? (isZh ? '不限' : 'Unlimited') : '-')}
{isZh ? '内存' : 'Memory'}{resourceSnapshot.runtime.limits.memory_bytes ? formatBytes(resourceSnapshot.runtime.limits.memory_bytes) : (Number(resourceSnapshot.configured.memory_mb) === 0 ? (isZh ? '不限' : 'Unlimited') : '-')}
{isZh ? '存储' : 'Storage'}{resourceSnapshot.runtime.limits.storage_bytes ? formatBytes(resourceSnapshot.runtime.limits.storage_bytes) : (resourceSnapshot.runtime.limits.storage_opt_raw || (Number(resourceSnapshot.configured.storage_gb) === 0 ? (isZh ? '不限' : 'Unlimited') : '-'))}