mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-04 09:07:29 +08:00
Release v0.1.0 agent topology and runtime refresh
This commit is contained in:
@@ -34,6 +34,7 @@ const Config: React.FC = () => {
|
||||
);
|
||||
|
||||
const hotPrefixes = useMemo(() => hotReloadFieldDetails.map((x) => String(x.path || '').replace(/\.\*$/, '')).filter(Boolean), [hotReloadFieldDetails]);
|
||||
const hotReloadTabKey = '__hot_reload__';
|
||||
|
||||
const allTopKeys = useMemo(() => Object.keys(cfg || {}).filter(k => typeof (cfg as any)?.[k] === 'object' && (cfg as any)?.[k] !== null), [cfg]);
|
||||
const basicTopKeys = useMemo(() => {
|
||||
@@ -50,7 +51,7 @@ const Config: React.FC = () => {
|
||||
const s = search.trim().toLowerCase();
|
||||
keys = keys.filter((k) => k.toLowerCase().includes(s));
|
||||
}
|
||||
return keys;
|
||||
return [hotReloadTabKey, ...keys];
|
||||
}, [allTopKeys, basicTopKeys, basicMode, hotOnly, search, hotPrefixes]);
|
||||
|
||||
const [selectedTop, setSelectedTop] = useState<string>('');
|
||||
@@ -193,7 +194,7 @@ const Config: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-8 max-w-7xl mx-auto space-y-6 flex flex-col min-h-full">
|
||||
<div className="p-4 md:p-8 w-full space-y-6 flex flex-col min-h-full">
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||
<h1 className="text-2xl font-semibold tracking-tight">{t('configuration')}</h1>
|
||||
<div className="flex items-center gap-1 bg-zinc-900/80 p-1 rounded-lg border border-zinc-800">
|
||||
@@ -222,18 +223,6 @@ const Config: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-zinc-900/40 border border-zinc-800/80 rounded-2xl p-4">
|
||||
<div className="text-sm font-semibold text-zinc-300 mb-2">{t('configHotFieldsFull')}</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-xs">
|
||||
{hotReloadFieldDetails.map((it) => (
|
||||
<div key={it.path} className="p-2 rounded bg-zinc-950 border border-zinc-800">
|
||||
<div className="font-mono text-zinc-200">{it.path}</div>
|
||||
<div className="text-zinc-400">{it.name || ''}{it.description ? ` · ${it.description}` : ''}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 bg-zinc-900/40 border border-zinc-800/80 rounded-2xl overflow-hidden flex flex-col shadow-sm min-h-[420px]">
|
||||
{!showRaw ? (
|
||||
<div className="flex-1 flex min-h-0">
|
||||
@@ -246,13 +235,26 @@ const Config: React.FC = () => {
|
||||
onClick={() => setSelectedTop(k)}
|
||||
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${activeTop === k ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' : 'text-zinc-300 hover:bg-zinc-800/60'}`}
|
||||
>
|
||||
{configLabels[k] || k}
|
||||
{k === hotReloadTabKey ? t('configHotFieldsFull') : (configLabels[k] || k)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="flex-1 p-4 md:p-6 overflow-y-auto space-y-4">
|
||||
{activeTop === hotReloadTabKey && (
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm font-semibold text-zinc-300">{t('configHotFieldsFull')}</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-xs">
|
||||
{hotReloadFieldDetails.map((it) => (
|
||||
<div key={it.path} className="p-2 rounded bg-zinc-950 border border-zinc-800">
|
||||
<div className="font-mono text-zinc-200">{it.path}</div>
|
||||
<div className="text-zinc-400">{it.name || ''}{it.description ? ` · ${it.description}` : ''}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeTop === 'providers' && !showRaw && (
|
||||
<div className="rounded-xl border border-zinc-800 bg-zinc-950/40 p-3 space-y-3">
|
||||
<div className="flex items-center justify-between gap-2 flex-wrap">
|
||||
@@ -278,7 +280,7 @@ const Config: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeTop ? (
|
||||
{activeTop && activeTop !== hotReloadTabKey ? (
|
||||
<RecursiveConfig
|
||||
data={(cfg as any)?.[activeTop] || {}}
|
||||
labels={configLabels}
|
||||
|
||||
@@ -8,6 +8,7 @@ type SubagentProfile = {
|
||||
name?: string;
|
||||
role?: string;
|
||||
system_prompt?: string;
|
||||
system_prompt_file?: string;
|
||||
tool_allowlist?: string[];
|
||||
memory_namespace?: string;
|
||||
max_retries?: number;
|
||||
@@ -32,6 +33,7 @@ const emptyDraft: SubagentProfile = {
|
||||
name: '',
|
||||
role: '',
|
||||
system_prompt: '',
|
||||
system_prompt_file: '',
|
||||
memory_namespace: '',
|
||||
status: 'active',
|
||||
tool_allowlist: [],
|
||||
@@ -52,6 +54,8 @@ const SubagentProfiles: React.FC = () => {
|
||||
const [draft, setDraft] = useState<SubagentProfile>(emptyDraft);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [groups, setGroups] = useState<ToolAllowlistGroup[]>([]);
|
||||
const [promptFileContent, setPromptFileContent] = useState('');
|
||||
const [promptFileFound, setPromptFileFound] = useState(false);
|
||||
|
||||
const selected = useMemo(
|
||||
() => items.find((p) => p.agent_id === selectedId) || null,
|
||||
@@ -77,6 +81,7 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: next.name || '',
|
||||
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',
|
||||
tool_allowlist: Array.isArray(next.tool_allowlist) ? next.tool_allowlist : [],
|
||||
@@ -103,6 +108,33 @@ const SubagentProfiles: React.FC = () => {
|
||||
loadGroups().catch(() => {});
|
||||
}, [q]);
|
||||
|
||||
useEffect(() => {
|
||||
const path = String(draft.system_prompt_file || '').trim();
|
||||
if (!path) {
|
||||
setPromptFileContent('');
|
||||
setPromptFileFound(false);
|
||||
return;
|
||||
}
|
||||
fetch(`/webui/api/subagents_runtime${q}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'prompt_file_get', path }),
|
||||
})
|
||||
.then(async (r) => {
|
||||
if (!r.ok) throw new Error(await r.text());
|
||||
return r.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const found = data?.result?.found === true;
|
||||
setPromptFileFound(found);
|
||||
setPromptFileContent(found ? String(data?.result?.content || '') : '');
|
||||
})
|
||||
.catch(() => {
|
||||
setPromptFileFound(false);
|
||||
setPromptFileContent('');
|
||||
});
|
||||
}, [draft.system_prompt_file, q]);
|
||||
|
||||
const onSelect = (p: SubagentProfile) => {
|
||||
setSelectedId(p.agent_id || '');
|
||||
setDraft({
|
||||
@@ -110,6 +142,7 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: p.name || '',
|
||||
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',
|
||||
tool_allowlist: Array.isArray(p.tool_allowlist) ? p.tool_allowlist : [],
|
||||
@@ -162,6 +195,7 @@ const SubagentProfiles: React.FC = () => {
|
||||
name: draft.name || '',
|
||||
role: draft.role || '',
|
||||
system_prompt: draft.system_prompt || '',
|
||||
system_prompt_file: draft.system_prompt_file || '',
|
||||
memory_namespace: draft.memory_namespace || '',
|
||||
status: draft.status || 'active',
|
||||
tool_allowlist: draft.tool_allowlist || [],
|
||||
@@ -218,6 +252,25 @@ const SubagentProfiles: React.FC = () => {
|
||||
await load();
|
||||
};
|
||||
|
||||
const savePromptFile = async () => {
|
||||
const path = String(draft.system_prompt_file || '').trim();
|
||||
if (!path) {
|
||||
await ui.notify({ title: t('requestFailed'), message: 'system_prompt_file is required' });
|
||||
return;
|
||||
}
|
||||
const r = await fetch(`/webui/api/subagents_runtime${q}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'prompt_file_set', path, content: promptFileContent }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
await ui.notify({ title: t('requestFailed'), message: await r.text() });
|
||||
return;
|
||||
}
|
||||
setPromptFileFound(true);
|
||||
await ui.notify({ title: t('saved'), message: t('promptFileSaved') });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full p-4 md:p-6 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -297,6 +350,15 @@ const SubagentProfiles: React.FC = () => {
|
||||
<option value="disabled">disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="text-xs text-zinc-400 mb-1">system_prompt_file</div>
|
||||
<input
|
||||
value={draft.system_prompt_file || ''}
|
||||
onChange={(e) => setDraft({ ...draft, system_prompt_file: e.target.value })}
|
||||
className="w-full px-2 py-1 text-xs bg-zinc-900 border border-zinc-700 rounded"
|
||||
placeholder="agents/coder/AGENT.md"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<div className="text-xs text-zinc-400 mb-1">{t('memoryNamespace')}</div>
|
||||
<input
|
||||
@@ -339,6 +401,28 @@ const SubagentProfiles: React.FC = () => {
|
||||
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>
|
||||
<div className="text-[11px] text-zinc-500">{promptFileFound ? t('promptFileReady') : t('promptFileMissing')}</div>
|
||||
</div>
|
||||
<textarea
|
||||
value={promptFileContent}
|
||||
onChange={(e) => setPromptFileContent(e.target.value)}
|
||||
className="w-full px-2 py-1 text-xs bg-zinc-900 border border-zinc-700 rounded min-h-[220px]"
|
||||
placeholder="AGENT.md content..."
|
||||
/>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={savePromptFile}
|
||||
disabled={!String(draft.system_prompt_file || '').trim()}
|
||||
className="px-3 py-1.5 text-xs rounded bg-zinc-800 hover:bg-zinc-700 disabled:opacity-50"
|
||||
>
|
||||
{t('savePromptFile')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-zinc-400 mb-1">Max Retries</div>
|
||||
<input
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user