mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
feat(documents): add the AI-settings toggle to read documents automatically
Add a single switch in AI settings — off by default — that turns on ambient AI reading of uploaded vault documents. Turning it on reveals a once-shown, vendor-blind honesty confirm (subscription providers may use the content to improve their models; a self-hosted local model never egresses) that must be acknowledged before the setting is written. Copy is translated across all six locales.
This commit is contained in:
1 parent
d2451021bc
commit
b3ae35b055
9 files changed
+202
-6
No files matched your search
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "z. B. gpt-4o",
|
||||
"ocrVisionHint": "Dieses Modell kann möglicherweise keine Bilder lesen. Wähle für den Dokumentenscan ein Modell mit Bildverständnis.",
|
||||
"ocrKeyLabel": "API-Key"
|
||||
"ocrKeyLabel": "API-Key",
|
||||
"autoRead": {
|
||||
"title": "Dokumente automatisch mit KI lesen",
|
||||
"subLabel": "Neu hochgeladene Dokumente werden von deinem konfigurierten KI-Anbieter gelesen und indiziert, ohne dass du jedes Dokument einzeln bestätigen musst.",
|
||||
"honesty": "Wenn aktiviert, wird jedes hochgeladene Dokument an deinen konfigurierten KI-Anbieter gesendet, um gelesen, beschrieben und für die Suche indiziert zu werden – ohne Bestätigung pro Dokument. Das geschieht auf dem Server, und der Inhalt des Dokuments verlässt dabei diese Maschine in Richtung dieses Anbieters. Wenn dein konfigurierter Anbieter eine Abo-Verbindung ist (ein angemeldetes KI-Konto statt eines API-Schlüssels), gelten dessen eigene Datenschutzeinstellungen – Abo-Anbieter dürfen den Inhalt zur Verbesserung ihrer Modelle verwenden, und kein Auftragsverarbeitungsvertrag deckt das ab. Dokumente, die von einem selbst gehosteten lokalen Modell gelesen werden, verlassen deinen Server nie. Standardmäßig aus; aktiviere es nur, wenn du diesen Kompromiss für die von dir hochgeladenen Dokumente akzeptierst.",
|
||||
"confirm": "Ich verstehe – automatisches KI-Lesen aktivieren",
|
||||
"cancel": "Abbrechen"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "e.g. gpt-4o",
|
||||
"ocrVisionHint": "This model may not be able to read images. Choose a vision-capable model for document scanning.",
|
||||
"ocrKeyLabel": "API key"
|
||||
"ocrKeyLabel": "API key",
|
||||
"autoRead": {
|
||||
"title": "Read documents automatically with AI",
|
||||
"subLabel": "Newly uploaded documents are read and indexed by your configured AI provider without a per-document tap.",
|
||||
"honesty": "When on, each document you upload is sent to your configured AI provider to be read, described, and indexed for search — with no per-document confirmation. This happens on the server and the document's contents leave this machine to that provider. If your configured provider is a subscription connection (a signed-in AI account rather than an API key), that provider's own data settings apply — subscription providers may use the content to improve their models, and no data-processing agreement covers it. Documents read by a self-hosted local model never leave your server. Off by default; turn it on only if you accept this trade for the documents you upload.",
|
||||
"confirm": "I understand — turn on automatic AI reading",
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "p. ej. gpt-4o",
|
||||
"ocrVisionHint": "Puede que este modelo no pueda leer imágenes. Elige un modelo con capacidad de visión para el escaneo de documentos.",
|
||||
"ocrKeyLabel": "Clave de API"
|
||||
"ocrKeyLabel": "Clave de API",
|
||||
"autoRead": {
|
||||
"title": "Leer documentos automáticamente con IA",
|
||||
"subLabel": "Los documentos recién subidos son leídos e indexados por tu proveedor de IA configurado, sin confirmar cada documento.",
|
||||
"honesty": "Cuando está activado, cada documento que subes se envía a tu proveedor de IA configurado para leerlo, describirlo e indexarlo para la búsqueda, sin confirmación por documento. Esto ocurre en el servidor y el contenido del documento sale de esta máquina hacia ese proveedor. Si tu proveedor configurado es una conexión de suscripción (una cuenta de IA con sesión iniciada en lugar de una clave de API), se aplican sus propios ajustes de datos: los proveedores de suscripción pueden usar el contenido para mejorar sus modelos, y ningún acuerdo de tratamiento de datos lo cubre. Los documentos leídos por un modelo local autoalojado nunca salen de tu servidor. Desactivado de forma predeterminada; actívalo solo si aceptas este compromiso para los documentos que subes.",
|
||||
"confirm": "Lo entiendo: activar la lectura automática con IA",
|
||||
"cancel": "Cancelar"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "p. ex. gpt-4o",
|
||||
"ocrVisionHint": "Ce modèle ne peut peut-être pas lire les images. Choisissez un modèle capable de vision pour la numérisation de documents.",
|
||||
"ocrKeyLabel": "Clé API"
|
||||
"ocrKeyLabel": "Clé API",
|
||||
"autoRead": {
|
||||
"title": "Lire les documents automatiquement avec l'IA",
|
||||
"subLabel": "Les documents nouvellement téléversés sont lus et indexés par votre fournisseur d'IA configuré, sans validation document par document.",
|
||||
"honesty": "Lorsque cette option est activée, chaque document que vous téléversez est envoyé à votre fournisseur d'IA configuré pour être lu, décrit et indexé pour la recherche, sans confirmation par document. Cela se produit sur le serveur et le contenu du document quitte cette machine vers ce fournisseur. Si votre fournisseur configuré est une connexion par abonnement (un compte d'IA connecté plutôt qu'une clé API), ses propres paramètres de données s'appliquent : les fournisseurs par abonnement peuvent utiliser le contenu pour améliorer leurs modèles, et aucun accord de traitement des données ne le couvre. Les documents lus par un modèle local auto-hébergé ne quittent jamais votre serveur. Désactivé par défaut ; activez-le uniquement si vous acceptez ce compromis pour les documents que vous téléversez.",
|
||||
"confirm": "Je comprends — activer la lecture automatique par l'IA",
|
||||
"cancel": "Annuler"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "es. gpt-4o",
|
||||
"ocrVisionHint": "Questo modello potrebbe non essere in grado di leggere le immagini. Scegli un modello con capacità di visione per la scansione dei documenti.",
|
||||
"ocrKeyLabel": "Chiave API"
|
||||
"ocrKeyLabel": "Chiave API",
|
||||
"autoRead": {
|
||||
"title": "Leggi i documenti automaticamente con l'IA",
|
||||
"subLabel": "I documenti appena caricati vengono letti e indicizzati dal provider di IA configurato, senza confermare ogni documento.",
|
||||
"honesty": "Quando è attivo, ogni documento che carichi viene inviato al provider di IA configurato per essere letto, descritto e indicizzato per la ricerca, senza conferma per singolo documento. Questo avviene sul server e il contenuto del documento lascia questa macchina verso quel provider. Se il provider configurato è una connessione in abbonamento (un account IA con accesso effettuato anziché una chiave API), si applicano le sue impostazioni sui dati: i provider in abbonamento possono usare il contenuto per migliorare i propri modelli e nessun accordo sul trattamento dei dati lo copre. I documenti letti da un modello locale self-hosted non lasciano mai il tuo server. Disattivato per impostazione predefinita; attivalo solo se accetti questo compromesso per i documenti che carichi.",
|
||||
"confirm": "Ho capito: attiva la lettura automatica con l'IA",
|
||||
"cancel": "Annulla"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
+8
-1
@@ -4731,7 +4731,14 @@
|
||||
},
|
||||
"ocrModelPlaceholder": "np. gpt-4o",
|
||||
"ocrVisionHint": "Ten model może nie odczytywać obrazów. Wybierz model obsługujący obraz do skanowania dokumentów.",
|
||||
"ocrKeyLabel": "Klucz API"
|
||||
"ocrKeyLabel": "Klucz API",
|
||||
"autoRead": {
|
||||
"title": "Automatyczne czytanie dokumentów przez AI",
|
||||
"subLabel": "Nowo przesłane dokumenty są czytane i indeksowane przez skonfigurowanego dostawcę AI bez potwierdzania każdego dokumentu.",
|
||||
"honesty": "Po włączeniu każdy przesłany dokument jest wysyłany do skonfigurowanego dostawcy AI, aby go odczytać, opisać i zaindeksować do wyszukiwania — bez potwierdzania każdego dokumentu. Dzieje się to na serwerze, a treść dokumentu opuszcza to urządzenie i trafia do tego dostawcy. Jeśli skonfigurowany dostawca to połączenie subskrypcyjne (zalogowane konto AI zamiast klucza API), obowiązują jego własne ustawienia danych — dostawcy subskrypcyjni mogą wykorzystywać treść do ulepszania swoich modeli i nie obejmuje tego żadna umowa powierzenia przetwarzania danych. Dokumenty odczytywane przez samodzielnie hostowany model lokalny nigdy nie opuszczają Twojego serwera. Domyślnie wyłączone; włącz je tylko wtedy, gdy akceptujesz ten kompromis dla przesyłanych dokumentów.",
|
||||
"confirm": "Rozumiem — włącz automatyczne czytanie przez AI",
|
||||
"cancel": "Anuluj"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"units": {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
import { AdminOpenAIProviderForm } from "./admin-openai-provider-form";
|
||||
import { AnthropicProviderForm } from "./anthropic-provider-form";
|
||||
import { AutoReadCard } from "./auto-read-card";
|
||||
import { CodexProviderForm } from "./codex-provider-form";
|
||||
import { DocumentScanCard } from "./document-scan-card";
|
||||
import { FallbackChainCard } from "./fallback-chain-card";
|
||||
@@ -181,6 +182,9 @@ export function AiInsightsCard({
|
||||
{/* v1.22 (#90) — dedicated document-scan (Lab-OCR) provider. */}
|
||||
<DocumentScanCard userProvider={userProvider} />
|
||||
|
||||
{/* Read uploaded vault documents automatically with AI (opt-in). */}
|
||||
<AutoReadCard />
|
||||
|
||||
<RuntimeActionsRow
|
||||
provider={selectedProvider}
|
||||
userProvider={userProvider}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
"use client";
|
||||
|
||||
/* ────────────────────────────────────────────────────────────────
|
||||
* Read documents automatically with AI (per-user opt-in).
|
||||
*
|
||||
* One switch. OFF by default: the vault stays local-first and every external AI
|
||||
* read of an uploaded document needs an explicit per-document action. When ON,
|
||||
* each newly uploaded document is read and indexed by the configured AI provider
|
||||
* with no per-document tap — the "upload and the AI just reads it" flow.
|
||||
*
|
||||
* Turning it ON reveals a once-shown honesty confirm (vendor-blind) before the
|
||||
* setting is written, because the trade — a subscription provider may use the
|
||||
* content to improve its models, with no data-processing agreement — must be
|
||||
* acknowledged, not buried. Turning it OFF is immediate.
|
||||
* ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ShieldAlert } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
interface AutoReadPref {
|
||||
documentsAutoAiRead: boolean;
|
||||
}
|
||||
|
||||
export function AutoReadCard() {
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Whether the honesty confirm is currently shown (user flipped the switch ON
|
||||
// but has not yet acknowledged the trade). Off→on reveals it; the write only
|
||||
// happens on confirm.
|
||||
const [pendingEnable, setPendingEnable] = useState(false);
|
||||
|
||||
const { data, isLoading } = useQuery<AutoReadPref>({
|
||||
queryKey: queryKeys.documentsAutoAiRead(),
|
||||
queryFn: () => apiGet<AutoReadPref>("/api/auth/me/documents-auto-ai-read"),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
const enabled = data?.documentsAutoAiRead ?? false;
|
||||
|
||||
const save = useMutation<AutoReadPref, Error, boolean>({
|
||||
mutationFn: (next: boolean) =>
|
||||
apiPatch<AutoReadPref>("/api/auth/me/documents-auto-ai-read", {
|
||||
documentsAutoAiRead: next,
|
||||
}),
|
||||
onSuccess: (result) => {
|
||||
queryClient.setQueryData(queryKeys.documentsAutoAiRead(), result);
|
||||
// Flipping the toggle changes whether an ambient/per-document read
|
||||
// egresses without a per-document consent step — refresh the capability
|
||||
// probe the vault UI reads.
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.inboundDocumentAiCapability(),
|
||||
});
|
||||
setPendingEnable(false);
|
||||
},
|
||||
});
|
||||
|
||||
function onSwitch(next: boolean) {
|
||||
if (next) {
|
||||
// Off → on: reveal the honesty confirm; do NOT write yet.
|
||||
if (!enabled) setPendingEnable(true);
|
||||
return;
|
||||
}
|
||||
// On → off (or cancel a pending enable): write immediately, hide confirm.
|
||||
setPendingEnable(false);
|
||||
if (enabled) save.mutate(false);
|
||||
}
|
||||
|
||||
const busy = isLoading || save.isPending;
|
||||
const checked = enabled || pendingEnable;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="documents-auto-read-card"
|
||||
className="bg-muted/50 space-y-4 rounded-lg p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{t("settings.ai.autoRead.title")}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t("settings.ai.autoRead.subLabel")}
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={checked}
|
||||
disabled={busy}
|
||||
onCheckedChange={onSwitch}
|
||||
aria-label={t("settings.ai.autoRead.title")}
|
||||
data-testid="documents-auto-read-enable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{pendingEnable ? (
|
||||
<div
|
||||
data-slot="documents-auto-read-confirm"
|
||||
role="note"
|
||||
className="border-border space-y-3 rounded-lg border border-dashed px-3 py-2.5"
|
||||
>
|
||||
<div className="text-muted-foreground flex items-start gap-2 text-xs">
|
||||
<ShieldAlert className="mt-0.5 size-3.5 shrink-0" aria-hidden />
|
||||
<p className="min-w-0">{t("settings.ai.autoRead.honesty")}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="min-h-11 sm:min-h-9"
|
||||
disabled={save.isPending}
|
||||
onClick={() => save.mutate(true)}
|
||||
data-slot="documents-auto-read-confirm-cta"
|
||||
>
|
||||
{t("settings.ai.autoRead.confirm")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="min-h-11 sm:min-h-9"
|
||||
disabled={save.isPending}
|
||||
onClick={() => setPendingEnable(false)}
|
||||
>
|
||||
{t("settings.ai.autoRead.cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{save.isError ? (
|
||||
<p className="text-destructive text-xs">
|
||||
{t("settings.ai.errorGeneric")}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -76,4 +76,10 @@ export const documentKeys = {
|
||||
*/
|
||||
inboundDocumentChat: (documentId: string) =>
|
||||
["documents", "inbound", documentId, "chat"] as const,
|
||||
/**
|
||||
* The per-user "read documents automatically with AI" opt-in
|
||||
* (`GET/PATCH /api/auth/me/documents-auto-ai-read`). Drives the AI-settings
|
||||
* toggle; a flip invalidates the document AI capability probe.
|
||||
*/
|
||||
documentsAutoAiRead: () => ["documents", "auto-ai-read"] as const,
|
||||
};
|
||||
Reference in new issue
Block a user