>({});
+ let advSaving = $state(false);
+ let advSaved = $state(false);
+ $effect(() => {
+ // (Re)load the form whenever persisted settings change.
+ const a = settings.settings.llm_advanced ?? {};
+ advSystem = a.system ?? "";
+ advThink = a.think ?? "off";
+ advKeepAlive = a.keep_alive ?? "";
+ advOptions = { ...(a.options ?? {}) };
+ });
+ let advChangedCount = $derived(
+ Object.keys(advOptions).length +
+ (advSystem.trim() ? 1 : 0) +
+ (advThink !== "off" ? 1 : 0) +
+ (advKeepAlive.trim() ? 1 : 0),
+ );
+ function mirostatOff(): boolean {
+ const m = advOptions["mirostat"];
+ return (m === undefined ? 0 : (m as number)) === 0;
+ }
+ function optDisplay(opt: OllamaOption): string {
+ const v = advOptions[opt.key];
+ if (opt.kind === "tags") return Array.isArray(v) ? v.join(", ") : "";
+ if (v !== undefined) return String(v);
+ return opt.default !== undefined ? String(opt.default) : "";
+ }
+ function isOverridden(opt: OllamaOption): boolean {
+ return opt.key in advOptions;
+ }
+ function commit(key: string, value: OptVal | undefined) {
+ const next = { ...advOptions };
+ if (value === undefined) delete next[key];
+ else next[key] = value;
+ advOptions = next;
+ advSaved = false;
+ }
+ function setNumber(opt: OllamaOption, raw: string) {
+ const t = raw.trim();
+ if (t === "") return commit(opt.key, undefined);
+ const n = Number(t);
+ if (Number.isNaN(n)) return;
+ commit(opt.key, opt.default !== undefined && n === opt.default ? undefined : n);
+ }
+ function setEnum(opt: OllamaOption, raw: string) {
+ const n = Number(raw);
+ commit(opt.key, opt.default !== undefined && n === opt.default ? undefined : n);
+ }
+ function setBool(opt: OllamaOption, checked: boolean) {
+ commit(opt.key, opt.default !== undefined && checked === opt.default ? undefined : checked);
+ }
+ function setTags(opt: OllamaOption, raw: string) {
+ const arr = raw
+ .split(",")
+ .map((s) => s.trim())
+ .filter(Boolean);
+ commit(opt.key, arr.length ? arr : undefined);
+ }
+ function resetAllAdvanced() {
+ advSystem = "";
+ advThink = "off";
+ advKeepAlive = "";
+ advOptions = {};
+ advSaved = false;
+ }
+ async function saveAdvanced() {
+ advSaving = true;
+ advSaved = false;
+ const adv: NonNullable<(typeof settings.settings)["llm_advanced"]> = {};
+ if (advSystem.trim()) adv.system = advSystem.trim();
+ if (advThink !== "off") adv.think = advThink;
+ if (advKeepAlive.trim()) adv.keep_alive = advKeepAlive.trim();
+ if (Object.keys(advOptions).length) adv.options = advOptions;
+ try {
+ await settings.patch({ llm_advanced: Object.keys(adv).length ? adv : null });
+ advSaved = true;
+ } finally {
+ advSaving = false;
+ }
+ }
+
// ---- Calendar / .pst import (T6.1/T6.2/T6.3, FR-CAL-1/2) ----
let pstPath = $state("");
let pstPassword = $state("");
@@ -698,25 +795,172 @@
-
- {#if settings.llmStatus}
-
- {#if settings.llmStatus.reachable}{:else}{/if}
- {settings.llmStatus.reachable ? "reachable" : "unreachable"}
- {#if settings.llmStatus.reachable && !settings.llmStatus.isLocal}· leaves your
- network{/if}
-
- {/if}
+
- {#if settings.llmStatus?.reachable && settings.llmStatus.models.length}
- Models: {settings.llmStatus.models.slice(0, 6).join(", ")}
+ {#if settings.llmStatus}
+ {@const s = settings.llmStatus}
+
+
+ {s.reachable ? "Reachable" : "Unreachable"}
+ {#if s.reachable}
+ · {s.models.length} model{s.models.length === 1 ? "" : "s"}
+ · {s.isLocal ? "on your machine / LAN" : "leaves your network"}
+ {/if}
+
+ {#if s.reachable && s.models.length}
+
+ {s.models.slice(0, 6).join(", ")}{s.models.length > 6 ? " …" : ""}
+
+ {/if}
+ {/if}
+
+ {#if llmProvider === "ollama"}
+ {#snippet paramRow(opt: OllamaOption)}
+ {@const disabled = opt.needsMirostat && mirostatOff()}
+
+ {/snippet}
+
+
+
+
+ Advanced Ollama configuration
+ {#if advChangedCount}{advChangedCount} changed{/if}
+
+
+ {#if advChangedCount}
+
+ {/if}
+
+
+
+ Added before WhispAssist's required output format, so your instructions can't
+ break summary/action-item parsing.
+
+
+ {#each OPTION_GROUPS as g (g)}
+
+ {/each}
+
+
+
+
+ Runtime & hardware
+ — rarely needed
+
+
+
+
+
+
+ {#if advSaved} Saved{/if}
+
+
+
{/if}
{:else}
@@ -1153,4 +1397,205 @@
ul.hosts li {
padding: 0.2rem 0;
}
+
+ /* ---- AI provider: status + advanced config ---- */
+ .ghost {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ background: transparent;
+ border: 1px solid var(--border);
+ color: var(--fg, inherit);
+ }
+ .ghost:hover {
+ background: var(--hover, rgba(127, 127, 127, 0.08));
+ }
+ .status-card {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ margin-top: 0.6rem;
+ padding: 0.5rem 0.7rem;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ font-size: 0.85rem;
+ }
+ .status-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--muted);
+ flex: none;
+ }
+ .status-dot.ok {
+ background: var(--success, #16a34a);
+ }
+ .status-dot.bad {
+ background: var(--danger, #dc2626);
+ }
+ .status-text {
+ font-weight: 600;
+ }
+ .muted.warn {
+ color: var(--danger, #dc2626);
+ }
+
+ .advanced,
+ .adv-nested {
+ margin-top: 0.9rem;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ }
+ .adv-nested {
+ margin-top: 0.6rem;
+ }
+ .advanced > summary,
+ .adv-nested > summary {
+ display: flex;
+ align-items: center;
+ gap: 0.45rem;
+ padding: 0.6rem 0.75rem;
+ cursor: pointer;
+ list-style: none;
+ font-weight: 600;
+ user-select: none;
+ }
+ .advanced > summary::-webkit-details-marker,
+ .adv-nested > summary::-webkit-details-marker {
+ display: none;
+ }
+ .advanced :global(.chevron),
+ .adv-nested :global(.chevron) {
+ transition: transform 0.18s ease;
+ flex: none;
+ color: var(--muted);
+ }
+ .advanced[open] > summary :global(.chevron),
+ .adv-nested[open] > summary :global(.chevron) {
+ transform: rotate(90deg);
+ }
+ @media (prefers-reduced-motion: reduce) {
+ .advanced :global(.chevron),
+ .adv-nested :global(.chevron) {
+ transition: none;
+ }
+ }
+ .changed-badge {
+ margin-left: auto;
+ font-size: 0.7rem;
+ font-weight: 500;
+ padding: 0.1rem 0.45rem;
+ border-radius: 10px;
+ background: var(--accent, #2563eb);
+ color: var(--accent-fg, #fff);
+ }
+ .adv-body {
+ padding: 0 0.75rem 0.85rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+ .reset-all {
+ align-self: flex-end;
+ font-size: 0.78rem;
+ }
+ .adv-system textarea {
+ width: 100%;
+ resize: vertical;
+ font-family: inherit;
+ font-size: 0.85rem;
+ padding: 0.4rem 0.5rem;
+ border: 1px solid var(--border-strong, var(--border));
+ border-radius: 6px;
+ }
+ .adv-group {
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 0.4rem 0.75rem 0.6rem;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ }
+ .adv-group legend {
+ font-size: 0.72rem;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--muted);
+ padding: 0 0.35rem;
+ }
+ .param {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.4rem 0;
+ border-bottom: 1px solid var(--border);
+ }
+ .param:last-child {
+ border-bottom: none;
+ }
+ .param-label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ min-width: 0;
+ flex: 1;
+ }
+ .param-name {
+ font-size: 0.85rem;
+ font-weight: 500;
+ }
+ .param-help {
+ font-size: 0.74rem;
+ color: var(--muted);
+ line-height: 1.35;
+ }
+ .param-control {
+ display: flex;
+ align-items: center;
+ gap: 0.3rem;
+ flex: none;
+ }
+ .param-control input[type="number"],
+ .param-control select {
+ width: 6.5rem;
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ padding: 0.3rem 0.4rem;
+ border: 1px solid var(--border-strong, var(--border));
+ border-radius: 6px;
+ }
+ .param-control select {
+ text-align: left;
+ }
+ .param-control input:not([type="number"]):not([type="checkbox"]) {
+ width: 9rem;
+ padding: 0.3rem 0.4rem;
+ border: 1px solid var(--border-strong, var(--border));
+ border-radius: 6px;
+ }
+ .param-disabled {
+ opacity: 0.45;
+ }
+ .reset-field {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.6rem;
+ height: 1.6rem;
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: var(--muted);
+ cursor: pointer;
+ border-radius: 5px;
+ }
+ .reset-field:hover {
+ background: var(--hover, rgba(127, 127, 127, 0.1));
+ color: var(--fg, inherit);
+ }
+ .reset-field.hidden {
+ visibility: hidden;
+ }