feat(electrobun): project wizard phases 1-5 (WIP)
- sanitize.ts: input sanitization (trim, control chars, path traversal) - provider-scanner.ts: detect Claude/Codex/Ollama/Gemini availability - model-fetcher.ts: live model lists from 4 provider APIs - ModelConfigPanel.svelte: per-provider config (thinking, effort, sandbox, temperature) - WizardStep1-3.svelte: split wizard into composable steps - CustomDropdown/Checkbox/Radio: themed UI components - provider-handlers.ts: provider.scan + provider.models RPC - Wire providers into wizard step 3 (live detection + model lists) - Replace native selects in 5 settings panels with CustomDropdown
This commit is contained in:
parent
b7fc3a0f9b
commit
d4014a193d
25 changed files with 2112 additions and 759 deletions
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appRpc } from '../rpc.ts';
|
||||
import CustomDropdown from '../ui/CustomDropdown.svelte';
|
||||
|
||||
const KNOWN_KEYS: Record<string, string> = {
|
||||
ANTHROPIC_API_KEY: 'Anthropic API Key',
|
||||
|
|
@ -16,7 +17,7 @@
|
|||
|
||||
let newKey = $state('');
|
||||
let newValue = $state('');
|
||||
let keyDropOpen = $state(false);
|
||||
// Dropdown state managed by CustomDropdown
|
||||
let saving = $state(false);
|
||||
|
||||
interface BranchPolicy { pattern: string; action: 'block' | 'warn'; }
|
||||
|
|
@ -28,7 +29,7 @@
|
|||
let newAction = $state<'block' | 'warn'>('warn');
|
||||
|
||||
let availableKeys = $derived(Object.keys(KNOWN_KEYS).filter(k => !storedKeys.includes(k)));
|
||||
let newKeyLabel = $derived(newKey ? (KNOWN_KEYS[newKey] ?? newKey) : 'Select key...');
|
||||
let keyDropItems = $derived(availableKeys.map(k => ({ value: k, label: KNOWN_KEYS[k] ?? k })));
|
||||
|
||||
function persistPolicies() {
|
||||
appRpc?.request['settings.set']({ key: 'branch_policies', value: JSON.stringify(branchPolicies) }).catch(console.error);
|
||||
|
|
@ -61,9 +62,7 @@
|
|||
persistPolicies();
|
||||
}
|
||||
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
if (!(e.target as HTMLElement).closest('.dd-wrap')) keyDropOpen = false;
|
||||
}
|
||||
// CustomDropdown handles its own outside click
|
||||
|
||||
onMount(async () => {
|
||||
if (!appRpc) return;
|
||||
|
|
@ -74,8 +73,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="section" onclick={handleOutsideClick} onkeydown={e => e.key === 'Escape' && (keyDropOpen = false)}>
|
||||
<div class="section">
|
||||
|
||||
<div class="prototype-notice">
|
||||
Prototype — secrets are stored locally in plain SQLite, not in the system keyring.
|
||||
|
|
@ -107,24 +105,13 @@
|
|||
{/if}
|
||||
|
||||
<div class="add-secret">
|
||||
<div class="dd-wrap">
|
||||
<button class="dd-btn small" onclick={() => keyDropOpen = !keyDropOpen}>
|
||||
{newKeyLabel}
|
||||
<svg class="chev" class:open={keyDropOpen} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
</button>
|
||||
{#if keyDropOpen}
|
||||
<ul class="dd-list" role="listbox">
|
||||
{#each availableKeys as k}
|
||||
<li class="dd-item" role="option" aria-selected={newKey === k} tabindex="0"
|
||||
onclick={() => { newKey = k; keyDropOpen = false; }}
|
||||
onkeydown={e => (e.key === 'Enter' || e.key === ' ') && (newKey = k, keyDropOpen = false)}
|
||||
>{KNOWN_KEYS[k]}</li>
|
||||
{/each}
|
||||
{#if availableKeys.length === 0}
|
||||
<li class="dd-item disabled-item">All known keys stored</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
<div class="dd-key-wrap">
|
||||
<CustomDropdown
|
||||
items={keyDropItems}
|
||||
selected={newKey}
|
||||
placeholder="Select key..."
|
||||
onSelect={v => newKey = v}
|
||||
/>
|
||||
</div>
|
||||
<input class="text-in flex1" type="password" bind:value={newValue} placeholder="Value" aria-label="Secret value" />
|
||||
<button class="save-btn" onclick={handleSaveSecret} disabled={!newKey || !newValue || saving}>
|
||||
|
|
@ -183,15 +170,7 @@
|
|||
.add-policy { display: flex; align-items: center; gap: 0.375rem; margin-top: 0.25rem; }
|
||||
.flex1 { flex: 1; min-width: 0; }
|
||||
|
||||
.dd-wrap { position: relative; flex-shrink: 0; }
|
||||
.dd-btn { display: flex; align-items: center; justify-content: space-between; gap: 0.25rem; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; color: var(--ctp-subtext1); font-family: var(--ui-font-family); cursor: pointer; white-space: nowrap; }
|
||||
.dd-btn.small { padding: 0.275rem 0.5rem; font-size: 0.75rem; min-width: 8rem; }
|
||||
.chev { width: 0.625rem; height: 0.625rem; color: var(--ctp-overlay1); transition: transform 0.15s; }
|
||||
.chev.open { transform: rotate(180deg); }
|
||||
.dd-list { position: absolute; top: calc(100% + 0.125rem); left: 0; z-index: 50; list-style: none; margin: 0; padding: 0.2rem; background: var(--ctp-mantle); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; min-width: 10rem; box-shadow: 0 0.5rem 1rem color-mix(in srgb, var(--ctp-crust) 60%, transparent); }
|
||||
.dd-item { padding: 0.3rem 0.5rem; border-radius: 0.2rem; font-size: 0.8rem; color: var(--ctp-subtext1); cursor: pointer; outline: none; }
|
||||
.dd-item:hover, .dd-item:focus { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
.disabled-item { opacity: 0.4; cursor: not-allowed; }
|
||||
.dd-key-wrap { flex-shrink: 0; min-width: 10rem; }
|
||||
|
||||
.text-in { padding: 0.275rem 0.5rem; background: var(--ctp-surface0); border: 1px solid var(--ctp-surface1); border-radius: 0.25rem; color: var(--ctp-text); font-size: 0.8rem; font-family: var(--ui-font-family); }
|
||||
.text-in:focus { outline: none; border-color: var(--ctp-blue); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue