fix: enforce subagent prompt files and refine webui

This commit is contained in:
lpf
2026-03-09 11:24:31 +08:00
parent ba3be33c91
commit acf8a22c0a
25 changed files with 257 additions and 211 deletions

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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>

View File

@@ -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,

View File

@@ -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: '知识与调试',

View File

@@ -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;

View File

@@ -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">

View File

@@ -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>
);
})}

View File

@@ -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>
)}

View File

@@ -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>

View File

@@ -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('');

View File

@@ -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>
);
})}