diff --git a/messages/de.json b/messages/de.json index 5073c6e7b..06011e984 100644 --- a/messages/de.json +++ b/messages/de.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Nur Download", "uploading": "Wird hochgeladen…", "dismissFailed": "Fehlgeschlagenen Upload ausblenden", - "searchableBadge": "Inhalt durchsuchbar" + "searchableBadge": "Inhalt durchsuchbar", + "aiReadBadge": "Von KI gelesen" }, "error": { "fileTooLarge": "Diese Datei ist größer als das Limit von {maxSize}.", @@ -8206,15 +8207,21 @@ "notSaved": "Nicht gespeichert · keine Diagnose. Nur einmalig für diese Ansicht angezeigt.", "close": "Ausblenden" }, + "ai": { + "blockTitle": "Mit KI auslesen", + "available": "Lass die KI dieses Dokument lesen — Titel, Art und Datum vorschlagen, zusammenfassen oder den ganzen Text durchsuchbar machen.", + "read": "Mit KI auslesen", + "reread": "Erneut lesen", + "reading": "Liest…", + "statusAiRead": "Von KI gelesen", + "statusSearchable": "Durchsuchbar", + "statusIndexing": "Wird durchsuchbar gemacht…", + "statusNotYet": "Noch nicht durchsuchbar", + "readDone": "Die KI hat dein Dokument gelesen." + }, "contentIndex": { "searchHint": "Die Suche durchsucht jetzt auch den Inhalt deiner Dokumente (ganze Wörter).", "indexPrompt": "Dokumentinhalte durchsuchbar machen.", - "searchable": "Inhalt durchsuchbar", - "notSearchable": "Inhalt noch nicht durchsuchbar", - "indexAction": "Für Suche indizieren", - "reindexAction": "Neu indizieren", - "indexing": "Wird indiziert…", - "indexed": "Für die Suche indiziert.", "indexAll": "Alle für Suche indizieren", "indexAllPending": "Wird gestartet…", "indexAllQueued": "{count} Dokument(e) werden im Hintergrund indiziert.", diff --git a/messages/en.json b/messages/en.json index e85e5c5fc..2b98c3856 100644 --- a/messages/en.json +++ b/messages/en.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Download only", "uploading": "Uploading…", "dismissFailed": "Dismiss failed upload", - "searchableBadge": "Contents searchable" + "searchableBadge": "Contents searchable", + "aiReadBadge": "Read by AI" }, "error": { "fileTooLarge": "This file is larger than the {maxSize} limit.", @@ -8206,15 +8207,21 @@ "notSaved": "Not saved · not a diagnosis. Shown once for this view only.", "close": "Hide" }, + "ai": { + "blockTitle": "Read with AI", + "available": "Let the AI read this document — suggest a title, type and date, summarise it, or make its full text searchable.", + "read": "Read with AI", + "reread": "Read again", + "reading": "Reading…", + "statusAiRead": "Read by AI", + "statusSearchable": "Searchable", + "statusIndexing": "Making searchable…", + "statusNotYet": "Not searchable yet", + "readDone": "The AI read your document." + }, "contentIndex": { "searchHint": "Search now looks inside your documents too (whole words).", "indexPrompt": "Make document contents searchable.", - "searchable": "Contents searchable", - "notSearchable": "Contents not searchable yet", - "indexAction": "Index for search", - "reindexAction": "Re-index", - "indexing": "Indexing…", - "indexed": "Indexed for search.", "indexAll": "Index all for search", "indexAllPending": "Starting…", "indexAllQueued": "Indexing {count} document(s) in the background.", diff --git a/messages/es.json b/messages/es.json index 80d3c7b3c..8b2cfebad 100644 --- a/messages/es.json +++ b/messages/es.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Solo descarga", "uploading": "Subiendo…", "dismissFailed": "Descartar subida fallida", - "searchableBadge": "Contenido buscable" + "searchableBadge": "Contenido buscable", + "aiReadBadge": "Leído por IA" }, "error": { "fileTooLarge": "Este archivo supera el límite de {maxSize}.", @@ -8206,15 +8207,21 @@ "notSaved": "No se guarda · no es un diagnóstico. Se muestra solo una vez en esta vista.", "close": "Ocultar" }, + "ai": { + "blockTitle": "Leer con IA", + "available": "Deja que la IA lea este documento: sugerir un título, tipo y fecha, resumirlo o hacer que todo su texto sea buscable.", + "read": "Leer con IA", + "reread": "Leer de nuevo", + "reading": "Leyendo…", + "statusAiRead": "Leído por IA", + "statusSearchable": "Buscable", + "statusIndexing": "Haciéndolo buscable…", + "statusNotYet": "Aún no buscable", + "readDone": "La IA leyó tu documento." + }, "contentIndex": { "searchHint": "La búsqueda ahora también mira dentro de tus documentos (palabras completas).", "indexPrompt": "Haz que el contenido de los documentos sea buscable.", - "searchable": "Contenido buscable", - "notSearchable": "Contenido aún no buscable", - "indexAction": "Indexar para búsqueda", - "reindexAction": "Reindexar", - "indexing": "Indexando…", - "indexed": "Indexado para búsqueda.", "indexAll": "Indexar todo para búsqueda", "indexAllPending": "Iniciando…", "indexAllQueued": "Indexando {count} documento(s) en segundo plano.", diff --git a/messages/fr.json b/messages/fr.json index 5bd017305..fc9ffd85b 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Téléchargement uniquement", "uploading": "Téléversement…", "dismissFailed": "Ignorer le téléversement échoué", - "searchableBadge": "Contenu consultable" + "searchableBadge": "Contenu consultable", + "aiReadBadge": "Lu par l’IA" }, "error": { "fileTooLarge": "Ce fichier dépasse la limite de {maxSize}.", @@ -8206,15 +8207,21 @@ "notSaved": "Non enregistré · pas un diagnostic. Affiché une seule fois pour cette vue.", "close": "Masquer" }, + "ai": { + "blockTitle": "Lire avec l’IA", + "available": "Laissez l’IA lire ce document — suggérer un titre, un type et une date, le résumer ou rendre tout son texte consultable.", + "read": "Lire avec l’IA", + "reread": "Relire", + "reading": "Lecture…", + "statusAiRead": "Lu par l’IA", + "statusSearchable": "Consultable", + "statusIndexing": "Rendu consultable…", + "statusNotYet": "Pas encore consultable", + "readDone": "L’IA a lu votre document." + }, "contentIndex": { "searchHint": "La recherche explore désormais aussi le contenu de vos documents (mots entiers).", "indexPrompt": "Rendre le contenu des documents consultable.", - "searchable": "Contenu consultable", - "notSearchable": "Contenu pas encore consultable", - "indexAction": "Indexer pour la recherche", - "reindexAction": "Réindexer", - "indexing": "Indexation…", - "indexed": "Indexé pour la recherche.", "indexAll": "Tout indexer pour la recherche", "indexAllPending": "Démarrage…", "indexAllQueued": "Indexation de {count} document(s) en arrière-plan.", diff --git a/messages/it.json b/messages/it.json index 933289bb6..e8ba8b1c8 100644 --- a/messages/it.json +++ b/messages/it.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Solo download", "uploading": "Caricamento…", "dismissFailed": "Ignora caricamento non riuscito", - "searchableBadge": "Contenuto ricercabile" + "searchableBadge": "Contenuto ricercabile", + "aiReadBadge": "Letto dall’IA" }, "error": { "fileTooLarge": "Questo file supera il limite di {maxSize}.", @@ -8206,15 +8207,21 @@ "notSaved": "Non salvato · non è una diagnosi. Mostrato una sola volta per questa vista.", "close": "Nascondi" }, + "ai": { + "blockTitle": "Leggi con l’IA", + "available": "Lascia che l’IA legga questo documento: suggerire un titolo, un tipo e una data, riassumerlo o rendere ricercabile tutto il testo.", + "read": "Leggi con l’IA", + "reread": "Leggi di nuovo", + "reading": "Lettura…", + "statusAiRead": "Letto dall’IA", + "statusSearchable": "Ricercabile", + "statusIndexing": "Rendo ricercabile…", + "statusNotYet": "Non ancora ricercabile", + "readDone": "L’IA ha letto il tuo documento." + }, "contentIndex": { "searchHint": "La ricerca ora esamina anche il contenuto dei tuoi documenti (parole intere).", "indexPrompt": "Rendi ricercabile il contenuto dei documenti.", - "searchable": "Contenuto ricercabile", - "notSearchable": "Contenuto non ancora ricercabile", - "indexAction": "Indicizza per la ricerca", - "reindexAction": "Reindicizza", - "indexing": "Indicizzazione…", - "indexed": "Indicizzato per la ricerca.", "indexAll": "Indicizza tutto per la ricerca", "indexAllPending": "Avvio…", "indexAllQueued": "Indicizzazione di {count} documento/i in background.", diff --git a/messages/pl.json b/messages/pl.json index fc43f1599..c465bd9c2 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -8085,7 +8085,8 @@ "attachmentBadge": "Tylko do pobrania", "uploading": "Przesyłanie…", "dismissFailed": "Odrzuć nieudane przesyłanie", - "searchableBadge": "Treść przeszukiwalna" + "searchableBadge": "Treść przeszukiwalna", + "aiReadBadge": "Odczytane przez AI" }, "error": { "fileTooLarge": "Ten plik przekracza limit {maxSize}.", @@ -8206,15 +8207,21 @@ "notSaved": "Nie zapisano · to nie diagnoza. Wyświetlane tylko raz w tym widoku.", "close": "Ukryj" }, + "ai": { + "blockTitle": "Odczytaj z AI", + "available": "Pozwól AI odczytać ten dokument — zaproponować tytuł, typ i datę, streścić go lub udostępnić cały tekst do wyszukiwania.", + "read": "Odczytaj z AI", + "reread": "Odczytaj ponownie", + "reading": "Odczytywanie…", + "statusAiRead": "Odczytane przez AI", + "statusSearchable": "Wyszukiwalne", + "statusIndexing": "Udostępnianie do wyszukiwania…", + "statusNotYet": "Jeszcze nie do wyszukania", + "readDone": "AI odczytało Twój dokument." + }, "contentIndex": { "searchHint": "Wyszukiwanie przeszukuje teraz także treść Twoich dokumentów (całe słowa).", "indexPrompt": "Udostępnij treść dokumentów do wyszukiwania.", - "searchable": "Treść przeszukiwalna", - "notSearchable": "Treść jeszcze nieprzeszukiwalna", - "indexAction": "Zindeksuj do wyszukiwania", - "reindexAction": "Zindeksuj ponownie", - "indexing": "Indeksowanie…", - "indexed": "Zindeksowano do wyszukiwania.", "indexAll": "Zindeksuj wszystko do wyszukiwania", "indexAllPending": "Uruchamianie…", "indexAllQueued": "Indeksowanie {count} dokumentu/ów w tle.", diff --git a/src/components/documents/__tests__/document-ai-panels.test.tsx b/src/components/documents/__tests__/document-ai-panels.test.tsx index 33dbf1ea0..3ee93d715 100644 --- a/src/components/documents/__tests__/document-ai-panels.test.tsx +++ b/src/components/documents/__tests__/document-ai-panels.test.tsx @@ -6,7 +6,7 @@ import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents" import { AiUnavailableHint, AssistSuggestionReview, - ContentIndexStatus, + ContentSearchStatus, DocumentSummaryPanel, } from "../document-ai-panels"; @@ -175,25 +175,47 @@ describe("", () => { }); }); -describe("", () => { - it("offers indexing when the document is not yet searchable", () => { +describe("", () => { + it("shows a calm not-searchable-yet pill before the auto-index lands", () => { const html = render( - , ); - expect(html).toContain('data-slot="content-index-status"'); - expect(html).toContain("Contents not searchable yet"); - expect(html).toContain("Index for search"); + expect(html).toContain('data-slot="content-search-status"'); + expect(html).toContain('data-state="none"'); + expect(html).toContain("Not searchable yet"); + // A status, never a chore button. + expect(html).not.toContain(" { - const html = render( - , + it("highlights an AI-read document distinctly from a locally-indexed one", () => { + const aiRead = render( + , ); - expect(html).toContain("Contents searchable"); - expect(html).toContain("Re-index"); + expect(aiRead).toContain('data-state="ai-read"'); + expect(aiRead).toContain("Read by AI"); + expect(aiRead).toContain("text-primary"); + + const local = render( + , + ); + expect(local).toContain('data-state="searchable"'); + expect(local).toContain("Searchable"); + expect(local).not.toContain("Read by AI"); + }); + + it("reflects a running read as an indexing state", () => { + const html = render( + , + ); + expect(html).toContain('data-state="indexing"'); + expect(html).toContain("Making searchable…"); }); }); diff --git a/src/components/documents/__tests__/document-ai-section.test.tsx b/src/components/documents/__tests__/document-ai-section.test.tsx index 9a304f2f3..b608f4e10 100644 --- a/src/components/documents/__tests__/document-ai-section.test.tsx +++ b/src/components/documents/__tests__/document-ai-section.test.tsx @@ -6,10 +6,11 @@ import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents" import { DocumentAiSection } from "../document-ai-section"; /** - * The detail sheet's AI area is availability-gated: with a provider it shows the - * assist / summary toolbar; without one it shows ONLY the calm settings pointer - * (never an action the endpoint would 422). The summary panel, when open, keeps - * the session-only note visible. + * The detail sheet's "Read with AI" block is availability-gated: with a provider + * it offers the prominent "Read with AI" action alongside suggest / summary; + * without one the actions collapse to the calm settings pointer (never an action + * the endpoint would 422). The searchable status pill reflects the auto-index in + * both states. The summary panel, when open, keeps the session-only note visible. */ function render(node: React.ReactNode) { @@ -52,6 +53,7 @@ function base( summaryErrorKey: null, onCloseSummary: noop, hasContentIndex: false, + contentIndexSource: null, indexPending: false, onIndex: noop, ...overrides, @@ -60,25 +62,59 @@ function base( } describe("", () => { - it("offers the assist + summary toolbar when a provider is available", () => { + it("leads with the prominent Read-with-AI action when a provider is available", () => { const html = render(base({ aiEnabled: true })); + expect(html).toContain('data-slot="document-read-ai"'); + expect(html).toContain("Read with AI"); expect(html).toContain('data-slot="assist-suggest"'); expect(html).toContain("Suggest details"); expect(html).toContain("Summarise"); expect(html).toContain("Show extracted text"); - // No unavailable pointer when the affordance is offered. + // No unavailable pointer when the actions are offered. expect(html).not.toContain('data-slot="assist-unavailable"'); }); - it("shows ONLY the calm settings pointer when no provider is available", () => { + it("labels the action as a re-read once a provider has already read it", () => { const html = render( - base({ aiEnabled: false, unavailableReason: "no-provider" }), + base({ hasContentIndex: true, contentIndexSource: "vision" }), + ); + expect(html).toContain('data-slot="document-read-ai"'); + expect(html).toContain("Read again"); + // The status pill reflects the AI-read provenance. + expect(html).toContain('data-state="ai-read"'); + expect(html).toContain("Read by AI"); + }); + + it("shows the calm settings pointer (no 422 actions) when no provider is available", () => { + const html = render( + base({ + aiEnabled: false, + indexEnabled: false, + unavailableReason: "no-provider", + }), ); expect(html).toContain('data-slot="assist-unavailable"'); expect(html).toContain('href="/settings/ai"'); - // The assist / index actions must be absent — never a 422-guaranteed tap. + // Every AI action must be absent — never a 422-guaranteed tap. expect(html).not.toContain('data-slot="assist-suggest"'); - expect(html).not.toContain('data-slot="content-index-status"'); + expect(html).not.toContain('data-slot="document-read-ai"'); + // But the searchable status pill still renders — auto-index is honest here. + expect(html).toContain('data-slot="content-search-status"'); + }); + + it("keeps a locally-indexed document honestly searchable without a provider", () => { + const html = render( + base({ + aiEnabled: false, + indexEnabled: false, + unavailableReason: "no-provider", + hasContentIndex: true, + contentIndexSource: "local-pdf", + }), + ); + expect(html).toContain('data-state="searchable"'); + expect(html).toContain("Searchable"); + expect(html).not.toContain("Read by AI"); }); it("renders the reviewed suggestion drafts when present", () => { @@ -106,9 +142,9 @@ describe("", () => { expect(html).toContain("Not saved"); }); - it("hides the content-index row when indexing is unavailable", () => { + it("hides the Read-with-AI action when indexing is unavailable but keeps suggest", () => { const html = render(base({ aiEnabled: true, indexEnabled: false })); expect(html).toContain('data-slot="assist-suggest"'); - expect(html).not.toContain('data-slot="content-index-status"'); + expect(html).not.toContain('data-slot="document-read-ai"'); }); }); diff --git a/src/components/documents/__tests__/document-card.test.tsx b/src/components/documents/__tests__/document-card.test.tsx index 61680c65e..a76c8b153 100644 --- a/src/components/documents/__tests__/document-card.test.tsx +++ b/src/components/documents/__tests__/document-card.test.tsx @@ -96,9 +96,25 @@ describe("", () => { />, ); expect(html).toContain('data-slot="document-searchable"'); + expect(html).toContain('data-source="local"'); expect(html).toContain("Contents searchable"); }); + it("marks an AI-read document with the highlighted read-by-AI badge", () => { + const html = render( + , + ); + expect(html).toContain('data-source="ai-read"'); + expect(html).toContain("Read by AI"); + expect(html).toContain("text-primary"); + }); + it("shows no searchable marker when the document is not indexed", () => { const html = render( void; }) { const { t } = useTranslations(); - return ( -
-

- - {hasContentIndex - ? t("documents.contentIndex.searchable") - : t("documents.contentIndex.notSearchable")} -

- -
+ + {t("documents.ai.statusIndexing")} + + ); + } + + if (!hasContentIndex) { + return ( + + + {t("documents.ai.statusNotYet")} + + ); + } + + if (isAiReadSource(source)) { + return ( + + + {t("documents.ai.statusAiRead")} + + ); + } + + return ( + + + {t("documents.ai.statusSearchable")} + ); } diff --git a/src/components/documents/document-ai-section.tsx b/src/components/documents/document-ai-section.tsx index 10070d160..56e842062 100644 --- a/src/components/documents/document-ai-section.tsx +++ b/src/components/documents/document-ai-section.tsx @@ -1,29 +1,43 @@ "use client"; /** - * v1.27.22 (Document vault P2) — the AI area of the document detail sheet, - * lifted out of the sheet so the availability gate (toolbar vs. calm pointer) - * and the review-first wiring render without the sheet's dialog portal and are - * pinned by static-render tests. + * The "Read with AI" block of the document detail sheet — one coherent, inviting + * home for every AI reading action, lifted out of the sheet so the availability + * gate and the review-first wiring render without the sheet's dialog portal and + * are pinned by static-render tests. + * + * Indexing is AUTOMATIC on upload, so this block never nags a "please index" + * chore. It shows the document's searchable status (auto-indexed) as a pill, and + * offers AI reading as a capability the user reaches for when they want more: + * + * - "Read with AI" (prominent) runs a provider read of the document — the + * richer pass that also refreshes the searchable index. Labelled "Read + * again" once a provider has already read it. + * - "Suggest details" drafts a title / type / date to review before saving. + * - "Summarise" / "Show text" surface a transient, session-only read-out. * * Presentational: the sheet owns the mutations + capability probe and passes - * their state + handlers down. When `aiEnabled` is false the whole area is the - * calm "set up an AI provider" pointer — never an error, and the manual form - * below stays fully usable. + * their state + handlers down. When `aiEnabled` is false the actions collapse to + * the calm "set up an AI provider" pointer (never an error) — the searchable + * status pill stays, honestly reflecting any local auto-index, and the manual + * form below stays fully usable. */ -import { FileText, Sparkles } from "lucide-react"; +import { FileText, ScanText, WandSparkles } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTranslations } from "@/lib/i18n/context"; -import type { - DocumentSuggestionDto, - DocumentSummaryMode, +import { cn } from "@/lib/utils"; +import { + isAiReadSource, + type DocumentContentIndexSourceValue, + type DocumentSuggestionDto, + type DocumentSummaryMode, } from "@/lib/validations/inbound-documents"; import { AiUnavailableHint, AssistSuggestionReview, - ContentIndexStatus, + ContentSearchStatus, DocumentSummaryPanel, } from "./document-ai-panels"; import type { DocumentDescribeResult } from "./use-document-assist"; @@ -51,6 +65,7 @@ export function DocumentAiSection({ summaryErrorKey, onCloseSummary, hasContentIndex, + contentIndexSource, indexPending, onIndex, }: { @@ -77,92 +92,128 @@ export function DocumentAiSection({ summaryErrorKey: string | null; onCloseSummary: () => void; hasContentIndex: boolean; + contentIndexSource: DocumentContentIndexSourceValue | null; indexPending: boolean; onIndex: () => void; }) { const { t } = useTranslations(); - - if (!aiEnabled) { - return ( -
- -
- ); - } + const aiRead = hasContentIndex && isAiReadSource(contentIndexSource); + const readLabel = indexPending + ? t("documents.ai.reading") + : aiRead + ? t("documents.ai.reread") + : t("documents.ai.read"); return ( -
-
- - - +
+
+
+ +

+ {t("documents.ai.blockTitle")} +

+
+
- {suggestErrorKey ? ( -

- {t(suggestErrorKey)} -

- ) : null} + {aiEnabled ? ( + <> +

+ {t("documents.ai.available")} +

- {suggestion ? ( - - ) : null} +
+ {indexEnabled ? ( + + ) : null} + + + +
- {summaryOutput ? ( - - ) : null} + {suggestErrorKey ? ( +

+ {t(suggestErrorKey)} +

+ ) : null} - {indexEnabled ? ( - - ) : null} + {suggestion ? ( + + ) : null} + + {summaryOutput ? ( + + ) : null} + + ) : ( + + )}
); } diff --git a/src/components/documents/document-card.tsx b/src/components/documents/document-card.tsx index 6d099b1ca..a74ea0195 100644 --- a/src/components/documents/document-card.tsx +++ b/src/components/documents/document-card.tsx @@ -13,7 +13,7 @@ * hover / focus / while selected. The whole card is clickable through an * invisible overlay button; the checkbox floats above it. */ -import { Download, ScanSearch, X } from "lucide-react"; +import { Download, ScanSearch, Sparkles, X } from "lucide-react"; import { useRef } from "react"; import { Badge } from "@/components/ui/badge"; @@ -22,7 +22,10 @@ import { Card, CardContent } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; -import type { InboundDocumentDto } from "@/lib/validations/inbound-documents"; +import { + isAiReadSource, + type InboundDocumentDto, +} from "@/lib/validations/inbound-documents"; import { DOCUMENT_KIND_ICONS } from "./document-kind-meta"; import type { UploadQueueItem } from "./use-document-upload"; import { documentDateKey, formatBytes } from "./vault-utils"; @@ -140,18 +143,35 @@ export function DocumentCard({ ) : null} {document.hasContentIndex ? ( - // Subtle "contents searchable" marker — icon-only to keep the - // row calm; the label rides an accessible name. - - - - {t("documents.card.searchableBadge")} + // Subtle searchable marker — icon-only to keep the row calm; the + // label rides an accessible name. An AI-read document gets the + // highlighted Sparkles (primary) so the richer read is legible at + // a glance; a locally-indexed one keeps the quiet muted glass. + isAiReadSource(document.contentIndexSource) ? ( + + + + {t("documents.card.aiReadBadge")} + - + ) : ( + + + + {t("documents.card.searchableBadge")} + + + ) ) : null}
) : null} diff --git a/src/components/documents/document-detail-sheet.tsx b/src/components/documents/document-detail-sheet.tsx index 463b78044..75bc50624 100644 --- a/src/components/documents/document-detail-sheet.tsx +++ b/src/components/documents/document-detail-sheet.tsx @@ -365,7 +365,7 @@ export function DocumentDetailSheet({ indexDoc.mutate( { mode: aiMode, target: aiTarget }, { - onSuccess: () => toast.success(t("documents.contentIndex.indexed")), + onSuccess: () => toast.success(t("documents.ai.readDone")), onError: (error) => toast.error(t(documentAiErrorKey(error))), }, ); @@ -531,6 +531,7 @@ export function DocumentDetailSheet({ summary.reset(); }} hasContentIndex={doc.hasContentIndex} + contentIndexSource={doc.contentIndexSource} indexPending={indexDoc.isPending} onIndex={runIndex} />