diff --git a/messages/de.json b/messages/de.json index cfe7abcc4..60821114e 100644 --- a/messages/de.json +++ b/messages/de.json @@ -11769,8 +11769,9 @@ "dismiss": "Verwerfen", "empty": "Aus diesem Dokument konnte nichts gelesen werden.", "unavailableBody": "Richte einen KI-Anbieter ein, um Titel, Typ und Datum vorzuschlagen.", - "unavailableLocalOcr": "Aktiviere lokale Texterkennung in den KI-Einstellungen, um Bilder auf diesem Gerät zu lesen.", + "unavailableLocalOcr": "Schalte „Scans auf diesem Gerät lesen“ unter Einstellungen → Module → Labor ein, um Bilder auf diesem Gerät zu lesen.", "unavailableAction": "KI-Einstellungen öffnen", + "unavailableLocalOcrAction": "Labor-Einstellungen öffnen", "errorGeneric": "Dokument konnte nicht gelesen werden. Bitte erneut versuchen.", "errorOcr": "Aus diesem Bild konnte kein Text gelesen werden.", "errorRateLimited": "Das stündliche KI-Leselimit ist erreicht. Bitte später erneut versuchen.", @@ -11782,7 +11783,7 @@ "errorFileType": "Dieser Dateityp kann nicht gelesen werden.", "errorNotIndexed": "Lies das Dokument zuerst aus, dann extrahiere.", "errorOutputLimit": "Der KI-Anbieter hat sein Ausgabelimit erreicht, bevor er einen einzigen Wert geliefert hat. Versuche ein Modell, das längere Antworten erlaubt.", - "errorLocalOcrDisabled": "Aktiviere zuerst die lokale Texterkennung in den KI-Einstellungen.", + "errorLocalOcrDisabled": "Schalte zuerst „Scans auf diesem Gerät lesen“ unter Einstellungen → Module → Labor ein.", "errorProvider": "Dafür ist kein KI-Anbieter eingerichtet.", "errorTextImageOnly": "Lokale Texterkennung liest nur Bilder, nicht diese Datei.", "errorConsent": "Für das Lesen von Dokumenten mit KI brauchst du zuerst deine Einwilligung.", diff --git a/messages/en.json b/messages/en.json index 9e7fa4317..53b89951f 100644 --- a/messages/en.json +++ b/messages/en.json @@ -11769,8 +11769,9 @@ "dismiss": "Dismiss", "empty": "Nothing could be read from this document.", "unavailableBody": "Set up an AI provider to suggest a title, type, and date.", - "unavailableLocalOcr": "Turn on local OCR in the AI settings to read images on this device.", + "unavailableLocalOcr": "Turn on “Read scans on this device” under Settings → Modules → Labs to read images on this device.", "unavailableAction": "Open AI settings", + "unavailableLocalOcrAction": "Open lab settings", "errorGeneric": "Couldn't read the document. Try again.", "errorOcr": "Couldn't read text from this image.", "errorRateLimited": "The hourly AI reading limit is reached. Try again later.", @@ -11782,7 +11783,7 @@ "errorFileType": "This file type can't be read.", "errorNotIndexed": "Read the document first, then extract.", "errorOutputLimit": "The AI provider reached its output limit before it returned a single value. Try a model that allows longer answers.", - "errorLocalOcrDisabled": "Turn on local OCR in the AI settings first.", + "errorLocalOcrDisabled": "Turn on “Read scans on this device” under Settings → Modules → Labs first.", "errorProvider": "No AI provider is set up for this.", "errorTextImageOnly": "Local OCR reads images only, not this file.", "errorConsent": "Reading documents with AI needs your consent first.", diff --git a/messages/es.json b/messages/es.json index 55dd09b69..be842de69 100644 --- a/messages/es.json +++ b/messages/es.json @@ -11769,8 +11769,9 @@ "dismiss": "Descartar", "empty": "No se pudo leer nada de este documento.", "unavailableBody": "Configura un proveedor de IA para sugerir título, tipo y fecha.", - "unavailableLocalOcr": "Activa el OCR local en los ajustes de IA para leer imágenes en este dispositivo.", + "unavailableLocalOcr": "Activa «Leer escaneos en este dispositivo» en Ajustes → Módulos → Laboratorio para leer imágenes en este dispositivo.", "unavailableAction": "Abrir ajustes de IA", + "unavailableLocalOcrAction": "Abrir ajustes de laboratorio", "errorGeneric": "No se pudo leer el documento. Inténtalo de nuevo.", "errorOcr": "No se pudo leer texto de esta imagen.", "errorRateLimited": "Se alcanzó el límite de lecturas de IA por hora. Inténtalo más tarde.", @@ -11782,7 +11783,7 @@ "errorFileType": "Este tipo de archivo no se puede leer.", "errorNotIndexed": "Primero lee el documento y luego extrae.", "errorOutputLimit": "El proveedor de IA alcanzó su límite de salida antes de devolver un solo valor. Prueba con un modelo que permita respuestas más largas.", - "errorLocalOcrDisabled": "Activa primero el OCR local en los ajustes de IA.", + "errorLocalOcrDisabled": "Activa primero «Leer escaneos en este dispositivo» en Ajustes → Módulos → Laboratorio.", "errorProvider": "No hay ningún proveedor de IA configurado para esto.", "errorTextImageOnly": "El OCR local solo lee imágenes, no este archivo.", "errorConsent": "Leer documentos con IA necesita primero tu consentimiento.", diff --git a/messages/fr.json b/messages/fr.json index d74a6043b..e009d81f5 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -11769,8 +11769,9 @@ "dismiss": "Ignorer", "empty": "Impossible de lire ce document.", "unavailableBody": "Configurez un fournisseur d'IA pour suggérer un titre, un type et une date.", - "unavailableLocalOcr": "Activez l'OCR local dans les paramètres d'IA pour lire les images sur cet appareil.", + "unavailableLocalOcr": "Activez « Lire les scans sur cet appareil » dans Paramètres → Modules → Analyses pour lire les images sur cet appareil.", "unavailableAction": "Ouvrir les paramètres d'IA", + "unavailableLocalOcrAction": "Ouvrir les paramètres des analyses", "errorGeneric": "Impossible de lire le document. Réessayez.", "errorOcr": "Impossible de lire le texte de cette image.", "errorRateLimited": "La limite horaire de lectures IA est atteinte. Réessayez plus tard.", @@ -11782,7 +11783,7 @@ "errorFileType": "Ce type de fichier ne peut pas être lu.", "errorNotIndexed": "Lisez d’abord le document, puis extrayez.", "errorOutputLimit": "Le fournisseur d’IA a atteint sa limite de sortie avant de renvoyer la moindre valeur. Essayez un modèle qui accepte des réponses plus longues.", - "errorLocalOcrDisabled": "Activez d'abord l'OCR local dans les paramètres d'IA.", + "errorLocalOcrDisabled": "Activez d'abord « Lire les scans sur cet appareil » dans Paramètres → Modules → Analyses.", "errorProvider": "Aucun fournisseur d'IA n'est configuré pour cela.", "errorTextImageOnly": "L'OCR local ne lit que les images, pas ce fichier.", "errorConsent": "La lecture de documents par IA nécessite d'abord votre consentement.", diff --git a/messages/it.json b/messages/it.json index b1de9b105..47004b65b 100644 --- a/messages/it.json +++ b/messages/it.json @@ -11769,8 +11769,9 @@ "dismiss": "Ignora", "empty": "Impossibile leggere questo documento.", "unavailableBody": "Configura un provider IA per suggerire titolo, tipo e data.", - "unavailableLocalOcr": "Attiva l'OCR locale nelle impostazioni IA per leggere le immagini su questo dispositivo.", + "unavailableLocalOcr": "Attiva «Leggi le scansioni su questo dispositivo» in Impostazioni → Moduli → Esami per leggere le immagini su questo dispositivo.", "unavailableAction": "Apri impostazioni IA", + "unavailableLocalOcrAction": "Apri le impostazioni degli esami", "errorGeneric": "Impossibile leggere il documento. Riprova.", "errorOcr": "Impossibile leggere il testo da questa immagine.", "errorRateLimited": "Il limite orario di letture IA è stato raggiunto. Riprova più tardi.", @@ -11782,7 +11783,7 @@ "errorFileType": "Questo tipo di file non può essere letto.", "errorNotIndexed": "Leggi prima il documento, poi estrai.", "errorOutputLimit": "Il fornitore di IA ha raggiunto il suo limite di output prima di restituire un solo valore. Prova con un modello che consente risposte più lunghe.", - "errorLocalOcrDisabled": "Attiva prima l'OCR locale nelle impostazioni IA.", + "errorLocalOcrDisabled": "Attiva prima «Leggi le scansioni su questo dispositivo» in Impostazioni → Moduli → Esami.", "errorProvider": "Nessun provider IA è configurato per questo.", "errorTextImageOnly": "L'OCR locale legge solo immagini, non questo file.", "errorConsent": "La lettura dei documenti con IA richiede prima il tuo consenso.", diff --git a/messages/ko.json b/messages/ko.json index 3fbdc541b..7e743c844 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -11769,8 +11769,9 @@ "dismiss": "닫기", "empty": "이 문서에서 읽어낼 내용이 없었어요.", "unavailableBody": "AI 제공자를 설정하면 제목·종류·날짜를 추천해요.", - "unavailableLocalOcr": "AI 설정에서 로컬 OCR을 켜면 이 기기에서 이미지를 읽어요.", + "unavailableLocalOcr": "설정 → 모듈 → 검사에서 “이 기기에서 스캔 읽기”를 켜면 이 기기에서 이미지를 읽어요.", "unavailableAction": "AI 설정 열기", + "unavailableLocalOcrAction": "검사 설정 열기", "errorGeneric": "문서를 읽지 못했어요. 다시 시도해 주세요.", "errorOcr": "이 이미지에서 텍스트를 읽지 못했어요.", "errorRateLimited": "시간당 AI 읽기 한도에 도달했어요. 나중에 다시 시도해 주세요.", @@ -11782,7 +11783,7 @@ "errorFileType": "이 파일 형식은 읽을 수 없어요.", "errorNotIndexed": "문서를 먼저 읽은 뒤에 추출해 주세요.", "errorOutputLimit": "AI 제공자가 값을 하나도 돌려주기 전에 출력 한도에 도달했어요. 더 긴 답변을 허용하는 모델로 다시 시도해 주세요.", - "errorLocalOcrDisabled": "AI 설정에서 로컬 OCR을 먼저 켜 주세요.", + "errorLocalOcrDisabled": "설정 → 모듈 → 검사에서 “이 기기에서 스캔 읽기”를 먼저 켜 주세요.", "errorProvider": "이 작업에 쓸 AI 제공자가 설정돼 있지 않아요.", "errorTextImageOnly": "로컬 OCR은 이미지만 읽어요. 이 파일은 읽을 수 없어요.", "errorConsent": "AI로 문서를 읽으려면 먼저 동의가 필요합니다.", diff --git a/messages/nl.json b/messages/nl.json index af2efc0a4..6a390f9eb 100644 --- a/messages/nl.json +++ b/messages/nl.json @@ -11769,8 +11769,9 @@ "dismiss": "Negeren", "empty": "Er kon niets uit dit document worden gelezen.", "unavailableBody": "Stel een AI-provider in om een titel, soort en datum voor te stellen.", - "unavailableLocalOcr": "Zet lokale OCR aan in de AI-instellingen om afbeeldingen op dit apparaat te lezen.", + "unavailableLocalOcr": "Zet “Scans op dit apparaat lezen” aan onder Instellingen → Modules → Labwaarden om afbeeldingen op dit apparaat te lezen.", "unavailableAction": "AI-instellingen openen", + "unavailableLocalOcrAction": "Instellingen voor labwaarden openen", "errorGeneric": "Het document kon niet worden gelezen. Probeer het opnieuw.", "errorOcr": "Er kon geen tekst uit deze afbeelding worden gelezen.", "errorRateLimited": "De limiet voor lezen met AI per uur is bereikt. Probeer het later opnieuw.", @@ -11782,7 +11783,7 @@ "errorFileType": "Dit bestandstype kan niet worden gelezen.", "errorNotIndexed": "Laat het document eerst lezen en extraheer daarna.", "errorOutputLimit": "De AI-provider bereikte zijn uitvoerlimiet voordat hij één waarde teruggaf. Probeer een model dat langere antwoorden toestaat.", - "errorLocalOcrDisabled": "Zet eerst lokale OCR aan in de AI-instellingen.", + "errorLocalOcrDisabled": "Zet eerst “Scans op dit apparaat lezen” aan onder Instellingen → Modules → Labwaarden.", "errorProvider": "Hiervoor is geen AI-provider ingesteld.", "errorTextImageOnly": "Lokale OCR leest alleen afbeeldingen, niet dit bestand.", "errorConsent": "Voor het lezen van documenten met AI is eerst je toestemming nodig.", diff --git a/messages/pl.json b/messages/pl.json index b0dec38cc..0d86a2800 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -11769,8 +11769,9 @@ "dismiss": "Odrzuć", "empty": "Nie udało się nic odczytać z tego dokumentu.", "unavailableBody": "Skonfiguruj dostawcę AI, aby zaproponować tytuł, typ i datę.", - "unavailableLocalOcr": "Włącz lokalne OCR w ustawieniach AI, aby odczytywać obrazy na tym urządzeniu.", + "unavailableLocalOcr": "Włącz „Odczytuj skany na tym urządzeniu” w Ustawienia → Moduły → Badania, aby odczytywać obrazy na tym urządzeniu.", "unavailableAction": "Otwórz ustawienia AI", + "unavailableLocalOcrAction": "Otwórz ustawienia badań", "errorGeneric": "Nie udało się odczytać dokumentu. Spróbuj ponownie.", "errorOcr": "Nie udało się odczytać tekstu z tego obrazu.", "errorRateLimited": "Godzinowy limit odczytów AI został osiągnięty. Spróbuj ponownie później.", @@ -11782,7 +11783,7 @@ "errorFileType": "Tego typu pliku nie można odczytać.", "errorNotIndexed": "Najpierw odczytaj dokument, potem wyodrębnij.", "errorOutputLimit": "Dostawca AI osiągnął limit odpowiedzi, zanim zwrócił choć jedną wartość. Spróbuj modelu, który pozwala na dłuższe odpowiedzi.", - "errorLocalOcrDisabled": "Najpierw włącz lokalne OCR w ustawieniach AI.", + "errorLocalOcrDisabled": "Najpierw włącz „Odczytuj skany na tym urządzeniu” w Ustawienia → Moduły → Badania.", "errorProvider": "Nie skonfigurowano dostawcy AI do tego celu.", "errorTextImageOnly": "Lokalne OCR odczytuje tylko obrazy, nie ten plik.", "errorConsent": "Odczyt dokumentów przez AI wymaga najpierw Twojej zgody.", diff --git a/messages/pt-BR.json b/messages/pt-BR.json index 3137e637c..33a57ff8b 100644 --- a/messages/pt-BR.json +++ b/messages/pt-BR.json @@ -11769,8 +11769,9 @@ "dismiss": "Dispensar", "empty": "Nada pôde ser lido deste documento.", "unavailableBody": "Configure um provedor de IA para sugerir um título, tipo e data.", - "unavailableLocalOcr": "Ative o OCR local nas configurações de IA para ler imagens neste dispositivo.", + "unavailableLocalOcr": "Ative “Ler digitalizações neste dispositivo” em Configurações → Módulos → Exames para ler imagens neste dispositivo.", "unavailableAction": "Abrir configurações de IA", + "unavailableLocalOcrAction": "Abrir configurações de exames", "errorGeneric": "Não foi possível ler o documento. Tente novamente.", "errorOcr": "Não foi possível ler texto desta imagem.", "errorRateLimited": "O limite horário de leitura por IA foi atingido. Tente novamente mais tarde.", @@ -11782,7 +11783,7 @@ "errorFileType": "Este tipo de arquivo não pode ser lido.", "errorNotIndexed": "Leia o documento primeiro, depois extraia.", "errorOutputLimit": "O provedor de IA atingiu o limite de saída antes de devolver um único valor. Tente um modelo que permita respostas mais longas.", - "errorLocalOcrDisabled": "Ative o OCR local nas configurações de IA primeiro.", + "errorLocalOcrDisabled": "Ative primeiro “Ler digitalizações neste dispositivo” em Configurações → Módulos → Exames.", "errorProvider": "Nenhum provedor de IA está configurado para isso.", "errorTextImageOnly": "O OCR local lê apenas imagens, não este arquivo.", "errorConsent": "Ler documentos com IA precisa do seu consentimento primeiro.", diff --git a/src/components/documents/__tests__/document-ai-panels.test.tsx b/src/components/documents/__tests__/document-ai-panels.test.tsx index 53748f937..d6e2c20dc 100644 --- a/src/components/documents/__tests__/document-ai-panels.test.tsx +++ b/src/components/documents/__tests__/document-ai-panels.test.tsx @@ -1,7 +1,12 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import { I18nProvider } from "@/lib/i18n/context"; + +const record = vi.hoisted(() => ({ inSharedRecord: false })); +vi.mock("@/hooks/use-record-capabilities", () => ({ + useRecordCapabilities: () => ({ inSharedRecord: record.inSharedRecord }), +})); import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents"; import { AiUnavailableHint, @@ -57,10 +62,26 @@ describe("", () => { expect(html).not.toContain('role="alert"'); }); - it("nudges toward local OCR when a text-only provider needs it", () => { + it("points at the on-device scan switch when a text-only provider needs it", () => { const html = render(); - expect(html).toContain("local OCR"); - expect(html).toContain('href="/settings/ai"'); + // The switch lives on the Labs module page, under its own label. + expect(html).toContain("Read scans on this device"); + expect(html).toContain("Settings → Modules → Labs"); + expect(html).toContain('href="/settings/modules/labs#labs-local-ocr"'); + expect(html).toContain("Open lab settings"); + expect(html).not.toContain('href="/settings/ai"'); + }); + + it("keeps the sentence but withholds the Settings link inside somebody else's record", () => { + record.inSharedRecord = true; + try { + const html = render(); + expect(html).toContain("Read scans on this device"); + expect(html).not.toContain('data-slot="assist-settings-link"'); + expect(html).not.toContain('href="/settings'); + } finally { + record.inSharedRecord = false; + } }); }); diff --git a/src/components/documents/__tests__/document-ai-section.test.tsx b/src/components/documents/__tests__/document-ai-section.test.tsx index 742859335..c6b85142d 100644 --- a/src/components/documents/__tests__/document-ai-section.test.tsx +++ b/src/components/documents/__tests__/document-ai-section.test.tsx @@ -1,7 +1,12 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import { I18nProvider } from "@/lib/i18n/context"; + +const record = vi.hoisted(() => ({ inSharedRecord: false })); +vi.mock("@/hooks/use-record-capabilities", () => ({ + useRecordCapabilities: () => ({ inSharedRecord: record.inSharedRecord }), +})); import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents"; import { DocumentAiSection } from "../document-ai-section"; diff --git a/src/components/documents/document-ai-panels.tsx b/src/components/documents/document-ai-panels.tsx index b2c5e8d66..114f3d987 100644 --- a/src/components/documents/document-ai-panels.tsx +++ b/src/components/documents/document-ai-panels.tsx @@ -22,6 +22,7 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import type { AiRunPhase } from "@/hooks/use-ai-run"; +import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import { @@ -64,9 +65,13 @@ export function AiRunPhaseNote({ } /** - * Calm pointer to the AI settings, shown when assist is unavailable. When the - * document is already searchable (auto-indexed locally) the copy stays honest — - * it is searchable, an AI provider only adds a richer read. + * Calm pointer to the setting that would make assist available: the AI + * settings when no provider is configured, the on-device scan switch on the + * Labs module page when the provider cannot read images. Both live under + * `/settings`, which is the caller's own and closed inside somebody else's + * record, so the link is withheld there and the sentence stands alone. When + * the document is already searchable (auto-indexed locally) the copy stays + * honest — it is searchable, an AI provider only adds a richer read. */ export function AiUnavailableHint({ reason, @@ -74,10 +79,11 @@ export function AiUnavailableHint({ reason: "no-provider" | "enable-local-ocr" | null; }) { const { t } = useTranslations(); - const body = - reason === "enable-local-ocr" - ? t("documents.assist.unavailableLocalOcr") - : t("documents.assist.unavailableBody"); + const { inSharedRecord } = useRecordCapabilities(); + const localOcr = reason === "enable-local-ocr"; + const body = localOcr + ? t("documents.assist.unavailableLocalOcr") + : t("documents.assist.unavailableBody"); return (

- {body}{" "} - - {t("documents.assist.unavailableAction")} - + {body} + {!inSharedRecord && ( + <> + {" "} + + {localOcr + ? t("documents.assist.unavailableLocalOcrAction") + : t("documents.assist.unavailableAction")} + + + )}

);