diff --git a/webui/src/components/GlobalDialog.tsx b/webui/src/components/GlobalDialog.tsx index dd734ce..db084ec 100644 --- a/webui/src/components/GlobalDialog.tsx +++ b/webui/src/components/GlobalDialog.tsx @@ -32,7 +32,7 @@ export const GlobalDialog: React.FC<{ return ( {open && ( - diff --git a/webui/src/components/Header.tsx b/webui/src/components/Header.tsx index 782af27..9b55f32 100644 --- a/webui/src/components/Header.tsx +++ b/webui/src/components/Header.tsx @@ -63,14 +63,14 @@ const Header: React.FC = () => {
- +
{!sidebarCollapsed && ( {t('appName')} )}
- +
{t('appName')} @@ -79,13 +79,13 @@ const Header: React.FC = () => {
{t('gatewayStatus')}: {isGatewayOnline ? ( -
-
+
+
{t('online')}
) : ( -
-
+
+
{t('offline')}
)} diff --git a/webui/src/components/Layout.tsx b/webui/src/components/Layout.tsx index 5a11bb4..04dc708 100644 --- a/webui/src/components/Layout.tsx +++ b/webui/src/components/Layout.tsx @@ -15,7 +15,7 @@ const Layout: React.FC = () => {
{sidebarOpen && ( - + ))}
diff --git a/webui/src/components/Sidebar.tsx b/webui/src/components/Sidebar.tsx index 2ffeff9..f56c575 100644 --- a/webui/src/components/Sidebar.tsx +++ b/webui/src/components/Sidebar.tsx @@ -11,12 +11,12 @@ const Sidebar: React.FC = () => { const location = useLocation(); const [expandedSections, setExpandedSections] = React.useState>({ main: true, - agents: true, - ops: true, - config: true, - knowledge: true, - insights: true, - channels: true, + agents: false, + ops: false, + config: false, + knowledge: false, + insights: false, + channels: false, }); const sections = [ @@ -158,7 +158,7 @@ const Sidebar: React.FC = () => {
) : (
-
+
)}
diff --git a/webui/src/components/SpaceParticles.tsx b/webui/src/components/SpaceParticles.tsx index c53fec9..57922b9 100644 --- a/webui/src/components/SpaceParticles.tsx +++ b/webui/src/components/SpaceParticles.tsx @@ -32,9 +32,22 @@ export const SpaceParticles: React.FC = () => { resize(); let observer: MutationObserver | null = null; + let themeStyles = typeof document !== 'undefined' ? getComputedStyle(document.documentElement) : null; + + const readThemeColor = (name: string, fallback: string) => { + const value = themeStyles?.getPropertyValue(name).trim(); + return value || fallback; + }; + + const readThemeNumber = (name: string, fallback: number) => { + const value = Number.parseFloat(themeStyles?.getPropertyValue(name).trim() || ''); + return Number.isFinite(value) ? value : fallback; + }; + if (typeof document !== 'undefined') { observer = new MutationObserver(() => { isDark = document.documentElement.classList.contains('theme-dark'); + themeStyles = getComputedStyle(document.documentElement); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); } @@ -60,18 +73,18 @@ export const SpaceParticles: React.FC = () => { canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height) ); - if (isDark) { - gradient.addColorStop(0, 'rgba(255, 133, 82, 0.16)'); - gradient.addColorStop(0.55, 'rgba(112, 41, 22, 0.20)'); - gradient.addColorStop(1, 'rgba(28, 12, 8, 0.88)'); - } else { - gradient.addColorStop(0, 'rgba(255, 243, 233, 0.30)'); - gradient.addColorStop(0.55, 'rgba(255, 179, 107, 0.16)'); - gradient.addColorStop(1, 'rgba(255, 226, 209, 0.55)'); - } + gradient.addColorStop(0, readThemeColor('--particle-glow-start', 'rgba(255, 243, 233, 0.30)')); + gradient.addColorStop(0.55, readThemeColor('--particle-glow-mid', 'rgba(255, 179, 107, 0.16)')); + gradient.addColorStop(1, readThemeColor('--particle-glow-end', 'rgba(255, 226, 209, 0.55)')); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); + const particleRGB = readThemeColor('--particle-dot-rgb', isDark ? '255, 179, 107' : '217, 72, 28'); + const particleOpacityFloor = readThemeNumber('--particle-dot-opacity-floor', isDark ? 0.1 : 0.08); + const particleOpacityScale = readThemeNumber('--particle-dot-opacity-scale', isDark ? 1 : 0.65); + const lineRGB = readThemeColor('--particle-line-rgb', isDark ? '240, 90, 40' : '164, 58, 24'); + const lineOpacityScale = readThemeNumber('--particle-line-opacity-scale', isDark ? 1 : 0.85); + particles.forEach((p) => { p.x += p.speedX; p.y += p.speedY; @@ -83,9 +96,7 @@ export const SpaceParticles: React.FC = () => { ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); - ctx.fillStyle = isDark - ? `rgba(255, 179, 107, ${p.opacity})` - : `rgba(217, 72, 28, ${Math.max(0.08, p.opacity * 0.65)})`; + ctx.fillStyle = `rgba(${particleRGB}, ${Math.max(particleOpacityFloor, p.opacity * particleOpacityScale)})`; ctx.fill(); }); @@ -100,9 +111,7 @@ export const SpaceParticles: React.FC = () => { if (dist < 120) { ctx.beginPath(); const lineOpacity = 0.16 * (1 - dist / 120); - ctx.strokeStyle = isDark - ? `rgba(240, 90, 40, ${lineOpacity})` - : `rgba(164, 58, 24, ${lineOpacity * 0.85})`; + ctx.strokeStyle = `rgba(${lineRGB}, ${lineOpacity * lineOpacityScale})`; ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.stroke(); diff --git a/webui/src/components/StatCard.tsx b/webui/src/components/StatCard.tsx index e1c8bf0..33e3d9c 100644 --- a/webui/src/components/StatCard.tsx +++ b/webui/src/components/StatCard.tsx @@ -8,7 +8,7 @@ interface StatCardProps { const StatCard: React.FC = ({ title, value, icon }) => (
-
+
{icon}
diff --git a/webui/src/context/UIContext.tsx b/webui/src/context/UIContext.tsx index ce377e7..6ce758b 100644 --- a/webui/src/context/UIContext.tsx +++ b/webui/src/context/UIContext.tsx @@ -114,7 +114,7 @@ export const UIProvider: React.FC<{ children: React.ReactNode }> = ({ children } {loading && ( -
@@ -134,7 +134,7 @@ export const UIProvider: React.FC<{ children: React.ReactNode }> = ({ children } {customModal && ( - diff --git a/webui/src/i18n/index.ts b/webui/src/i18n/index.ts index a6f2034..982513c 100644 --- a/webui/src/i18n/index.ts +++ b/webui/src/i18n/index.ts @@ -30,10 +30,16 @@ const resources = { whatsappStateDisconnected: 'Disconnected', whatsappBridgeDevHintTitle: 'How to use', whatsappBridgeDevHint: '1. Start the bridge with `clawgo channel whatsapp bridge run`.\n2. Scan the QR code here with WhatsApp.\n3. Keep `make dev` running to receive WhatsApp messages in ClawGo.', + whatsappFieldEnabledHint: 'Master switch for receiving WhatsApp messages through the bridge.', + whatsappFieldBridgeURLHint: 'WebSocket address of the local WhatsApp companion bridge service.', + whatsappFieldAllowFromHint: 'One sender JID per line. Only these senders can trigger ClawGo.', + whatsappFieldEnableGroupsHint: 'Allow messages from WhatsApp groups to enter the channel.', + whatsappFieldRequireMentionHint: 'When enabled, group messages must mention the bot before being handled.', + whatsappFieldAllowFromFootnote: 'Supports one JID per line, and also accepts comma-separated values.', whatsappLogoutTitle: 'Logout WhatsApp Session', whatsappLogoutMessage: 'Unlink the current WhatsApp companion session and request a new QR code?', mcpServices: 'MCP', - mcpServicesHint: 'Manage MCP servers, install packages, and inspect discovered remote tools.', + mcpServicesHint: 'Manage MCP servers and install service packages.', cronJobs: 'Cron Jobs', nodes: 'Nodes', nodeArtifacts: 'Node Artifacts', @@ -435,9 +441,6 @@ const resources = { configMCPArgsEnterHint: 'Type one argument and press Enter', configMCPCommandMissing: 'MCP command is unavailable.', configMCPInstallSuggested: 'Suggested package: {{pkg}}', - configMCPDiscoveredTools: 'Discovered MCP Tools', - configMCPDiscoveredToolsCount: '{{count}} discovered', - configNoMCPDiscoveredTools: 'No MCP tools discovered yet.', configDeleteMCPServerConfirmTitle: 'Delete MCP Server', configDeleteMCPServerConfirmMessage: 'Delete MCP server "{{name}}" from current config?', configNoGroups: 'No config groups found.', @@ -682,10 +685,16 @@ const resources = { whatsappStateDisconnected: '已断开', whatsappBridgeDevHintTitle: '使用方式', whatsappBridgeDevHint: '1. 先执行 `clawgo channel whatsapp bridge run` 启动 bridge。\n2. 在这里用 WhatsApp 扫描二维码。\n3. 再保持 `make dev` 运行,让 ClawGo 接收 WhatsApp 消息。', + whatsappFieldEnabledHint: '总开关,控制是否通过 bridge 接收 WhatsApp 消息。', + whatsappFieldBridgeURLHint: '本地 WhatsApp companion bridge 服务的 WebSocket 地址。', + whatsappFieldAllowFromHint: '每行一个发送者 JID,只有这些发送者可以触发 ClawGo。', + whatsappFieldEnableGroupsHint: '允许来自 WhatsApp 群组的消息进入该通道。', + whatsappFieldRequireMentionHint: '开启后,群消息必须显式 @ 提及机器人才会被处理。', + whatsappFieldAllowFromFootnote: '支持每行一个 JID,也支持逗号分隔后自动拆分。', whatsappLogoutTitle: '退出 WhatsApp 会话', whatsappLogoutMessage: '是否解除当前 WhatsApp companion 会话,并重新申请新的二维码?', mcpServices: 'MCP', - mcpServicesHint: '管理 MCP 服务、安装服务包,并查看已发现的远端工具。', + mcpServicesHint: '管理 MCP 服务并安装服务包。', cronJobs: '定时任务', nodes: '节点', nodeArtifacts: '节点工件', @@ -1087,9 +1096,6 @@ const resources = { configMCPArgsEnterHint: '输入一个参数后按回车添加', configMCPCommandMissing: 'MCP 命令不可用。', configMCPInstallSuggested: '建议安装包:{{pkg}}', - configMCPDiscoveredTools: '已发现的 MCP 工具', - configMCPDiscoveredToolsCount: '已发现 {{count}} 个', - configNoMCPDiscoveredTools: '暂未发现 MCP 工具。', configDeleteMCPServerConfirmTitle: '删除 MCP 服务', configDeleteMCPServerConfirmMessage: '确认从当前配置中删除 MCP 服务 “{{name}}”吗?', configNoGroups: '未找到配置分组。', diff --git a/webui/src/index.css b/webui/src/index.css index fac437d..51eb6b6 100644 --- a/webui/src/index.css +++ b/webui/src/index.css @@ -20,6 +20,24 @@ --color-indigo-600: #ea580c; --color-indigo-700: #c2410c; --color-indigo-800: #9a3412; + --color-emerald-300: #6ee7b7; + --color-emerald-400: #34d399; + --color-emerald-500: #10b981; + --color-red-400: #f87171; + --color-red-500: #ef4444; + --color-amber-300: #fcd34d; + --color-amber-400: #fbbf24; + --color-amber-500: #f59e0b; + --color-orange-400: #fb923c; + --color-sky-300: #7dd3fc; + --color-sky-400: #38bdf8; + --color-sky-500: #0ea5e9; + --color-violet-300: #c4b5fd; + --color-violet-400: #a78bfa; + --color-violet-500: #8b5cf6; + --color-rose-300: #fda4af; + --color-rose-500: #f43f5e; + --color-fuchsia-400: #e879f9; } html { @@ -76,6 +94,75 @@ html { --button-danger-bg-hover: rgb(254 205 211 / 0.98); --button-danger-border: rgb(244 63 94 / 0.28); --button-danger-text: rgb(159 18 57 / 0.98); + --brand-badge-start: #ffb36b; + --brand-badge-mid: #ff8552; + --brand-badge-end: #d9481c; + --brand-badge-shadow: rgb(234 88 12 / 0.2); + --card-icon-inset: rgb(255 255 255 / 0.18); + --status-online-bg: rgb(16 185 129 / 0.1); + --status-online-border: rgb(16 185 129 / 0.2); + --status-online-text: var(--color-emerald-500); + --status-online-dot: var(--color-emerald-500); + --status-online-glow: rgb(16 185 129 / 0.8); + --status-offline-bg: rgb(239 68 68 / 0.1); + --status-offline-border: rgb(239 68 68 / 0.2); + --status-offline-text: var(--color-red-500); + --status-offline-dot: var(--color-red-500); + --status-offline-glow: rgb(239 68 68 / 0.8); + --token-indicator-glow: rgb(240 90 40 / 0.38); + --topology-node-highlight-shadow: rgb(245 158 11 / 0.2); + --topology-node-base-shadow: rgb(0 0 0 / 0.6); + --topology-node-hover-shadow: rgb(255 255 255 / 0.05); + --topology-node-inner-border: rgb(255 255 255 / 0.05); + --topology-node-highlight-border: rgb(245 158 11 / 0.8); + --topology-online-glow: rgb(16 185 129 / 0.8); + --topology-line-highlight-track: rgb(245 158 11 / 0.15); + --topology-line-track: rgb(161 161 170 / 0.05); + --topology-line-highlight-flow: rgb(245 158 11 / 0.9); + --topology-line-flow: rgb(161 161 170 / 0.5); + --topology-tooltip-shadow: rgb(0 0 0 / 0.5); + --particle-glow-start: rgba(255, 243, 233, 0.30); + --particle-glow-mid: rgba(255, 179, 107, 0.16); + --particle-glow-end: rgba(255, 226, 209, 0.55); + --particle-dot-rgb: 217, 72, 28; + --particle-dot-opacity-floor: 0.08; + --particle-dot-opacity-scale: 0.65; + --particle-line-rgb: 164, 58, 24; + --particle-line-opacity-scale: 0.85; + --ekg-bar-start: rgb(245 158 11 / 0.7); + --ekg-bar-end: rgb(251 146 60 / 0.8); + --overlay-scrim-soft: rgb(15 23 42 / 0.4); + --overlay-scrim-medium: rgb(15 23 42 / 0.55); + --overlay-scrim-strong: rgb(15 23 42 / 0.6); + --media-surface: rgb(15 23 42 / 0.2); + --media-surface-strong: rgb(15 23 42 / 0.3); + --media-border: var(--color-zinc-800); + --notice-warning-bg: rgb(254 243 199 / 0.72); + --notice-warning-border: rgb(245 158 11 / 0.28); + --notice-warning-text: rgb(146 64 14 / 0.96); + --notice-danger-bg: rgb(255 228 230 / 0.72); + --notice-danger-border: rgb(244 63 94 / 0.24); + --notice-danger-text: rgb(159 18 57 / 0.96); + --pill-warning-bg: rgb(254 243 199 / 0.82); + --pill-warning-border: rgb(245 158 11 / 0.24); + --pill-warning-text: rgb(146 64 14 / 0.96); + --pill-danger-bg: rgb(255 228 230 / 0.8); + --pill-danger-border: rgb(244 63 94 / 0.22); + --pill-danger-text: rgb(159 18 57 / 0.96); + --pill-success-bg: rgb(220 252 231 / 0.82); + --pill-success-border: rgb(52 211 153 / 0.24); + --pill-success-text: rgb(6 95 70 / 0.96); + --pill-info-bg: rgb(224 242 254 / 0.82); + --pill-info-border: rgb(14 165 233 / 0.22); + --pill-info-text: rgb(3 105 161 / 0.96); + --pill-accent-bg: rgb(237 233 254 / 0.82); + --pill-accent-border: rgb(139 92 246 / 0.22); + --pill-accent-text: rgb(109 40 217 / 0.96); + --pill-neutral-bg: rgb(226 232 240 / 0.82); + --pill-neutral-border: rgb(203 213 225 / 0.24); + --pill-neutral-text: rgb(71 85 105 / 0.96); + --code-danger-text: rgb(190 24 93 / 0.96); + --code-warning-text: rgb(180 83 9 / 0.96); --chip-bg: rgb(226 232 240 / 0.82); --chip-bg-hover: rgb(203 213 225 / 0.92); --chip-border: rgb(203 213 225 / 0.96); @@ -165,6 +252,75 @@ html.theme-dark { --button-danger-bg-hover: rgb(244 63 94 / 0.22); --button-danger-border: rgb(251 113 133 / 0.24); --button-danger-text: rgb(255 228 230 / 0.96); + --brand-badge-start: #f8c58d; + --brand-badge-mid: #ee9852; + --brand-badge-end: #af4f16; + --brand-badge-shadow: rgb(217 107 37 / 0.18); + --card-icon-inset: rgb(255 255 255 / 0.1); + --status-online-bg: rgb(16 185 129 / 0.14); + --status-online-border: rgb(52 211 153 / 0.24); + --status-online-text: rgb(209 250 229 / 0.96); + --status-online-dot: var(--color-emerald-400); + --status-online-glow: rgb(16 185 129 / 0.78); + --status-offline-bg: rgb(244 63 94 / 0.14); + --status-offline-border: rgb(251 113 133 / 0.24); + --status-offline-text: rgb(255 228 230 / 0.96); + --status-offline-dot: var(--color-red-400); + --status-offline-glow: rgb(244 63 94 / 0.72); + --token-indicator-glow: rgb(232 132 58 / 0.42); + --topology-node-highlight-shadow: rgb(245 158 11 / 0.22); + --topology-node-base-shadow: rgb(0 0 0 / 0.7); + --topology-node-hover-shadow: rgb(255 255 255 / 0.06); + --topology-node-inner-border: rgb(255 255 255 / 0.06); + --topology-node-highlight-border: rgb(251 191 36 / 0.72); + --topology-online-glow: rgb(52 211 153 / 0.8); + --topology-line-highlight-track: rgb(245 158 11 / 0.16); + --topology-line-track: rgb(148 163 184 / 0.08); + --topology-line-highlight-flow: rgb(251 191 36 / 0.88); + --topology-line-flow: rgb(148 163 184 / 0.5); + --topology-tooltip-shadow: rgb(0 0 0 / 0.56); + --particle-glow-start: rgba(255, 133, 82, 0.16); + --particle-glow-mid: rgba(112, 41, 22, 0.20); + --particle-glow-end: rgba(28, 12, 8, 0.88); + --particle-dot-rgb: 255, 179, 107; + --particle-dot-opacity-floor: 0.1; + --particle-dot-opacity-scale: 1; + --particle-line-rgb: 240, 90, 40; + --particle-line-opacity-scale: 1; + --ekg-bar-start: rgb(251 191 36 / 0.78); + --ekg-bar-end: rgb(232 132 58 / 0.86); + --overlay-scrim-soft: rgb(0 0 0 / 0.4); + --overlay-scrim-medium: rgb(0 0 0 / 0.55); + --overlay-scrim-strong: rgb(0 0 0 / 0.62); + --media-surface: rgb(0 0 0 / 0.2); + --media-surface-strong: rgb(0 0 0 / 0.3); + --media-border: var(--color-zinc-700); + --notice-warning-bg: rgb(245 158 11 / 0.14); + --notice-warning-border: rgb(251 191 36 / 0.22); + --notice-warning-text: rgb(254 243 199 / 0.96); + --notice-danger-bg: rgb(244 63 94 / 0.14); + --notice-danger-border: rgb(251 113 133 / 0.22); + --notice-danger-text: rgb(255 228 230 / 0.96); + --pill-warning-bg: rgb(245 158 11 / 0.14); + --pill-warning-border: rgb(251 191 36 / 0.22); + --pill-warning-text: rgb(254 243 199 / 0.96); + --pill-danger-bg: rgb(244 63 94 / 0.14); + --pill-danger-border: rgb(251 113 133 / 0.22); + --pill-danger-text: rgb(255 228 230 / 0.96); + --pill-success-bg: rgb(16 185 129 / 0.14); + --pill-success-border: rgb(52 211 153 / 0.22); + --pill-success-text: rgb(209 250 229 / 0.96); + --pill-info-bg: rgb(14 165 233 / 0.14); + --pill-info-border: rgb(56 189 248 / 0.22); + --pill-info-text: rgb(224 242 254 / 0.96); + --pill-accent-bg: rgb(139 92 246 / 0.14); + --pill-accent-border: rgb(167 139 250 / 0.22); + --pill-accent-text: rgb(237 233 254 / 0.96); + --pill-neutral-bg: rgb(36 49 69 / 0.9); + --pill-neutral-border: rgb(93 109 135 / 0.22); + --pill-neutral-text: rgb(148 163 184 / 0.96); + --code-danger-text: rgb(251 113 133 / 0.96); + --code-warning-text: rgb(253 224 71 / 0.96); --chip-bg: rgb(36 49 69 / 0.9); --chip-bg-hover: rgb(48 63 87 / 0.96); --chip-border: rgb(93 109 135 / 0.82); @@ -247,8 +403,8 @@ html.theme-dark .app-header { } .brand-badge { - background: linear-gradient(135deg, #ffb36b 0%, #ff8552 48%, #d9481c 100%); - box-shadow: 0 12px 28px rgb(234 88 12 / 0.2); + background: linear-gradient(135deg, var(--brand-badge-start) 0%, var(--brand-badge-mid) 48%, var(--brand-badge-end) 100%); + box-shadow: 0 12px 28px var(--brand-badge-shadow); border-radius: var(--radius-badge); } @@ -335,6 +491,233 @@ html.theme-dark .brand-card-subtle { box-shadow: 0 8px 20px var(--button-shadow); } +.status-pill-online { + background: var(--status-online-bg); + border-color: var(--status-online-border); + color: var(--status-online-text); +} + +.status-pill-offline { + background: var(--status-offline-bg); + border-color: var(--status-offline-border); + color: var(--status-offline-text); +} + +.status-dot-online { + background: var(--status-online-dot); + box-shadow: 0 0 8px var(--status-online-glow); +} + +.status-dot-offline { + background: var(--status-offline-dot); + box-shadow: 0 0 8px var(--status-offline-glow); +} + +.gateway-token-indicator { + background: var(--color-indigo-500); + box-shadow: 0 0 14px var(--token-indicator-glow); +} + +.card-icon-shell { + box-shadow: inset 0 1px 0 var(--card-icon-inset); +} + +.topology-node-highlight { + box-shadow: 0 0 30px var(--topology-node-highlight-shadow); +} + +.topology-node-base { + box-shadow: 0 20px 40px var(--topology-node-base-shadow); +} + +.group:hover .topology-node-base { + box-shadow: + 0 20px 40px var(--topology-node-base-shadow), + 0 0 20px var(--topology-node-hover-shadow); +} + +.topology-node-inner-border { + border-color: var(--topology-node-inner-border); +} + +.topology-node-border-highlight { + border-color: var(--topology-node-highlight-border); +} + +.topology-online-indicator { + box-shadow: 0 0 10px var(--topology-online-glow); +} + +.topology-tooltip { + box-shadow: 0 24px 48px var(--topology-tooltip-shadow); +} + +.ekg-bar-fill { + background: linear-gradient(90deg, var(--ekg-bar-start), var(--ekg-bar-end)); +} + +.ui-overlay-soft { + background: var(--overlay-scrim-soft); +} + +.ui-overlay-medium { + background: var(--overlay-scrim-medium); +} + +.ui-overlay-strong { + background: var(--overlay-scrim-strong); +} + +.ui-media-surface { + background: var(--media-surface); + border-color: var(--media-border); +} + +.ui-media-surface-strong { + background: var(--media-surface-strong); + border-color: var(--media-border); +} + +.ui-notice-warning { + background: var(--notice-warning-bg); + border-color: var(--notice-warning-border); + color: var(--notice-warning-text); +} + +.ui-notice-danger { + background: var(--notice-danger-bg); + border-color: var(--notice-danger-border); + color: var(--notice-danger-text); +} + +.ui-pill { + border: 1px solid transparent; +} + +.ui-pill-warning { + background: var(--pill-warning-bg); + border-color: var(--pill-warning-border); + color: var(--pill-warning-text); +} + +.ui-pill-danger { + background: var(--pill-danger-bg); + border-color: var(--pill-danger-border); + color: var(--pill-danger-text); +} + +.ui-pill-success { + background: var(--pill-success-bg); + border-color: var(--pill-success-border); + color: var(--pill-success-text); +} + +.ui-pill-info { + background: var(--pill-info-bg); + border-color: var(--pill-info-border); + color: var(--pill-info-text); +} + +.ui-pill-accent { + background: var(--pill-accent-bg); + border-color: var(--pill-accent-border); + color: var(--pill-accent-text); +} + +.ui-pill-neutral { + background: var(--pill-neutral-bg); + border-color: var(--pill-neutral-border); + color: var(--pill-neutral-text); +} + +.ui-code-danger { + color: var(--code-danger-text); +} + +.ui-code-warning { + color: var(--code-warning-text); +} + +.avatar-user, +.avatar-agent, +.avatar-tool, +.avatar-system, +.avatar-tone-1, +.avatar-tone-2, +.avatar-tone-3, +.avatar-tone-4, +.avatar-tone-5, +.avatar-tone-6, +.avatar-tone-7 { + color: var(--color-zinc-950); +} + +.avatar-user { + background: color-mix(in srgb, var(--color-indigo-600) 88%, transparent); +} + +.avatar-agent { + background: color-mix(in srgb, var(--color-emerald-500) 80%, transparent); +} + +.avatar-tool { + background: color-mix(in srgb, var(--color-amber-500) 80%, transparent); +} + +.avatar-system { + background: color-mix(in srgb, var(--color-zinc-700) 92%, transparent); + color: var(--color-zinc-100); +} + +.avatar-tone-1 { background: color-mix(in srgb, var(--color-emerald-500) 80%, transparent); } +.avatar-tone-2 { background: color-mix(in srgb, var(--color-sky-500) 80%, transparent); } +.avatar-tone-3 { background: color-mix(in srgb, var(--color-violet-500) 80%, transparent); } +.avatar-tone-4 { background: color-mix(in srgb, var(--color-amber-500) 80%, transparent); } +.avatar-tone-5 { background: color-mix(in srgb, var(--color-rose-500) 80%, transparent); } +.avatar-tone-6 { background: color-mix(in srgb, var(--color-sky-400) 80%, transparent); } +.avatar-tone-7 { background: color-mix(in srgb, var(--color-fuchsia-400) 80%, transparent); } + +.chat-meta-user { + color: rgb(255 255 255 / 0.75); +} + +.chat-submeta-user { + color: rgb(255 255 255 / 0.7); +} + +.chat-meta-tool { + color: color-mix(in srgb, var(--button-warning-text) 78%, transparent); +} + +.chat-submeta-tool { + color: color-mix(in srgb, var(--button-warning-text) 84%, transparent); +} + +.ui-dot-live { + background: var(--color-emerald-500); +} + +.ui-dot-neutral { + background: var(--color-zinc-600); +} + +.ui-text-danger-hover:hover { + color: var(--color-red-500); +} + +.ui-card-active-warning { + border-color: var(--color-amber-500); + background: var(--pill-warning-bg); +} + +.topology-accent-warning { + background: var(--color-amber-500); +} + +.topology-icon-danger { + color: var(--color-red-500); +} + html.theme-dark .brand-button { box-shadow: 0 10px 24px var(--button-shadow); } diff --git a/webui/src/pages/Chat.tsx b/webui/src/pages/Chat.tsx index fa5c84c..a7552d6 100644 --- a/webui/src/pages/Chat.tsx +++ b/webui/src/pages/Chat.tsx @@ -68,13 +68,13 @@ function formatAgentName(agentID?: string): string { function avatarSeed(key?: string): string { const palette = [ - 'bg-emerald-600/80 text-white', - 'bg-sky-600/80 text-white', - 'bg-violet-600/80 text-white', - 'bg-amber-600/80 text-white', - 'bg-rose-600/80 text-white', - 'bg-cyan-600/80 text-white', - 'bg-fuchsia-600/80 text-white', + 'avatar-tone-1', + 'avatar-tone-2', + 'avatar-tone-3', + 'avatar-tone-4', + 'avatar-tone-5', + 'avatar-tone-6', + 'avatar-tone-7', ]; const source = String(key || 'agent'); let hash = 0; @@ -192,12 +192,12 @@ const Chat: React.FC = () => { const actorName = role === 'user' ? t('user') : role === 'tool' || role === 'exec' ? t('exec') : role === 'system' ? t('system') : t('agent'); const avatarClassName = role === 'user' - ? 'bg-indigo-600/90 text-white' + ? 'avatar-user' : role === 'tool' || role === 'exec' - ? 'bg-amber-600/80 text-white' + ? 'avatar-tool' : role === 'system' - ? 'bg-zinc-700 text-zinc-100' - : 'bg-emerald-600/80 text-white'; + ? 'avatar-system' + : 'avatar-agent'; return { id: `${targetSessionKey}-${index}`, @@ -312,7 +312,7 @@ const Chat: React.FC = () => { label: t('user'), actorName: t('user'), avatarText: 'U', - avatarClassName: 'bg-indigo-600/90 text-white', + avatarClassName: 'avatar-user', }]); const currentMsg = msg; @@ -334,7 +334,7 @@ const Chat: React.FC = () => { label: t('agent'), actorName: t('agent'), avatarText: 'A', - avatarClassName: 'bg-emerald-600/80 text-white', + avatarClassName: 'avatar-agent', }]); await new Promise((resolve, reject) => { @@ -656,23 +656,23 @@ const Chat: React.FC = () => { const active = dispatchAgentID === agent.agent_id; const badge = runtimeBadgeByAgent[String(agent.agent_id || '')]; const badgeClass = badge?.status === 'running' - ? 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30' + ? 'ui-pill-success' : badge?.status === 'waiting' - ? 'bg-amber-500/15 text-amber-300 border-amber-500/30' + ? 'ui-pill-warning' : badge?.status === 'failed' - ? 'bg-rose-500/15 text-rose-300 border-rose-500/30' + ? 'ui-pill-danger' : badge?.status === 'completed' - ? 'bg-sky-500/15 text-sky-300 border-sky-500/30' - : 'bg-zinc-800 text-zinc-400 border-zinc-700'; + ? 'ui-pill-info' + : 'ui-pill-neutral'; return ( @@ -711,14 +711,14 @@ const Chat: React.FC = () => { ? 'chat-bubble-system rounded-bl-sm' : 'chat-bubble-agent rounded-bl-sm'; const metaClass = isUser - ? 'text-white/75' + ? 'chat-meta-user' : isExec - ? 'text-amber-800/75 dark:text-amber-100/75' + ? 'chat-meta-tool' : 'text-zinc-500 dark:text-zinc-400'; const subLabelClass = isUser - ? 'text-white/70' + ? 'chat-submeta-user' : isExec - ? 'text-amber-700/80 dark:text-amber-100/70' + ? 'chat-submeta-tool' : 'text-zinc-500 dark:text-zinc-400'; return ( @@ -729,7 +729,7 @@ const Chat: React.FC = () => { className={`flex ${isUser ? 'justify-end' : 'justify-start'}`} >
-
{m.avatarText || (isUser ? 'U' : 'A')}
+
{m.avatarText || (isUser ? 'U' : 'A')}
{m.actorName || m.label || (isUser ? t('user') : isExec ? t('exec') : isSystem ? t('system') : t('agent'))}
@@ -774,7 +774,7 @@ const Chat: React.FC = () => { diff --git a/webui/src/pages/Config.tsx b/webui/src/pages/Config.tsx index e89c104..7a11b12 100644 --- a/webui/src/pages/Config.tsx +++ b/webui/src/pages/Config.tsx @@ -598,7 +598,7 @@ const Config: React.FC = () => {
{showDiff && ( -
+
{t('configDiffPreviewCount', { count: diffRows.length })}
diff --git a/webui/src/pages/Cron.tsx b/webui/src/pages/Cron.tsx index d59daa5..d795cac 100644 --- a/webui/src/pages/Cron.tsx +++ b/webui/src/pages/Cron.tsx @@ -253,7 +253,7 @@ const Cron: React.FC = () => { animate={{ opacity: 1 }} exit={{ opacity: 0 }} onClick={() => setIsCronModalOpen(false)} - className="absolute inset-0 bg-black/60 backdrop-blur-sm" + className="ui-overlay-strong absolute inset-0 backdrop-blur-sm" /> { {t('webui')}: {webuiVersion}
-
} /> - } /> - } /> - } /> + } /> + } /> + } /> } /> } />
@@ -172,7 +172,7 @@ const Dashboard: React.FC = () => {
{t('nodeArtifactsRetentionHint')}
-
+
{artifactRetentionEnabled ? t('enabled') : t('disabled')}
@@ -214,7 +214,7 @@ const Dashboard: React.FC = () => {
{String(alert?.title || '-')}
{String(alert?.node || '-')} · {String(alert?.kind || '-')}
-
+
{severity}
@@ -242,7 +242,7 @@ const Dashboard: React.FC = () => {
{task.task_id || `task-${index + 1}`}
{task.channel || '-'} · {task.source || '-'}
-
+
{task.status || '-'}
@@ -304,7 +304,7 @@ const Dashboard: React.FC = () => { {t('dashboardNodeP2PSessionCreated')}: {session.createdAt}
-
+
{session.status}
@@ -356,7 +356,7 @@ const Dashboard: React.FC = () => {
{`${item.node} · ${item.action}`}
{item.time}
-
+
{item.ok ? 'ok' : 'error'}
@@ -407,13 +407,13 @@ const Dashboard: React.FC = () => {
{String(artifact?.storage || '-')}
{isImage && dataUrl && ( - {String(artifact?.name + {String(artifact?.name )} {isVideo && dataUrl && ( -