bos-settings/frontend/src/lib/views/Breadpad.svelte
Breadway 93c3b88b10 Migrate bos-settings from GTK4 to Tauri + Svelte 5
Ports every remaining view (Keybinds, the last one — dual Flat/MultiLayout
schema detection, per-action dedicated fields with a raw-JSON fallback for
anything not modeled) and removes the now-fully-dead GTK4 crate (root
Cargo.toml + src/), leaving src-tauri/ as the single Rust binary crate.

Also folds in the UX pass done alongside the migration: responsive
multi-column layout instead of a single centered column, chip/dropdown
pickers replacing free-typed fields (Wi-Fi networks, Bar style, keyboard
layout, tag lists), consistent pill-switch toggles and boxed-list card
styling across every page, and a sidebar scroll-chaining fix.
2026-07-22 19:23:02 +08:00

138 lines
5 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import ViewScaffold from "$lib/components/ViewScaffold.svelte";
import Group from "$lib/components/Group.svelte";
import SwitchField from "$lib/components/SwitchField.svelte";
import TextField from "$lib/components/TextField.svelte";
import FileField from "$lib/components/FileField.svelte";
import PasswordField from "$lib/components/PasswordField.svelte";
import SelectField from "$lib/components/SelectField.svelte";
import NumberField from "$lib/components/NumberField.svelte";
import TagsField from "$lib/components/TagsField.svelte";
import Row from "$lib/components/Row.svelte";
import SaveButton from "$lib/components/SaveButton.svelte";
interface BreadpadConfig {
default_type: string;
workspace_tag: boolean;
snooze_options: string[];
archive_after_days: number;
model_path: string;
tokenizer_path: string;
ollama_enabled: boolean;
ollama_endpoint: string;
ollama_model: string;
ollama_confidence_threshold: number;
reminders_default_morning: string;
reminders_missed_grace_minutes: number;
calendar_enabled: boolean;
calendar_url: string;
calendar_username: string;
calendar_password: string;
}
let cfg = $state<BreadpadConfig | null>(null);
// The confidence threshold is an f64 (0-1, step 0.05); binding a plain
// number input directly to it shows raw float noise (e.g. "0.6000000")
// once the value has been nudged by the spinner. Instead we mirror it
// into a formatted display string, and only parse it back into `cfg` as
// a value rounded to 2 decimals.
let confidenceText = $state("0.6");
let confidenceLoaded = false;
function formatConfidence(n: number): string {
return (Math.round(n * 100) / 100).toString();
}
function onConfidenceInput(e: Event) {
confidenceText = (e.currentTarget as HTMLInputElement).value;
const parsed = parseFloat(confidenceText);
if (!Number.isNaN(parsed) && cfg) {
cfg.ollama_confidence_threshold = Math.round(parsed * 100) / 100;
}
}
function onConfidenceBlur() {
if (cfg) confidenceText = formatConfidence(cfg.ollama_confidence_threshold);
}
$effect(() => {
if (cfg && !confidenceLoaded) {
confidenceText = formatConfidence(cfg.ollama_confidence_threshold);
confidenceLoaded = true;
}
});
onMount(async () => {
cfg = await invoke<BreadpadConfig>("get_breadpad_config");
});
async function save() {
await invoke("save_breadpad_config", { cfg });
}
</script>
<ViewScaffold title="Notes">
{#if cfg}
<Group title="Capture">
<SelectField label="Default note type" bind:value={cfg.default_type} options={["note", "reminder", "task"]} />
<SwitchField label="Tag with active workspace" bind:value={cfg.workspace_tag} />
<TagsField label="Snooze options" bind:value={cfg.snooze_options} />
<NumberField label="Archive after (days)" bind:value={cfg.archive_after_days} min={0} max={3650} />
</Group>
<Group title="Classifier model" hint="The local model breadpad uses to guess note vs. reminder vs. task — no network needed.">
<FileField label="ONNX model" bind:value={cfg.model_path} placeholder="~/.local/share/breadpad/model/classifier.onnx" extensions={["onnx"]} />
<FileField label="Tokenizer" bind:value={cfg.tokenizer_path} placeholder="~/.local/share/breadpad/model/tokenizer.json" extensions={["json"]} />
</Group>
<Group title="Ollama (LLM classifier)" hint="Optional: ask a locally-running LLM (via Ollama) to classify notes instead of, or alongside, the ONNX model above.">
<SwitchField label="Use Ollama" bind:value={cfg.ollama_enabled} />
<TextField label="Endpoint" bind:value={cfg.ollama_endpoint} placeholder="http://localhost:11434" />
<TextField label="Model" bind:value={cfg.ollama_model} placeholder="e.g. fastflowlm" />
<Row label="Confidence threshold">
<input
type="number"
min="0"
max="1"
step="0.05"
value={confidenceText}
oninput={onConfidenceInput}
onblur={onConfidenceBlur}
/>
</Row>
</Group>
<Group title="Reminders">
<TextField label="Default morning time" bind:value={cfg.reminders_default_morning} placeholder="7:00" />
<NumberField label="Missed grace (minutes)" bind:value={cfg.reminders_missed_grace_minutes} min={0} max={1440} step={5} />
</Group>
<Group title="Calendar (CalDAV)">
<SwitchField label="Sync to calendar" bind:value={cfg.calendar_enabled} />
<TextField label="CalDAV URL" bind:value={cfg.calendar_url} placeholder="https://host/remote.php/dav/calendars/..." />
<TextField label="Username" bind:value={cfg.calendar_username} />
<PasswordField label="Password" bind:value={cfg.calendar_password} />
</Group>
<SaveButton onSave={save} />
{/if}
</ViewScaffold>
<style>
input[type="number"] {
width: 10ch;
background-color: var(--bg);
color: var(--on-surface);
border: 1px solid transparent;
border-radius: var(--radius-secondary, 6px);
padding: var(--space-xs, 4px) var(--space-sm, 8px);
}
input[type="number"]:focus {
outline: none;
border-color: var(--accent);
}
</style>