feat(electrobun): port all Tauri features — full settings, popup menus, provider capabilities
New components (8): - provider-capabilities.ts: per-provider feature flags (claude/codex/ollama) - settings/AppearanceSettings.svelte: theme, fonts, cursor, scrollback - settings/AgentSettings.svelte: shell, CWD, permissions, providers - settings/SecuritySettings.svelte: keyring, secrets, branch policies - settings/ProjectSettings.svelte: per-project provider/model/worktree/sandbox - settings/OrchestrationSettings.svelte: wake strategy, notifications, anchors - settings/AdvancedSettings.svelte: logging, OTLP, plugins, import/export Updated: - ChatInput: radial context indicator (78% demo, color-coded arc), 4 popup menus (upload/context/web/slash), provider-gated icons - SettingsDrawer: 6-category sidebar shell - AgentPane: passes provider + contextPct to ChatInput
This commit is contained in:
parent
54d6f0b94a
commit
0b9e8b305a
15 changed files with 1510 additions and 441 deletions
|
|
@ -1,4 +1,11 @@
|
|||
<script lang="ts">
|
||||
import AppearanceSettings from './settings/AppearanceSettings.svelte';
|
||||
import AgentSettings from './settings/AgentSettings.svelte';
|
||||
import SecuritySettings from './settings/SecuritySettings.svelte';
|
||||
import ProjectSettings from './settings/ProjectSettings.svelte';
|
||||
import OrchestrationSettings from './settings/OrchestrationSettings.svelte';
|
||||
import AdvancedSettings from './settings/AdvancedSettings.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
|
|
@ -6,40 +13,24 @@
|
|||
|
||||
let { open, onClose }: Props = $props();
|
||||
|
||||
// Local settings state (demo — no persistence in prototype)
|
||||
const THEMES = [
|
||||
{ id: 'mocha', label: 'Catppuccin Mocha' },
|
||||
{ id: 'macchiato', label: 'Catppuccin Macchiato' },
|
||||
{ id: 'frappe', label: 'Catppuccin Frappé' },
|
||||
{ id: 'latte', label: 'Catppuccin Latte' },
|
||||
];
|
||||
let themeId = $state('mocha');
|
||||
let themeDropdownOpen = $state(false);
|
||||
let selectedThemeLabel = $derived(THEMES.find(t => t.id === themeId)?.label ?? 'Catppuccin Mocha');
|
||||
type CategoryId = 'appearance' | 'agents' | 'security' | 'projects' | 'orchestration' | 'advanced';
|
||||
|
||||
function selectTheme(id: string) {
|
||||
themeId = id;
|
||||
themeDropdownOpen = false;
|
||||
}
|
||||
|
||||
let uiFontSize = $state(14);
|
||||
let termFontSize = $state(13);
|
||||
|
||||
interface Provider {
|
||||
id: string;
|
||||
interface Category {
|
||||
id: CategoryId;
|
||||
label: string;
|
||||
enabled: boolean;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
let providers = $state<Provider[]>([
|
||||
{ id: 'claude', label: 'Claude (Anthropic)', enabled: true },
|
||||
{ id: 'codex', label: 'Codex (OpenAI)', enabled: false },
|
||||
{ id: 'ollama', label: 'Ollama (local)', enabled: false },
|
||||
]);
|
||||
const CATEGORIES: Category[] = [
|
||||
{ id: 'appearance', label: 'Appearance', icon: '🎨' },
|
||||
{ id: 'agents', label: 'Agents', icon: '🤖' },
|
||||
{ id: 'security', label: 'Security', icon: '🔒' },
|
||||
{ id: 'projects', label: 'Projects', icon: '📁' },
|
||||
{ id: 'orchestration', label: 'Orchestration', icon: '⚙' },
|
||||
{ id: 'advanced', label: 'Advanced', icon: '🔧' },
|
||||
];
|
||||
|
||||
function toggleProvider(id: string) {
|
||||
providers = providers.map(p => p.id === id ? { ...p, enabled: !p.enabled } : p);
|
||||
}
|
||||
let activeCategory = $state<CategoryId>('appearance');
|
||||
|
||||
function handleBackdropClick(e: MouseEvent) {
|
||||
if (e.target === e.currentTarget) onClose();
|
||||
|
|
@ -50,7 +41,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- Backdrop -->
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<div
|
||||
|
|
@ -62,109 +52,50 @@
|
|||
onclick={handleBackdropClick}
|
||||
onkeydown={handleKeydown}
|
||||
>
|
||||
<aside class="drawer-panel">
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<aside class="drawer-panel" onclick={e => e.stopPropagation()} onkeydown={e => e.stopPropagation()}>
|
||||
|
||||
<!-- Header -->
|
||||
<header class="drawer-header">
|
||||
<h2 class="drawer-title">Settings</h2>
|
||||
<button class="drawer-close" onclick={onClose} aria-label="Close settings">×</button>
|
||||
</header>
|
||||
|
||||
<!-- Body: sidebar + content -->
|
||||
<div class="drawer-body">
|
||||
<!-- Appearance -->
|
||||
<section class="settings-section">
|
||||
<h3 class="section-heading">Appearance</h3>
|
||||
|
||||
<div class="setting-row">
|
||||
<label class="setting-label" for="theme-dropdown-btn">Theme</label>
|
||||
<div class="theme-dropdown">
|
||||
<button
|
||||
id="theme-dropdown-btn"
|
||||
class="theme-dropdown-btn"
|
||||
onclick={() => themeDropdownOpen = !themeDropdownOpen}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={themeDropdownOpen}
|
||||
>
|
||||
<span class="theme-dropdown-label">{selectedThemeLabel}</span>
|
||||
<svg class="theme-chevron" class:open={themeDropdownOpen} 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 themeDropdownOpen}
|
||||
<ul class="theme-dropdown-list" role="listbox" aria-label="Theme options">
|
||||
{#each THEMES as t}
|
||||
<li
|
||||
class="theme-option"
|
||||
class:selected={themeId === t.id}
|
||||
role="option"
|
||||
aria-selected={themeId === t.id}
|
||||
onclick={() => selectTheme(t.id)}
|
||||
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && selectTheme(t.id)}
|
||||
tabindex="0"
|
||||
>{t.label}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label" id="ui-font-label">UI font size</span>
|
||||
<div class="font-stepper" role="group" aria-labelledby="ui-font-label">
|
||||
<button
|
||||
class="stepper-btn"
|
||||
onclick={() => uiFontSize = Math.max(10, uiFontSize - 1)}
|
||||
aria-label="Decrease UI font size"
|
||||
>−</button>
|
||||
<span class="stepper-value">{uiFontSize}px</span>
|
||||
<button
|
||||
class="stepper-btn"
|
||||
onclick={() => uiFontSize = Math.min(24, uiFontSize + 1)}
|
||||
aria-label="Increase UI font size"
|
||||
>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<span class="setting-label" id="term-font-label">Terminal font size</span>
|
||||
<div class="font-stepper" role="group" aria-labelledby="term-font-label">
|
||||
<button
|
||||
class="stepper-btn"
|
||||
onclick={() => termFontSize = Math.max(8, termFontSize - 1)}
|
||||
aria-label="Decrease terminal font size"
|
||||
>−</button>
|
||||
<span class="stepper-value">{termFontSize}px</span>
|
||||
<button
|
||||
class="stepper-btn"
|
||||
onclick={() => termFontSize = Math.min(24, termFontSize + 1)}
|
||||
aria-label="Increase terminal font size"
|
||||
>+</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Providers -->
|
||||
<section class="settings-section">
|
||||
<h3 class="section-heading">Providers</h3>
|
||||
{#each providers as prov (prov.id)}
|
||||
<div class="setting-row">
|
||||
<label class="setting-label" for="prov-{prov.id}">{prov.label}</label>
|
||||
<button
|
||||
id="prov-{prov.id}"
|
||||
class="toggle-btn"
|
||||
class:enabled={prov.enabled}
|
||||
onclick={() => toggleProvider(prov.id)}
|
||||
role="switch"
|
||||
aria-checked={prov.enabled}
|
||||
aria-label="{prov.label} {prov.enabled ? 'enabled' : 'disabled'}"
|
||||
>
|
||||
<span class="toggle-track">
|
||||
<span class="toggle-thumb"></span>
|
||||
</span>
|
||||
<span class="toggle-label">{prov.enabled ? 'on' : 'off'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Category nav -->
|
||||
<nav class="cat-nav" aria-label="Settings categories">
|
||||
{#each CATEGORIES as cat}
|
||||
<button
|
||||
class="cat-btn"
|
||||
class:active={activeCategory === cat.id}
|
||||
onclick={() => activeCategory = cat.id}
|
||||
aria-current={activeCategory === cat.id ? 'page' : undefined}
|
||||
>
|
||||
<span class="cat-icon" aria-hidden="true">{cat.icon}</span>
|
||||
<span class="cat-label">{cat.label}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<!-- Category content -->
|
||||
<div class="cat-content">
|
||||
{#if activeCategory === 'appearance'}
|
||||
<AppearanceSettings />
|
||||
{:else if activeCategory === 'agents'}
|
||||
<AgentSettings />
|
||||
{:else if activeCategory === 'security'}
|
||||
<SecuritySettings />
|
||||
{:else if activeCategory === 'projects'}
|
||||
<ProjectSettings />
|
||||
{:else if activeCategory === 'orchestration'}
|
||||
<OrchestrationSettings />
|
||||
{:else if activeCategory === 'advanced'}
|
||||
<AdvancedSettings />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -180,8 +111,8 @@
|
|||
}
|
||||
|
||||
.drawer-panel {
|
||||
width: 18rem;
|
||||
max-width: 90vw;
|
||||
width: 30rem;
|
||||
max-width: 95vw;
|
||||
background: var(--ctp-mantle);
|
||||
border-right: 1px solid var(--ctp-surface0);
|
||||
display: flex;
|
||||
|
|
@ -195,6 +126,7 @@
|
|||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.drawer-header {
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
|
|
@ -232,199 +164,77 @@
|
|||
color: var(--ctp-text);
|
||||
}
|
||||
|
||||
/* Body: two-column layout */
|
||||
.drawer-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Category nav sidebar */
|
||||
.cat-nav {
|
||||
width: 8.5rem;
|
||||
flex-shrink: 0;
|
||||
background: var(--ctp-crust);
|
||||
border-right: 1px solid var(--ctp-surface0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.375rem 0;
|
||||
gap: 0.0625rem;
|
||||
overflow-y: auto;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.drawer-body::-webkit-scrollbar { width: 0.375rem; }
|
||||
.drawer-body::-webkit-scrollbar-track { background: transparent; }
|
||||
.drawer-body::-webkit-scrollbar-thumb { background: var(--ctp-surface1); border-radius: 0.25rem; }
|
||||
.cat-nav::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Sections */
|
||||
.settings-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--ctp-overlay0);
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
.cat-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--ctp-subtext1);
|
||||
}
|
||||
|
||||
/* Theme dropdown */
|
||||
.theme-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.theme-dropdown-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
background: var(--ctp-surface0);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.3rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
color: var(--ctp-mauve);
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--ctp-subtext0);
|
||||
font-family: var(--ui-font-family);
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.12s;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
border-radius: 0;
|
||||
transition: background 0.1s, color 0.1s;
|
||||
}
|
||||
|
||||
.theme-dropdown-btn:hover { border-color: var(--ctp-surface2); }
|
||||
.cat-btn:hover {
|
||||
background: var(--ctp-surface0);
|
||||
color: var(--ctp-text);
|
||||
}
|
||||
|
||||
.theme-dropdown-label { flex: 1; }
|
||||
.cat-btn.active {
|
||||
background: var(--ctp-surface0);
|
||||
color: var(--ctp-text);
|
||||
border-left: 2px solid var(--ctp-mauve);
|
||||
padding-left: calc(0.75rem - 2px);
|
||||
}
|
||||
|
||||
.theme-chevron {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
color: var(--ctp-overlay1);
|
||||
transition: transform 0.15s;
|
||||
.cat-icon {
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.theme-chevron.open { transform: rotate(180deg); }
|
||||
|
||||
.theme-dropdown-list {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 0.25rem);
|
||||
z-index: 10;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
background: var(--ctp-mantle);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.375rem;
|
||||
min-width: 11rem;
|
||||
box-shadow: 0 0.5rem 1.25rem color-mix(in srgb, var(--ctp-crust) 60%, transparent);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.0625rem;
|
||||
.cat-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--ctp-subtext1);
|
||||
cursor: pointer;
|
||||
transition: background 0.08s, color 0.08s;
|
||||
outline: none;
|
||||
/* Content area */
|
||||
.cat-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
padding: 0.875rem;
|
||||
}
|
||||
|
||||
.theme-option:hover,
|
||||
.theme-option:focus { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
|
||||
.theme-option.selected {
|
||||
background: color-mix(in srgb, var(--ctp-mauve) 15%, transparent);
|
||||
color: var(--ctp-mauve);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Font stepper */
|
||||
.font-stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.stepper-btn {
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
background: var(--ctp-surface0);
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.25rem;
|
||||
color: var(--ctp-text);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.stepper-btn:hover { background: var(--ctp-surface1); }
|
||||
|
||||
.stepper-value {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--ctp-text);
|
||||
min-width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
.toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toggle-track {
|
||||
width: 2rem;
|
||||
height: 1.125rem;
|
||||
background: var(--ctp-surface1);
|
||||
border-radius: 0.5625rem;
|
||||
position: relative;
|
||||
transition: background 0.15s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toggle-btn.enabled .toggle-track {
|
||||
background: var(--ctp-mauve);
|
||||
}
|
||||
|
||||
.toggle-thumb {
|
||||
position: absolute;
|
||||
top: 0.1875rem;
|
||||
left: 0.1875rem;
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
background: var(--ctp-base);
|
||||
border-radius: 50%;
|
||||
transition: transform 0.15s;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toggle-btn.enabled .toggle-thumb {
|
||||
transform: translateX(0.875rem);
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--ctp-subtext0);
|
||||
min-width: 1.5rem;
|
||||
}
|
||||
|
||||
.toggle-btn.enabled .toggle-label {
|
||||
color: var(--ctp-mauve);
|
||||
}
|
||||
.cat-content::-webkit-scrollbar { width: 0.375rem; }
|
||||
.cat-content::-webkit-scrollbar-track { background: transparent; }
|
||||
.cat-content::-webkit-scrollbar-thumb { background: var(--ctp-surface1); border-radius: 0.25rem; }
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue