feat(settings): add Tier 1 agent config panel with system prompt editor
- Agent cards in SettingsTab: name, enable/disable, CWD, model, wake interval - Custom Context textarea for editable system prompt per agent - Collapsible preview of full generated introductory prompt - Agent cards styled with mauve left border accent and role badge - Export AGENT_ROLE_ICONS from groups.ts, add updateAgent() to workspace store
This commit is contained in:
parent
a158ed9544
commit
14808a97e9
2 changed files with 229 additions and 2 deletions
|
|
@ -6,13 +6,15 @@
|
|||
getActiveGroupId,
|
||||
getAllGroups,
|
||||
updateProject,
|
||||
updateAgent,
|
||||
addProject,
|
||||
removeProject,
|
||||
addGroup,
|
||||
removeGroup,
|
||||
switchGroup,
|
||||
} from '../../stores/workspace.svelte';
|
||||
import { deriveIdentifier } from '../../types/groups';
|
||||
import { deriveIdentifier, type GroupAgentRole, AGENT_ROLE_ICONS } from '../../types/groups';
|
||||
import { generateAgentPrompt } from '../../utils/agent-prompts';
|
||||
import { getSetting, setSetting } from '../../adapters/settings-bridge';
|
||||
import { getCurrentTheme, setTheme } from '../../stores/theme.svelte';
|
||||
import { THEME_LIST, getPalette, type ThemeId } from '../../styles/themes';
|
||||
|
|
@ -625,6 +627,117 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{#if activeGroup && (activeGroup.agents?.length ?? 0) > 0}
|
||||
<section class="settings-section">
|
||||
<h2>Agents in "{activeGroup.name}"</h2>
|
||||
|
||||
<div class="agent-cards">
|
||||
{#each activeGroup.agents ?? [] as agent (agent.id)}
|
||||
<div class="agent-config-card">
|
||||
<div class="card-top-row">
|
||||
<span class="agent-config-icon">{AGENT_ROLE_ICONS[agent.role] ?? '🤖'}</span>
|
||||
<input
|
||||
class="card-name-input"
|
||||
value={agent.name}
|
||||
placeholder="Agent name"
|
||||
onchange={e => updateAgent(activeGroupId, agent.id, { name: (e.target as HTMLInputElement).value })}
|
||||
/>
|
||||
<span class="agent-role-badge">{agent.role}</span>
|
||||
<label class="card-toggle" title={agent.enabled ? 'Enabled' : 'Disabled'}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={agent.enabled}
|
||||
onchange={e => updateAgent(activeGroupId, agent.id, { enabled: (e.target as HTMLInputElement).checked })}
|
||||
/>
|
||||
<span class="toggle-track"><span class="toggle-thumb"></span></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<span class="card-field-label">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v13h18V7H3zm0-2h7l2 2h9v1H3V5z"/></svg>
|
||||
Working Directory
|
||||
</span>
|
||||
<div class="input-with-browse">
|
||||
<input
|
||||
class="cwd-input"
|
||||
value={agent.cwd ?? ''}
|
||||
placeholder="Inherits from group"
|
||||
title={agent.cwd ?? 'Inherits from first project'}
|
||||
onchange={e => updateAgent(activeGroupId, agent.id, { cwd: (e.target as HTMLInputElement).value || undefined })}
|
||||
/>
|
||||
<button class="browse-btn" title="Browse..." onclick={async () => { const d = await browseDirectory(); if (d) updateAgent(activeGroupId, agent.id, { cwd: d }); }}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M3 7v13h18V7H3zm0-2h7l2 2h9v1H3V5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-field">
|
||||
<span class="card-field-label">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||
Model
|
||||
</span>
|
||||
<input
|
||||
class="card-field-input"
|
||||
value={agent.model ?? ''}
|
||||
placeholder="Default (provider default)"
|
||||
onchange={e => updateAgent(activeGroupId, agent.id, { model: (e.target as HTMLInputElement).value || undefined })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if agent.role === 'manager'}
|
||||
<div class="card-field">
|
||||
<span class="card-field-label">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
Wake Interval
|
||||
</span>
|
||||
<div class="scale-slider">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="30"
|
||||
step="1"
|
||||
value={agent.wakeIntervalMin ?? 3}
|
||||
oninput={e => updateAgent(activeGroupId, agent.id, { wakeIntervalMin: parseInt((e.target as HTMLInputElement).value) })}
|
||||
/>
|
||||
<span class="scale-label">{agent.wakeIntervalMin ?? 3} min</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card-field">
|
||||
<span class="card-field-label">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
Custom Context
|
||||
</span>
|
||||
<textarea
|
||||
class="agent-prompt-input"
|
||||
value={agent.systemPrompt ?? ''}
|
||||
placeholder="Additional instructions for this agent (appended to auto-generated context)"
|
||||
rows="3"
|
||||
onchange={e => updateAgent(activeGroupId, agent.id, { systemPrompt: (e.target as HTMLTextAreaElement).value || undefined })}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<details class="prompt-preview">
|
||||
<summary class="prompt-preview-toggle">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
Preview full introductory prompt
|
||||
</summary>
|
||||
<pre class="prompt-preview-content">{generateAgentPrompt({
|
||||
role: agent.role as GroupAgentRole,
|
||||
agentId: agent.id,
|
||||
agentName: agent.name,
|
||||
group: activeGroup,
|
||||
customPrompt: agent.systemPrompt,
|
||||
})}</pre>
|
||||
</details>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if activeGroup}
|
||||
<section class="settings-section">
|
||||
<h2>Projects in "{activeGroup.name}"</h2>
|
||||
|
|
@ -1727,4 +1840,118 @@
|
|||
background: var(--ctp-base);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
/* Agent config cards */
|
||||
.agent-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.agent-config-card {
|
||||
background: var(--ctp-surface0);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-left: 3px solid var(--ctp-mauve);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.625rem 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.agent-config-card:hover {
|
||||
border-color: var(--ctp-surface2);
|
||||
border-left-color: var(--ctp-mauve);
|
||||
}
|
||||
|
||||
.agent-config-icon {
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.agent-role-badge {
|
||||
font-size: 0.6rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ctp-mauve);
|
||||
background: color-mix(in srgb, var(--ctp-mauve) 10%, transparent);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-field-input {
|
||||
padding: 0.3125rem 0.5rem;
|
||||
background: var(--ctp-base);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--ctp-text);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.card-field-input:focus {
|
||||
border-color: var(--ctp-blue);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.agent-prompt-input {
|
||||
padding: 0.375rem 0.5rem;
|
||||
background: var(--ctp-base);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--ctp-text);
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--ui-font-family, sans-serif);
|
||||
line-height: 1.4;
|
||||
resize: vertical;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.agent-prompt-input:focus {
|
||||
border-color: var(--ctp-blue);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.agent-prompt-input::placeholder {
|
||||
color: var(--ctp-overlay0);
|
||||
}
|
||||
|
||||
.prompt-preview {
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prompt-preview-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.3125rem 0.5rem;
|
||||
font-size: 0.65rem;
|
||||
color: var(--ctp-overlay0);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.prompt-preview-toggle:hover {
|
||||
color: var(--ctp-subtext0);
|
||||
}
|
||||
|
||||
.prompt-preview-content {
|
||||
padding: 0.5rem;
|
||||
margin: 0;
|
||||
background: var(--ctp-base);
|
||||
color: var(--ctp-subtext0);
|
||||
font-size: 0.65rem;
|
||||
font-family: var(--term-font-family, monospace);
|
||||
line-height: 1.45;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 20rem;
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid var(--ctp-surface1);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface ProjectConfig {
|
|||
systemPrompt?: string;
|
||||
}
|
||||
|
||||
const AGENT_ROLE_ICONS: Record<string, string> = {
|
||||
export const AGENT_ROLE_ICONS: Record<string, string> = {
|
||||
manager: '🎯',
|
||||
architect: '🏗',
|
||||
tester: '🧪',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue