mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-15 05:07:30 +08:00
fix: enforce subagent prompt files and refine webui
This commit is contained in:
@@ -54,11 +54,10 @@ const Header: React.FC = () => {
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="flex items-center gap-2 text-sm font-medium text-zinc-400 hover:text-zinc-200 transition-colors bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 px-3 py-1.5 rounded-lg"
|
||||
className="inline-flex h-9 w-9 items-center justify-center text-sm font-medium text-zinc-400 hover:text-zinc-200 transition-colors bg-zinc-900 hover:bg-zinc-800 border border-zinc-800 rounded-lg"
|
||||
title={theme === 'dark' ? t('themeLight') : t('themeDark')}
|
||||
>
|
||||
{theme === 'dark' ? <SunMedium className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
|
||||
<span className="hidden sm:inline">{theme === 'dark' ? t('themeLight') : t('themeDark')}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
interface NavItemProps {
|
||||
icon: React.ReactNode;
|
||||
@@ -9,17 +10,26 @@ interface NavItemProps {
|
||||
}
|
||||
|
||||
const NavItem: React.FC<NavItemProps> = ({ icon, label, to, collapsed = false }) => (
|
||||
<NavLink
|
||||
<NavLink
|
||||
to={to}
|
||||
title={collapsed ? label : undefined}
|
||||
className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 ${
|
||||
className={({ isActive }) => `w-full flex items-center ${collapsed ? 'justify-center' : 'gap-3'} px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200 border ${
|
||||
isActive
|
||||
? 'nav-item-active text-indigo-700 border border-indigo-500/30'
|
||||
: 'text-zinc-400 hover:bg-zinc-800/30 hover:text-zinc-200 border border-transparent'
|
||||
? 'nav-item-active text-indigo-700 border-indigo-500/30'
|
||||
: 'text-zinc-400 border-transparent'
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
{!collapsed && label}
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<span className="shrink-0">{icon}</span>
|
||||
{!collapsed && <span className="min-w-0 flex-1 truncate">{label}</span>}
|
||||
{!collapsed && isActive && (
|
||||
<span className="ml-auto inline-flex h-5 w-5 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
|
||||
@@ -14,17 +14,21 @@ const Sidebar: React.FC = () => {
|
||||
items: [
|
||||
{ icon: <LayoutDashboard className="w-5 h-5" />, label: t('dashboard'), to: '/' },
|
||||
{ icon: <MessageSquare className="w-5 h-5" />, label: t('chat'), to: '/chat' },
|
||||
{ icon: <Boxes className="w-5 h-5" />, label: t('subagentsRuntime'), to: '/subagents' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('sidebarRuntime'),
|
||||
title: t('sidebarAgents'),
|
||||
items: [
|
||||
{ icon: <Boxes className="w-5 h-5" />, label: t('subagentsRuntime'), to: '/subagents' },
|
||||
{ icon: <Bot className="w-5 h-5" />, label: t('subagentProfiles'), to: '/subagent-profiles' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('sidebarOps'),
|
||||
items: [
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('nodes'), to: '/nodes' },
|
||||
{ icon: <FolderOpen className="w-5 h-5" />, label: t('nodeArtifacts'), to: '/node-artifacts' },
|
||||
{ icon: <ClipboardList className="w-5 h-5" />, label: t('taskAudit'), to: '/task-audit' },
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('logs'), to: '/logs' },
|
||||
{ icon: <BrainCircuit className="w-5 h-5" />, label: t('ekg'), to: '/ekg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -32,7 +36,6 @@ const Sidebar: React.FC = () => {
|
||||
items: [
|
||||
{ icon: <Settings className="w-5 h-5" />, label: t('config'), to: '/config' },
|
||||
{ icon: <Plug className="w-5 h-5" />, label: t('mcpServices'), to: '/mcp' },
|
||||
{ icon: <Bot className="w-5 h-5" />, label: t('subagentProfiles'), to: '/subagent-profiles' },
|
||||
{ icon: <Clock className="w-5 h-5" />, label: t('cronJobs'), to: '/cron' },
|
||||
],
|
||||
},
|
||||
@@ -41,6 +44,13 @@ const Sidebar: React.FC = () => {
|
||||
items: [
|
||||
{ icon: <FolderOpen className="w-5 h-5" />, label: t('memory'), to: '/memory' },
|
||||
{ icon: <Zap className="w-5 h-5" />, label: t('skills'), to: '/skills' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('sidebarInsights'),
|
||||
items: [
|
||||
{ icon: <Terminal className="w-5 h-5" />, label: t('logs'), to: '/logs' },
|
||||
{ icon: <BrainCircuit className="w-5 h-5" />, label: t('ekg'), to: '/ekg' },
|
||||
{ icon: <Hash className="w-5 h-5" />, label: t('logCodes'), to: '/log-codes' },
|
||||
],
|
||||
},
|
||||
@@ -80,15 +90,10 @@ const Sidebar: React.FC = () => {
|
||||
<div className={`hidden md:flex border-t border-zinc-800 bg-zinc-900/20 ${sidebarCollapsed ? 'justify-center p-3' : 'p-3'}`}>
|
||||
<button
|
||||
onClick={() => setSidebarCollapsed((prev) => !prev)}
|
||||
className={`flex items-center ${sidebarCollapsed ? 'justify-center' : 'justify-between'} gap-3 rounded-2xl border border-zinc-800 brand-card-subtle hover:bg-zinc-900/40 text-zinc-300 transition-colors ${sidebarCollapsed ? 'w-11 h-11' : 'w-full px-3 py-2.5'}`}
|
||||
className="flex h-11 w-11 items-center justify-center rounded-2xl border border-zinc-800 brand-card-subtle hover:bg-zinc-900/40 text-zinc-300 transition-colors"
|
||||
title={sidebarCollapsed ? t('expand') : t('collapse')}
|
||||
>
|
||||
{sidebarCollapsed ? <PanelLeftOpen className="w-4 h-4" /> : (
|
||||
<>
|
||||
<span className="text-sm font-medium">{t('collapse')}</span>
|
||||
<PanelLeftClose className="w-4 h-4 shrink-0" />
|
||||
</>
|
||||
)}
|
||||
{sidebarCollapsed ? <PanelLeftOpen className="w-4 h-4" /> : <PanelLeftClose className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -21,12 +21,9 @@ type UIContextType = {
|
||||
};
|
||||
|
||||
const UIContext = createContext<UIContextType | undefined>(undefined);
|
||||
const THEME_STORAGE_KEY = 'clawgo:webui:theme';
|
||||
|
||||
function getInitialTheme(): ThemeMode {
|
||||
if (typeof window === 'undefined') return 'dark';
|
||||
const saved = window.localStorage.getItem(THEME_STORAGE_KEY);
|
||||
if (saved === 'light' || saved === 'dark') return saved;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
@@ -45,9 +42,25 @@ export const UIProvider: React.FC<{ children: React.ReactNode }> = ({ children }
|
||||
root.classList.remove('theme-light', 'theme-dark');
|
||||
root.classList.add(theme === 'dark' ? 'theme-dark' : 'theme-light');
|
||||
root.style.colorScheme = theme;
|
||||
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const applySystemTheme = (event?: MediaQueryList | MediaQueryListEvent) => {
|
||||
const matches = 'matches' in (event || media) ? (event || media).matches : media.matches;
|
||||
setTheme(matches ? 'dark' : 'light');
|
||||
};
|
||||
applySystemTheme(media);
|
||||
const onChange = (event: MediaQueryListEvent) => applySystemTheme(event);
|
||||
if (typeof media.addEventListener === 'function') {
|
||||
media.addEventListener('change', onChange);
|
||||
return () => media.removeEventListener('change', onChange);
|
||||
}
|
||||
media.addListener(onChange);
|
||||
return () => media.removeListener(onChange);
|
||||
}, []);
|
||||
|
||||
const value = useMemo<UIContextType>(() => ({
|
||||
loading,
|
||||
theme,
|
||||
|
||||
@@ -108,6 +108,7 @@ const resources = {
|
||||
subagentDeleteConfirmMessage: 'Delete subagent profile "{{id}}" permanently?',
|
||||
sidebarCore: 'Core',
|
||||
sidebarMain: 'Main',
|
||||
sidebarAgents: 'Agents',
|
||||
sidebarRuntime: 'Runtime',
|
||||
sidebarConfig: 'Configuration',
|
||||
sidebarKnowledge: 'Knowledge',
|
||||
@@ -726,6 +727,7 @@ const resources = {
|
||||
subagentDeleteConfirmMessage: '确认永久删除子代理档案 "{{id}}"?',
|
||||
sidebarCore: '核心',
|
||||
sidebarMain: '主入口',
|
||||
sidebarAgents: 'Agents',
|
||||
sidebarRuntime: '运行态',
|
||||
sidebarConfig: '配置',
|
||||
sidebarKnowledge: '知识与调试',
|
||||
|
||||
@@ -78,62 +78,62 @@ html {
|
||||
html.theme-dark {
|
||||
--color-zinc-50: #f8fafc;
|
||||
--color-zinc-100: #e2e8f0;
|
||||
--color-zinc-200: #cbd5e1;
|
||||
--color-zinc-300: #94a3b8;
|
||||
--color-zinc-400: #64748b;
|
||||
--color-zinc-500: #475569;
|
||||
--color-zinc-600: #334155;
|
||||
--color-zinc-700: #1e293b;
|
||||
--color-zinc-800: #111827;
|
||||
--color-zinc-900: #0b1220;
|
||||
--color-zinc-950: #070b13;
|
||||
--color-indigo-300: #fdba74;
|
||||
--color-indigo-400: #fb923c;
|
||||
--color-indigo-500: #f97316;
|
||||
--color-indigo-600: #ea580c;
|
||||
--color-indigo-700: #c2410c;
|
||||
--color-indigo-800: #9a3412;
|
||||
--app-bg-spot-a: rgb(249 115 22 / 0.02);
|
||||
--app-bg-spot-b: rgb(96 165 250 / 0.02);
|
||||
--app-bg-base-top: rgb(3 7 12 / 0.998);
|
||||
--app-bg-base-bottom: rgb(8 12 18 / 0.995);
|
||||
--shell-spot-a: rgb(249 115 22 / 0.018);
|
||||
--shell-spot-b: rgb(148 163 184 / 0.025);
|
||||
--main-surface-top: rgb(255 255 255 / 0.012);
|
||||
--main-surface-mid: rgb(255 255 255 / 0.008);
|
||||
--main-surface-bottom: rgb(255 255 255 / 0.002);
|
||||
--header-bg-a: rgb(10 14 20 / 0.86);
|
||||
--header-bg-b: rgb(6 10 15 / 0.82);
|
||||
--header-overlay-a: rgb(255 255 255 / 0.018);
|
||||
--header-overlay-b: rgb(255 255 255 / 0.005);
|
||||
--sidebar-bg-a: rgb(10 14 20 / 0.9);
|
||||
--sidebar-bg-b: rgb(6 10 15 / 0.88);
|
||||
--sidebar-overlay-a: rgb(255 255 255 / 0.012);
|
||||
--sidebar-overlay-b: rgb(255 255 255 / 0.003);
|
||||
--sidebar-edge: rgb(30 41 59 / 0.58);
|
||||
--sidebar-section-a: rgb(15 23 42 / 0.52);
|
||||
--sidebar-section-b: rgb(9 14 23 / 0.34);
|
||||
--active-bg: rgb(249 115 22 / 0.09);
|
||||
--active-ring: rgb(249 115 22 / 0.16);
|
||||
--card-bg-a: rgb(15 23 42 / 0.56);
|
||||
--card-bg-b: rgb(9 14 23 / 0.44);
|
||||
--card-topline: rgb(255 255 255 / 0.035);
|
||||
--card-inner-highlight: rgb(255 255 255 / 0.03);
|
||||
--card-shadow: rgb(0 0 0 / 0.16);
|
||||
--card-subtle-a: rgb(17 24 39 / 0.4);
|
||||
--card-subtle-b: rgb(9 14 23 / 0.24);
|
||||
--button-start: #fb923c;
|
||||
--button-end: #ea580c;
|
||||
--button-shadow: rgb(0 0 0 / 0.14);
|
||||
--chip-bg: rgb(30 41 59 / 0.82);
|
||||
--chip-bg-hover: rgb(51 65 85 / 0.92);
|
||||
--chip-border: rgb(71 85 105 / 0.85);
|
||||
--color-zinc-200: #d7e1ee;
|
||||
--color-zinc-300: #b8c6d8;
|
||||
--color-zinc-400: #90a4bc;
|
||||
--color-zinc-500: #6f839b;
|
||||
--color-zinc-600: #516278;
|
||||
--color-zinc-700: #243244;
|
||||
--color-zinc-800: #162131;
|
||||
--color-zinc-900: #101827;
|
||||
--color-zinc-950: #0d1522;
|
||||
--color-indigo-300: #f8c58d;
|
||||
--color-indigo-400: #f1a561;
|
||||
--color-indigo-500: #e8843a;
|
||||
--color-indigo-600: #d46a23;
|
||||
--color-indigo-700: #af4f16;
|
||||
--color-indigo-800: #8d3f13;
|
||||
--app-bg-spot-a: rgb(249 115 22 / 0.035);
|
||||
--app-bg-spot-b: rgb(56 189 248 / 0.05);
|
||||
--app-bg-base-top: rgb(9 16 28 / 0.995);
|
||||
--app-bg-base-bottom: rgb(14 22 36 / 0.992);
|
||||
--shell-spot-a: rgb(249 115 22 / 0.03);
|
||||
--shell-spot-b: rgb(96 165 250 / 0.04);
|
||||
--main-surface-top: rgb(255 255 255 / 0.026);
|
||||
--main-surface-mid: rgb(255 255 255 / 0.016);
|
||||
--main-surface-bottom: rgb(255 255 255 / 0.008);
|
||||
--header-bg-a: rgb(14 21 34 / 0.92);
|
||||
--header-bg-b: rgb(11 18 29 / 0.9);
|
||||
--header-overlay-a: rgb(255 255 255 / 0.03);
|
||||
--header-overlay-b: rgb(255 255 255 / 0.01);
|
||||
--sidebar-bg-a: rgb(13 20 33 / 0.93);
|
||||
--sidebar-bg-b: rgb(10 16 27 / 0.9);
|
||||
--sidebar-overlay-a: rgb(255 255 255 / 0.022);
|
||||
--sidebar-overlay-b: rgb(255 255 255 / 0.008);
|
||||
--sidebar-edge: rgb(71 85 105 / 0.64);
|
||||
--sidebar-section-a: rgb(18 30 49 / 0.74);
|
||||
--sidebar-section-b: rgb(12 20 34 / 0.58);
|
||||
--active-bg: rgb(232 132 58 / 0.11);
|
||||
--active-ring: rgb(241 165 97 / 0.22);
|
||||
--card-bg-a: rgb(17 28 46 / 0.82);
|
||||
--card-bg-b: rgb(11 20 34 / 0.72);
|
||||
--card-topline: rgb(255 255 255 / 0.065);
|
||||
--card-inner-highlight: rgb(255 255 255 / 0.045);
|
||||
--card-shadow: rgb(0 0 0 / 0.24);
|
||||
--card-subtle-a: rgb(22 32 50 / 0.62);
|
||||
--card-subtle-b: rgb(13 21 35 / 0.48);
|
||||
--button-start: #ee9852;
|
||||
--button-end: #d96b25;
|
||||
--button-shadow: rgb(217 107 37 / 0.18);
|
||||
--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);
|
||||
--chip-text: rgb(226 232 240 / 0.96);
|
||||
--chip-active-bg: rgb(124 45 18 / 0.36);
|
||||
--chip-active-border: rgb(251 146 60 / 0.35);
|
||||
--chip-active-text: rgb(255 237 213 / 0.96);
|
||||
--chip-group-bg: rgb(15 23 42 / 0.5);
|
||||
--chip-group-border: rgb(51 65 85 / 0.7);
|
||||
--chip-active-bg: rgb(123 58 24 / 0.28);
|
||||
--chip-active-border: rgb(232 132 58 / 0.28);
|
||||
--chip-active-text: rgb(255 237 213 / 0.92);
|
||||
--chip-group-bg: rgb(19 31 49 / 0.68);
|
||||
--chip-group-border: rgb(71 85 105 / 0.76);
|
||||
--radius-card: 18px;
|
||||
--radius-subtle: 12px;
|
||||
--radius-panel: 16px;
|
||||
|
||||
@@ -147,15 +147,11 @@ const Dashboard: React.FC = () => {
|
||||
</div>
|
||||
<div className="brand-card rounded-[28px] border border-zinc-800 p-5 min-h-[148px]">
|
||||
<div className="flex items-center gap-2 text-zinc-200 mb-2">
|
||||
<Workflow className="w-4 h-4 text-sky-400" />
|
||||
<div className="text-sm font-medium">{t('nodeP2P')}</div>
|
||||
</div>
|
||||
<div className="text-2xl font-semibold text-zinc-100 truncate">
|
||||
{p2pEnabled ? `${p2pConfiguredIce} ICE · ${p2pConfiguredStun} STUN` : t('disabled')}
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-zinc-500">
|
||||
{t('dashboardNodeP2PDetail', { transport: p2pTransport, sessions: p2pSessions, retries: p2pRetryCount })}
|
||||
<Sparkles className="w-4 h-4 text-sky-400" />
|
||||
<div className="text-sm font-medium">{t('ekgTopProvidersWorkload')}</div>
|
||||
</div>
|
||||
<div className="text-2xl font-semibold text-zinc-100 truncate">{ekgTopProvider}</div>
|
||||
<div className="mt-2 text-xs text-zinc-500">{t('dashboardWorkloadSnapshot')}</div>
|
||||
</div>
|
||||
<div className="brand-card rounded-[28px] border border-zinc-800 p-5 min-h-[148px]">
|
||||
<div className="flex items-center gap-2 text-zinc-200 mb-2">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import { formatLocalDateTime } from '../utils/time';
|
||||
|
||||
@@ -247,20 +248,29 @@ const Nodes: React.FC = () => {
|
||||
<button
|
||||
key={nodeID}
|
||||
onClick={() => setSelectedNodeID(nodeID)}
|
||||
className={`w-full text-left px-3 py-3 border-b border-zinc-800/60 hover:bg-zinc-800/20 ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
className={`w-full text-left px-3 py-3 border-b border-zinc-800/60 transition-colors ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
>
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{String(node?.name || nodeID)}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{nodeID} · {String(node?.os || '-')} / {String(node?.arch || '-')}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{String(node?.online ? t('online') : t('offline'))} · {String(node?.version || '-')}</div>
|
||||
{tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{tags.slice(0, 4).map((tag: string) => (
|
||||
<span key={`${nodeID}-${tag}`} className="rounded-full border border-zinc-700 bg-zinc-900/60 px-2 py-0.5 text-[10px] text-zinc-300">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{String(node?.name || nodeID)}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{nodeID} · {String(node?.os || '-')} / {String(node?.arch || '-')}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{String(node?.online ? t('online') : t('offline'))} · {String(node?.version || '-')}</div>
|
||||
{tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{tags.slice(0, 4).map((tag: string) => (
|
||||
<span key={`${nodeID}-${tag}`} className="rounded-full border border-zinc-700 bg-zinc-900/60 px-2 py-0.5 text-[10px] text-zinc-300">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{active && (
|
||||
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -416,10 +426,19 @@ const Nodes: React.FC = () => {
|
||||
<button
|
||||
key={key || `dispatch-${index}`}
|
||||
onClick={() => setSelectedDispatchKey(key)}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 hover:bg-zinc-800/20 ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 transition-colors ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
>
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{`${item?.action || '-'} · ${item?.used_transport || '-'}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{formatLocalDateTime(item?.time)} · {Number(item?.duration_ms || 0)}ms · {Number(item?.artifact_count || 0)} {t('dashboardNodeDispatchArtifacts')}</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{`${item?.action || '-'} · ${item?.used_transport || '-'}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{formatLocalDateTime(item?.time)} · {Number(item?.duration_ms || 0)}ms · {Number(item?.artifact_count || 0)} {t('dashboardNodeDispatchArtifacts')}</div>
|
||||
</div>
|
||||
{active && (
|
||||
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -202,13 +202,13 @@ const Skills: React.FC = () => {
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className={`text-xs px-2 py-1 rounded-md border ${clawhubInstalled ? 'text-emerald-300 border-emerald-700/50 bg-emerald-900/20' : 'text-amber-300 border-amber-700/50 bg-amber-900/20'}`} title={clawhubPath || t('skillsClawhubNotFound')}>
|
||||
<div className={`text-xs px-2 py-1 rounded-md border font-medium ${clawhubInstalled ? 'text-emerald-200 border-emerald-500/35 bg-emerald-500/12' : 'text-amber-100 border-amber-500/45 bg-amber-500/14 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]'}`} title={clawhubPath || t('skillsClawhubNotFound')}>
|
||||
{t('skillsClawhubStatus')}: {clawhubInstalled ? t('installed') : t('notInstalled')}
|
||||
</div>
|
||||
{!clawhubInstalled && (
|
||||
<button
|
||||
onClick={installClawHubIfNeeded}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-amber-600 hover:bg-amber-500 text-white rounded-xl text-sm font-medium transition-colors shadow-sm"
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-colors shadow-sm bg-cyan-500/15 text-cyan-200 border border-cyan-400/25 hover:bg-cyan-500/25 hover:border-cyan-300/35"
|
||||
>
|
||||
<Zap className="w-4 h-4" /> {t('skillsInstallNow')}
|
||||
</button>
|
||||
@@ -223,9 +223,16 @@ const Skills: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{!clawhubInstalled && (
|
||||
<div className="rounded-2xl border border-amber-700/40 bg-amber-950/20 p-4 text-sm text-amber-100">
|
||||
<div className="font-medium mb-1">{t('skillsClawhubMissingTitle')}</div>
|
||||
<div className="text-amber-200/90">{t('skillsInstallPanelHint')}</div>
|
||||
<div className="rounded-2xl border border-zinc-800/80 bg-zinc-950/45 p-4 text-sm shadow-sm">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-amber-400/20 bg-amber-500/10 text-amber-300">
|
||||
<Zap className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium text-zinc-100 mb-1">{t('skillsClawhubMissingTitle')}</div>
|
||||
<div className="text-zinc-400 leading-6">{t('skillsInstallPanelHint')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import { useUI } from '../context/UIContext';
|
||||
|
||||
@@ -8,7 +9,6 @@ type SubagentProfile = {
|
||||
name?: string;
|
||||
notify_main_policy?: string;
|
||||
role?: string;
|
||||
system_prompt?: string;
|
||||
system_prompt_file?: string;
|
||||
tool_allowlist?: string[];
|
||||
memory_namespace?: string;
|
||||
@@ -33,7 +33,6 @@ const emptyDraft: SubagentProfile = {
|
||||
name: '',
|
||||
notify_main_policy: 'final_only',
|
||||
role: '',
|
||||
system_prompt: '',
|
||||
system_prompt_file: '',
|
||||
memory_namespace: '',
|
||||
status: 'active',
|
||||
@@ -81,7 +80,6 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: next.name || '',
|
||||
notify_main_policy: next.notify_main_policy || 'final_only',
|
||||
role: next.role || '',
|
||||
system_prompt: next.system_prompt || '',
|
||||
system_prompt_file: next.system_prompt_file || '',
|
||||
memory_namespace: next.memory_namespace || '',
|
||||
status: (next.status as string) || 'active',
|
||||
@@ -142,7 +140,6 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: p.name || '',
|
||||
notify_main_policy: p.notify_main_policy || 'final_only',
|
||||
role: p.role || '',
|
||||
system_prompt: p.system_prompt || '',
|
||||
system_prompt_file: p.system_prompt_file || '',
|
||||
memory_namespace: p.memory_namespace || '',
|
||||
status: (p.status as string) || 'active',
|
||||
@@ -195,7 +192,6 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: draft.name || '',
|
||||
notify_main_policy: draft.notify_main_policy || 'final_only',
|
||||
role: draft.role || '',
|
||||
system_prompt: draft.system_prompt || '',
|
||||
system_prompt_file: draft.system_prompt_file || '',
|
||||
memory_namespace: draft.memory_namespace || '',
|
||||
status: draft.status || 'active',
|
||||
@@ -295,11 +291,20 @@ const SubagentProfiles: React.FC = () => {
|
||||
<button
|
||||
key={it.agent_id}
|
||||
onClick={() => onSelect(it)}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/50 hover:bg-zinc-800/20 ${selectedId === it.agent_id ? 'bg-indigo-500/15' : ''}`}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/50 transition-colors ${selectedId === it.agent_id ? 'bg-indigo-500/15' : ''}`}
|
||||
>
|
||||
<div className="text-sm text-zinc-100 truncate">{it.agent_id || '-'}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">
|
||||
{(it.status || 'active')} · {it.role || '-'} · {(it.memory_namespace || '-')}
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm text-zinc-100 truncate">{it.agent_id || '-'}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">
|
||||
{(it.status || 'active')} · {it.role || '-'} · {(it.memory_namespace || '-')}
|
||||
</div>
|
||||
</div>
|
||||
{selectedId === it.agent_id && (
|
||||
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -409,15 +414,6 @@ const SubagentProfiles: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="text-xs text-zinc-400 mb-1">System Prompt</div>
|
||||
<textarea
|
||||
value={draft.system_prompt || ''}
|
||||
onChange={(e) => setDraft({ ...draft, system_prompt: e.target.value })}
|
||||
className="w-full px-2 py-1 text-xs bg-zinc-900 border border-zinc-700 rounded min-h-[140px]"
|
||||
placeholder="You are a coding specialist..."
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="flex items-center justify-between mb-1 gap-3">
|
||||
<div className="text-xs text-zinc-400">system_prompt_file content</div>
|
||||
|
||||
@@ -91,7 +91,6 @@ type RegistrySubagent = {
|
||||
display_name?: string;
|
||||
role?: string;
|
||||
description?: string;
|
||||
system_prompt?: string;
|
||||
system_prompt_file?: string;
|
||||
prompt_file_found?: boolean;
|
||||
memory_namespace?: string;
|
||||
@@ -392,7 +391,6 @@ const Subagents: React.FC = () => {
|
||||
const [configAgentID, setConfigAgentID] = useState('');
|
||||
const [configRole, setConfigRole] = useState('');
|
||||
const [configDisplayName, setConfigDisplayName] = useState('');
|
||||
const [configSystemPrompt, setConfigSystemPrompt] = useState('');
|
||||
const [configSystemPromptFile, setConfigSystemPromptFile] = useState('');
|
||||
const [configToolAllowlist, setConfigToolAllowlist] = useState('');
|
||||
const [configRoutingKeywords, setConfigRoutingKeywords] = useState('');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import { formatLocalDateTime } from '../utils/time';
|
||||
|
||||
@@ -143,11 +144,20 @@ const TaskAudit: React.FC = () => {
|
||||
<button
|
||||
key={`${it.task_id || idx}-${it.time || idx}`}
|
||||
onClick={() => setSelected(it)}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 hover:bg-zinc-800/20 ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 transition-colors ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
>
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{it.task_id || `task-${idx + 1}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{it.channel || '-'} · {it.status} · attempts:{it.attempts || 1} · {it.duration_ms || 0}ms · retry:{it.retry_count || 0} · {it.source || '-'} · {it.provider || '-'} / {it.model || '-'}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{formatLocalDateTime(it.time)}</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{it.task_id || `task-${idx + 1}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{it.channel || '-'} · {it.status} · attempts:{it.attempts || 1} · {it.duration_ms || 0}ms · retry:{it.retry_count || 0} · {it.source || '-'} · {it.provider || '-'} / {it.model || '-'}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{formatLocalDateTime(it.time)}</div>
|
||||
</div>
|
||||
{active && (
|
||||
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -240,11 +250,20 @@ const TaskAudit: React.FC = () => {
|
||||
<button
|
||||
key={`${it.time || idx}-${it.node || idx}-${it.action || idx}`}
|
||||
onClick={() => setSelectedNode(it)}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 hover:bg-zinc-800/20 ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
className={`w-full text-left px-3 py-2 border-b border-zinc-800/60 transition-colors ${active ? 'bg-indigo-500/15' : ''}`}
|
||||
>
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{`${it.node || '-'} · ${it.action || '-'}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{it.used_transport || '-'} · {(it.duration_ms || 0)}ms · {(it.artifact_count || 0)} {t('dashboardNodeDispatchArtifacts')}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{formatLocalDateTime(it.time)}</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium text-zinc-100 truncate">{`${it.node || '-'} · ${it.action || '-'}`}</div>
|
||||
<div className="text-xs text-zinc-400 truncate">{it.used_transport || '-'} · {(it.duration_ms || 0)}ms · {(it.artifact_count || 0)} {t('dashboardNodeDispatchArtifacts')}</div>
|
||||
<div className="text-[11px] text-zinc-500 truncate">{formatLocalDateTime(it.time)}</div>
|
||||
</div>
|
||||
{active && (
|
||||
<span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/15 text-indigo-300">
|
||||
<Check className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user