Feature chore bug 007 #21

Merged
iamdoubz merged 68 commits from feature_chore_bug_007 into main 2026-07-12 12:59:04 -05:00
Showing only changes of commit 27a1f98051 - Show all commits
+74 -44
View File
@@ -1836,41 +1836,51 @@
</section>
{:else if section === "privacy"}
<section>
<h3>Privacy</h3>
<p class="muted">
What WhispAssist is actually allowed to send off this device right now. With everything
off (the default), nothing leaves the device at all.
</p>
<h3>{t("settings.privacy.title")}</h3>
<p class="muted">{t("settings.privacy.intro")}</p>
{#if settings.privacy}
<div class="row">
LLM endpoint<code>{settings.privacy.llmEndpoint || "off"}</code>
{t("settings.privacy.llm_endpoint")}<code
>{settings.privacy.llmEndpoint || t("settings.privacy.off")}</code
>
<span class="badge" class:third={!settings.privacy.llmIsLocal}>
{settings.privacy.llmIsLocal ? "local-only" : "leaves this device"}
{settings.privacy.llmIsLocal
? t("settings.privacy.local_only")
: t("settings.privacy.leaves_device")}
</span>
</div>
<div class="row">
Sync<span class="badge" class:third={settings.privacy.syncEnabled}>
{settings.privacy.syncEnabled ? "enabled" : "off"}
{t("settings.privacy.sync_label")}<span
class="badge"
class:third={settings.privacy.syncEnabled}
>
{settings.privacy.syncEnabled
? t("settings.privacy.enabled")
: t("settings.privacy.off")}
</span>
</div>
<div class="row">
MCP server<span class="badge" class:busy={settings.mcpStatus?.enabled}>
{settings.mcpStatus?.enabled ? `on · ${settings.mcpStatus.exposeScope}` : "off"}
{t("settings.privacy.mcp_label")}<span
class="badge"
class:busy={settings.mcpStatus?.enabled}
>
{settings.mcpStatus?.enabled
? t("settings.privacy.mcp_on", { scope: settings.mcpStatus.exposeScope })
: t("settings.privacy.off")}
</span>
</div>
{#if settings.mcpStatus?.enabled}
<p class="muted confirm">
<Check size={14} aria-hidden="true" />
Inbound on loopback only — it adds nothing to the egress list above. A connected agent may
still forward what it reads to its own model provider; see the MCP server tab.
{t("settings.privacy.mcp_loopback_note")}
</p>
{/if}
<h4>Egress allowlist</h4>
<h4>{t("settings.privacy.egress_title")}</h4>
{#if settings.privacy.allowlistedHosts.length === 0}
<p class="muted confirm">
<Check size={14} aria-hidden="true" />
No hosts are allowlisted — WA makes no content egress.
{t("settings.privacy.no_egress")}
</p>
{:else}
<ul class="hosts">
@@ -1881,70 +1891,90 @@
{/if}
{#if settings.privacy.syncTargets.length > 0}
<h4>Sync targets</h4>
<h4>{t("settings.privacy.sync_targets_title")}</h4>
<ul class="targets">
{#each settings.privacy.syncTargets as t (t.name)}
{#each settings.privacy.syncTargets as target (target.name)}
<li>
<span class="name">{t.name}</span>
<span class="badge" class:third={t.thirdParty}
>{t.thirdParty ? "third-party" : "your server"}</span
<span class="name">{target.name}</span>
<span class="badge" class:third={target.thirdParty}
>{target.thirdParty
? t("settings.privacy.third_party")
: t("settings.privacy.your_server")}</span
>
<span class="host">{target.host}</span>
<span class="badge" class:third={!target.tls}
>{target.tls ? t("settings.privacy.tls") : t("settings.privacy.no_tls")}</span
>
<span class="host">{t.host}</span>
<span class="badge" class:third={!t.tls}>{t.tls ? "TLS" : "no TLS"}</span>
</li>
{/each}
</ul>
{/if}
<button class="link" onclick={() => settings.loadPrivacy()}>Refresh</button>
<button class="link" onclick={() => settings.loadPrivacy()}
>{t("settings.privacy.refresh")}</button
>
{:else}
<p class="muted">Privacy self-check unavailable.</p>
<p class="muted">{t("settings.privacy.unavailable")}</p>
{/if}
{#if vault}
<h4>Encryption vault</h4>
<h4>{t("settings.privacy.vault_title")}</h4>
{#if !vault.enabled}
<p class="muted">
Encrypt notes, transcripts, and summaries at rest with a password. (Audio files are
not encrypted yet.)
</p>
<p class="muted">{t("settings.privacy.vault_intro")}</p>
<div class="grid">
<label class="wide"
>Vault password<input type="password" bind:value={vaultPw} /></label
>{t("settings.privacy.vault_password")}<input
type="password"
bind:value={vaultPw}
/></label
>
</div>
<button class="primary" onclick={enableVault} disabled={vaultPw.length < 8}
>Enable vault</button
>{t("settings.privacy.enable_vault")}</button
>
<p class="muted">
Use at least 8 characters. If you forget it, encrypted content can't be recovered.
</p>
<p class="muted">{t("settings.privacy.vault_pw_hint")}</p>
{:else if !vault.unlocked}
<p class="muted">
Vault is <strong>locked</strong>. Unlock to read encrypted meetings.
{t("settings.privacy.vault_locked_1")}<strong
>{t("settings.privacy.locked_word")}</strong
>{t("settings.privacy.vault_locked_2")}
</p>
<div class="grid">
<label class="wide">Password<input type="password" bind:value={vaultPw} /></label>
<label class="wide"
>{t("settings.privacy.password")}<input
type="password"
bind:value={vaultPw}
/></label
>
</div>
<button class="primary" onclick={unlockVault} disabled={!vaultPw}>Unlock</button>
<button class="primary" onclick={unlockVault} disabled={!vaultPw}
>{t("settings.privacy.unlock")}</button
>
{:else}
<p class="muted">
Vault is <strong>unlocked</strong>. New notes, transcripts, and summaries are
encrypted at rest.
{t("settings.privacy.vault_unlocked_1")}<strong
>{t("settings.privacy.unlocked_word")}</strong
>{t("settings.privacy.vault_unlocked_2")}
</p>
<button onclick={lockVault}>Lock now</button>
<button onclick={lockVault}>{t("settings.privacy.lock_now")}</button>
<details>
<summary>Change password</summary>
<summary>{t("settings.privacy.change_password")}</summary>
<div class="grid">
<label class="wide"
>Current password<input type="password" bind:value={vaultPw} /></label
>{t("settings.privacy.current_password")}<input
type="password"
bind:value={vaultPw}
/></label
>
<label class="wide"
>New password<input type="password" bind:value={vaultPw2} /></label
>{t("settings.privacy.new_password")}<input
type="password"
bind:value={vaultPw2}
/></label
>
</div>
<button onclick={changeVaultPassword} disabled={!vaultPw || vaultPw2.length < 8}
>Change password</button
>{t("settings.privacy.change_password")}</button
>
</details>
{/if}