refactor(electrobun): modularize stores + shared UI components
Stores: - notifications-store.svelte.ts: owns notifications array (was inline in App) - workspace-store.svelte.ts: extended with addProjectFromWizard, loadGroupsFromDb, loadProjectsFromDb, derived getters (totalCost, totalTokens, mountedGroupIds) Shared UI components (ui/): - SegmentedControl.svelte: replaces repeated .seg button groups - SliderInput.svelte: labeled range slider with value display - StatusDot.svelte: colored dot with pulse support - IconButton.svelte: icon-only button with tooltip, 3 sizes - Section.svelte: settings section wrapper with heading App.svelte: script 390→221 lines (removed all inline CRUD, delegates to stores) ProjectCard: uses StatusDot shared component AgentSettings + OrchestrationSettings: use SegmentedControl, SliderInput, Section
This commit is contained in:
parent
265ddd3f1d
commit
c88577a34a
12 changed files with 647 additions and 437 deletions
|
|
@ -2,6 +2,8 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { appRpc } from '../rpc.ts';
|
||||
import { PROVIDER_CAPABILITIES, type ProviderId } from '../provider-capabilities';
|
||||
import SegmentedControl from '../ui/SegmentedControl.svelte';
|
||||
import Section from '../ui/Section.svelte';
|
||||
|
||||
type PermMode = 'bypassPermissions' | 'default' | 'plan';
|
||||
|
||||
|
|
@ -12,6 +14,12 @@
|
|||
{ id: 'gemini', label: 'Gemini', desc: 'Google — gemini-2.5-pro' },
|
||||
];
|
||||
|
||||
const PERM_OPTIONS = [
|
||||
{ value: 'bypassPermissions', label: 'Bypass' },
|
||||
{ value: 'default', label: 'Default' },
|
||||
{ value: 'plan', label: 'Plan' },
|
||||
];
|
||||
|
||||
let defaultShell = $state('/bin/bash');
|
||||
let defaultCwd = $state('~');
|
||||
let permissionMode = $state<PermMode>('bypassPermissions');
|
||||
|
|
@ -27,8 +35,6 @@
|
|||
|
||||
let expandedProvider = $state<string | null>(null);
|
||||
|
||||
// ── Persistence helpers ──────────────────────────────────────────────────
|
||||
|
||||
function persist(key: string, value: string) {
|
||||
appRpc?.request['settings.set']({ key, value }).catch(console.error);
|
||||
}
|
||||
|
|
@ -37,12 +43,10 @@
|
|||
persist('provider_settings', JSON.stringify(providerState));
|
||||
}
|
||||
|
||||
// ── Actions ──────────────────────────────────────────────────────────────
|
||||
|
||||
function setShell(v: string) { defaultShell = v; persist('default_shell', v); }
|
||||
function setCwd(v: string) { defaultCwd = v; persist('default_cwd', v); }
|
||||
function setPermMode(v: PermMode) { permissionMode = v; persist('permission_mode', v); }
|
||||
function setPrompt(v: string) { systemPrompt = v; persist('system_prompt_template', v); }
|
||||
function setPermMode(v: string) { permissionMode = v as PermMode; persist('permission_mode', v); }
|
||||
function setPrompt(v: string) { systemPrompt = v; persist('system_prompt_template', v); }
|
||||
|
||||
function toggleProvider(id: string) {
|
||||
providerState[id] = { ...providerState[id], enabled: !providerState[id].enabled };
|
||||
|
|
@ -56,8 +60,6 @@
|
|||
persistProviders();
|
||||
}
|
||||
|
||||
// ── Restore on mount ─────────────────────────────────────────────────────
|
||||
|
||||
onMount(async () => {
|
||||
if (!appRpc) return;
|
||||
const { settings } = await appRpc.request['settings.getAll']({}).catch(() => ({ settings: {} }));
|
||||
|
|
@ -71,9 +73,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class="section">
|
||||
<h3 class="sh">Defaults</h3>
|
||||
|
||||
<Section heading="Defaults">
|
||||
<div class="field">
|
||||
<label class="lbl" for="ag-shell">Shell</label>
|
||||
<input id="ag-shell" class="text-in" value={defaultShell} placeholder="/bin/bash"
|
||||
|
|
@ -85,20 +85,19 @@
|
|||
<input id="ag-cwd" class="text-in" value={defaultCwd} placeholder="~"
|
||||
onchange={e => setCwd((e.target as HTMLInputElement).value)} />
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">Permission mode</h3>
|
||||
<div class="seg">
|
||||
<button class:active={permissionMode === 'bypassPermissions'} onclick={() => setPermMode('bypassPermissions')}>Bypass</button>
|
||||
<button class:active={permissionMode === 'default'} onclick={() => setPermMode('default')}>Default</button>
|
||||
<button class:active={permissionMode === 'plan'} onclick={() => setPermMode('plan')}>Plan</button>
|
||||
</div>
|
||||
<Section heading="Permission mode" spaced>
|
||||
<SegmentedControl options={PERM_OPTIONS} selected={permissionMode} onSelect={setPermMode} />
|
||||
</Section>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">System prompt template</h3>
|
||||
<Section heading="System prompt template" spaced>
|
||||
<textarea class="prompt" value={systemPrompt} rows="3"
|
||||
placeholder="Optional prompt prepended to all agent sessions..."
|
||||
onchange={e => setPrompt((e.target as HTMLTextAreaElement).value)}></textarea>
|
||||
</Section>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">Providers</h3>
|
||||
<Section heading="Providers" spaced>
|
||||
<div class="prov-list">
|
||||
{#each PROVIDERS as prov}
|
||||
{@const state = providerState[prov.id]}
|
||||
|
|
@ -106,7 +105,7 @@
|
|||
<button class="prov-hdr" onclick={() => expandedProvider = expandedProvider === prov.id ? null : prov.id}>
|
||||
<span class="prov-name">{prov.label}</span>
|
||||
<span class="prov-desc">{prov.desc}</span>
|
||||
<span class="prov-chev">{expandedProvider === prov.id ? '▴' : '▾'}</span>
|
||||
<span class="prov-chev">{expandedProvider === prov.id ? '\u25B4' : '\u25BE'}</span>
|
||||
</button>
|
||||
{#if expandedProvider === prov.id}
|
||||
<div class="prov-body">
|
||||
|
|
@ -132,11 +131,9 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<style>
|
||||
.section { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.sh { margin: 0.125rem 0; font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ctp-overlay0); }
|
||||
.field { display: flex; flex-direction: column; gap: 0.2rem; }
|
||||
.lbl { font-size: 0.75rem; color: var(--ctp-subtext0); }
|
||||
|
||||
|
|
@ -154,12 +151,6 @@
|
|||
.prompt:focus { outline: none; border-color: var(--ctp-blue); }
|
||||
.prompt::placeholder { color: var(--ctp-overlay0); }
|
||||
|
||||
.seg { display: flex; border-radius: 0.25rem; overflow: hidden; border: 1px solid var(--ctp-surface1); }
|
||||
.seg button { flex: 1; padding: 0.25rem 0.5rem; background: var(--ctp-surface0); border: none; color: var(--ctp-overlay1); font-size: 0.75rem; cursor: pointer; font-family: var(--ui-font-family); }
|
||||
.seg button:not(:last-child) { border-right: 1px solid var(--ctp-surface1); }
|
||||
.seg button:hover { background: var(--ctp-surface1); color: var(--ctp-subtext1); }
|
||||
.seg button.active { background: color-mix(in srgb, var(--ctp-blue) 20%, var(--ctp-surface0)); color: var(--ctp-blue); font-weight: 600; }
|
||||
|
||||
.prov-list { display: flex; flex-direction: column; gap: 0.3rem; }
|
||||
.prov-panel { background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.3rem; overflow: hidden; transition: opacity 0.15s; }
|
||||
.prov-panel.disabled { opacity: 0.5; }
|
||||
|
|
@ -173,7 +164,7 @@
|
|||
.toggle-row { display: flex; align-items: center; justify-content: space-between; cursor: pointer; }
|
||||
.toggle { position: relative; width: 2rem; height: 1.125rem; border: none; border-radius: 0.5625rem; background: var(--ctp-surface1); cursor: pointer; transition: background 0.2s; padding: 0; flex-shrink: 0; }
|
||||
.toggle.on { background: var(--ctp-blue); }
|
||||
.thumb { position: absolute; top: 0.125rem; left: 0.125rem; width: 0.875rem; height: 0.875rem; border-radius: 50%; background: var(--ctp-text); transition: transform 0.2s; }
|
||||
.thumb { position: absolute; top: 0.125rem; left: 0.125rem; width: 0.875rem; height: 0.875rem; border-radius: 50%; background: var(--ctp-text); transition: transform 0.2s; display: block; }
|
||||
.toggle.on .thumb { transform: translateX(0.875rem); }
|
||||
|
||||
.caps { display: flex; flex-wrap: wrap; gap: 0.25rem; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue