mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
fix(documents): keep the AI read action + provenance pill, trim only the block title
The detail sheet's AI block over-removed: the read-with-AI action and the read-provenance pill are both contract surfaces (the action is the explicit read path when auto-read is off / for an external provider; the pill is the read-state indicator). Only the redundant block title + wand were the wasted row. Restore the action and pill; drop just the title. The action row still collapses when auto-read is on, as before.
This commit is contained in:
1 parent
c2c4d56b44
commit
993e693ad6
8 files changed
+124
-55
No files matched your search
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Durchsuchbar",
|
||||
"statusIndexing": "Wird durchsuchbar gemacht…",
|
||||
"statusNotYet": "Noch nicht durchsuchbar",
|
||||
"readDone": "Die KI hat dein Dokument gelesen."
|
||||
"readDone": "Die KI hat dein Dokument gelesen.",
|
||||
"read": "Mit KI auslesen",
|
||||
"reread": "Erneut lesen",
|
||||
"reading": "Liest…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Dokumentinhalte durchsuchbar machen.",
|
||||
|
||||
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Searchable",
|
||||
"statusIndexing": "Making searchable…",
|
||||
"statusNotYet": "Not searchable yet",
|
||||
"readDone": "The AI read your document."
|
||||
"readDone": "The AI read your document.",
|
||||
"read": "Read with AI",
|
||||
"reread": "Read again",
|
||||
"reading": "Reading…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Make document contents searchable.",
|
||||
|
||||
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Buscable",
|
||||
"statusIndexing": "Haciéndolo buscable…",
|
||||
"statusNotYet": "Aún no buscable",
|
||||
"readDone": "La IA leyó tu documento."
|
||||
"readDone": "La IA leyó tu documento.",
|
||||
"read": "Leer con IA",
|
||||
"reread": "Leer de nuevo",
|
||||
"reading": "Leyendo…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Haz que el contenido de los documentos sea buscable.",
|
||||
|
||||
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Consultable",
|
||||
"statusIndexing": "Rendu consultable…",
|
||||
"statusNotYet": "Pas encore consultable",
|
||||
"readDone": "L’IA a lu votre document."
|
||||
"readDone": "L’IA a lu votre document.",
|
||||
"read": "Lire avec l’IA",
|
||||
"reread": "Relire",
|
||||
"reading": "Lecture…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Rendre le contenu des documents consultable.",
|
||||
|
||||
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Ricercabile",
|
||||
"statusIndexing": "Rendo ricercabile…",
|
||||
"statusNotYet": "Non ancora ricercabile",
|
||||
"readDone": "L’IA ha letto il tuo documento."
|
||||
"readDone": "L’IA ha letto il tuo documento.",
|
||||
"read": "Leggi con l’IA",
|
||||
"reread": "Leggi di nuovo",
|
||||
"reading": "Lettura…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Rendi ricercabile il contenuto dei documenti.",
|
||||
|
||||
+4
-1
@@ -8321,7 +8321,10 @@
|
||||
"statusSearchable": "Wyszukiwalne",
|
||||
"statusIndexing": "Udostępnianie do wyszukiwania…",
|
||||
"statusNotYet": "Jeszcze nie do wyszukania",
|
||||
"readDone": "AI odczytało Twój dokument."
|
||||
"readDone": "AI odczytało Twój dokument.",
|
||||
"read": "Odczytaj z AI",
|
||||
"reread": "Odczytaj ponownie",
|
||||
"reading": "Odczytywanie…"
|
||||
},
|
||||
"contentIndex": {
|
||||
"indexPrompt": "Udostępnij treść dokumentów do wyszukiwania.",
|
||||
|
||||
@@ -6,13 +6,11 @@ import type { DocumentSuggestionDto } from "@/lib/validations/inbound-documents"
|
||||
import { DocumentAiSection } from "../document-ai-section";
|
||||
|
||||
/**
|
||||
* The detail sheet's AI-assist block is availability-gated: with a provider it
|
||||
* offers the review-first Suggest / Summarise / Show-text actions; without one
|
||||
* the actions collapse to the calm settings pointer (never an action the
|
||||
* endpoint would 422). The redundant "read with AI" button + the searchable
|
||||
* status pill were removed — reading + indexing happen automatically on upload,
|
||||
* so with auto-read ON and no active suggestion/summary the whole block
|
||||
* collapses. 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) {
|
||||
@@ -64,20 +62,29 @@ function base(
|
||||
}
|
||||
|
||||
describe("<DocumentAiSection>", () => {
|
||||
it("offers the review-first assist actions 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 }));
|
||||
// The standalone "read with AI" button is gone — reading is automatic.
|
||||
expect(html).not.toContain('data-slot="document-read-ai"');
|
||||
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");
|
||||
// The searchable status pill was removed from this block.
|
||||
expect(html).not.toContain('data-slot="content-search-status"');
|
||||
// No unavailable pointer when the actions are offered.
|
||||
expect(html).not.toContain('data-slot="assist-unavailable"');
|
||||
});
|
||||
|
||||
it("labels the action as a re-read once a provider has already read it", () => {
|
||||
const html = render(
|
||||
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({
|
||||
@@ -91,6 +98,23 @@ describe("<DocumentAiSection>", () => {
|
||||
// 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="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", () => {
|
||||
@@ -123,36 +147,32 @@ describe("<DocumentAiSection>", () => {
|
||||
expect(html).not.toContain('data-slot="document-ai-egress-notice"');
|
||||
// The actions are still offered.
|
||||
expect(html).toContain('data-slot="assist-suggest"');
|
||||
expect(html).toContain('data-slot="document-read-ai"');
|
||||
});
|
||||
|
||||
it("collapses the whole block when auto-read is on with nothing to review", () => {
|
||||
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="document-read-ai"');
|
||||
});
|
||||
|
||||
it("hides the manual AI action row when auto-read is on, keeping status", () => {
|
||||
const html = render(base({ aiEnabled: true, autoReadEnabled: true }));
|
||||
// Auto-read reads on upload — the manual per-document actions are gone,
|
||||
// and with no active suggestion/summary the block renders nothing at all
|
||||
// rather than an empty bordered box.
|
||||
expect(html).toBe("");
|
||||
});
|
||||
|
||||
it("keeps the review panels when auto-read is on and a suggestion is present", () => {
|
||||
const html = render(
|
||||
base({
|
||||
aiEnabled: true,
|
||||
autoReadEnabled: true,
|
||||
suggestion,
|
||||
suggestionKindLabel: "Imaging",
|
||||
suggestionDateLabel: "14 Feb 2026",
|
||||
}),
|
||||
);
|
||||
// The manual action row stays hidden, but the block itself renders to host
|
||||
// the review panel.
|
||||
expect(html).toContain('data-slot="document-ai-section"');
|
||||
// Auto-read reads on upload — the manual per-document actions are gone.
|
||||
expect(html).not.toContain('data-slot="document-read-ai"');
|
||||
expect(html).not.toContain('data-slot="assist-suggest"');
|
||||
expect(html).toContain('data-slot="assist-suggestion-review"');
|
||||
expect(html).not.toContain("Summarise");
|
||||
expect(html).not.toContain("Show extracted text");
|
||||
// The header and the searchable status pill stay. The scoped chat entry
|
||||
// now lives in the detail-sheet header (a neutral Coach icon), not in this
|
||||
// section, so this block no longer owns a chat slot.
|
||||
expect(html).toContain('data-slot="document-ai-section"');
|
||||
expect(html).toContain('data-slot="content-search-status"');
|
||||
});
|
||||
|
||||
it("shows the manual AI action row when auto-read is off", () => {
|
||||
const html = render(base({ aiEnabled: true, autoReadEnabled: false }));
|
||||
expect(html).not.toContain('data-slot="document-read-ai"');
|
||||
expect(html).toContain('data-slot="document-read-ai"');
|
||||
expect(html).toContain('data-slot="assist-suggest"');
|
||||
expect(html).toContain("Summarise");
|
||||
});
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
* status pill stays, honestly reflecting any local auto-index, and the manual
|
||||
* form below stays fully usable.
|
||||
*/
|
||||
import { FileText, WandSparkles } from "lucide-react";
|
||||
import { FileText, ScanText, WandSparkles } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
isAiReadSource,
|
||||
type DocumentContentIndexSourceValue,
|
||||
type DocumentSuggestionDto,
|
||||
type DocumentSummaryMode,
|
||||
@@ -35,6 +37,7 @@ import {
|
||||
import {
|
||||
AiUnavailableHint,
|
||||
AssistSuggestionReview,
|
||||
ContentSearchStatus,
|
||||
DocumentSummaryPanel,
|
||||
} from "./document-ai-panels";
|
||||
import type { DocumentDescribeResult } from "./use-document-assist";
|
||||
@@ -42,6 +45,7 @@ import type { DocumentDescribeResult } from "./use-document-assist";
|
||||
export function DocumentAiSection({
|
||||
aiEnabled,
|
||||
autoReadEnabled = false,
|
||||
indexEnabled,
|
||||
unavailableReason,
|
||||
actionsDisabled,
|
||||
onSuggest,
|
||||
@@ -61,6 +65,10 @@ export function DocumentAiSection({
|
||||
summaryResult,
|
||||
summaryErrorKey,
|
||||
onCloseSummary,
|
||||
hasContentIndex,
|
||||
contentIndexSource,
|
||||
indexPending,
|
||||
onIndex,
|
||||
}: {
|
||||
aiEnabled: boolean;
|
||||
/**
|
||||
@@ -98,30 +106,53 @@ export function DocumentAiSection({
|
||||
onIndex: () => void;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
|
||||
// The redundant "read with AI / read again" button + the searchable-status
|
||||
// pill header have been removed — reading + indexing happen automatically on
|
||||
// upload, so this block now only carries the review-first assist actions
|
||||
// (Suggest / Summarise / Show text) and their transient panels. With
|
||||
// auto-read ON and no active suggestion/summary there is nothing to show, so
|
||||
// the whole block collapses rather than render an empty bordered box.
|
||||
const hasContent =
|
||||
!aiEnabled ||
|
||||
!autoReadEnabled ||
|
||||
Boolean(suggestErrorKey) ||
|
||||
Boolean(suggestion) ||
|
||||
Boolean(summaryOutput);
|
||||
if (!hasContent) return null;
|
||||
const aiRead = hasContentIndex && isAiReadSource(contentIndexSource);
|
||||
const readLabel = indexPending
|
||||
? t("documents.ai.reading")
|
||||
: aiRead
|
||||
? t("documents.ai.reread")
|
||||
: t("documents.ai.read");
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="document-ai-section"
|
||||
className="border-border bg-muted/30 space-y-3 rounded-lg border p-3 md:p-4"
|
||||
>
|
||||
{/* v1.28.19 — the "read with AI" block title + wand were redundant with
|
||||
the read-provenance pill (auto-read already surfaces "read by AI"), so
|
||||
only the provenance pill leads the block now. The pill itself stays —
|
||||
it is the read-state contract the recipient/owner relies on. */}
|
||||
<div className="flex items-center justify-end">
|
||||
<ContentSearchStatus
|
||||
hasContentIndex={hasContentIndex}
|
||||
source={contentIndexSource}
|
||||
isPending={indexPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{aiEnabled ? (
|
||||
<>
|
||||
{!autoReadEnabled ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{indexEnabled ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
data-slot="document-read-ai"
|
||||
onClick={onIndex}
|
||||
disabled={indexPending || actionsDisabled}
|
||||
>
|
||||
<ScanText
|
||||
className={cn(
|
||||
"size-4",
|
||||
indexPending &&
|
||||
"animate-pulse motion-reduce:animate-none",
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
{readLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
|
||||
Reference in new issue
Block a user