diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 9687d2b4d..f288bfa59 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -10,6 +10,7 @@ import { useAtomValue, useSetAtom } from "jotai"; import { useParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; +import { z } from "zod"; import { type MentionedDocumentInfo, mentionedDocumentIdsAtom, @@ -55,20 +56,33 @@ function extractThinkingSteps(content: unknown): ThinkingStep[] { } /** - * Extract mentioned documents from message content + * Zod schema for mentioned document info (for type-safe parsing) + */ +const MentionedDocumentInfoSchema = z.object({ + id: z.number(), + title: z.string(), + document_type: z.string(), +}); + +const MentionedDocumentsPartSchema = z.object({ + type: z.literal("mentioned-documents"), + documents: z.array(MentionedDocumentInfoSchema), +}); + +/** + * Extract mentioned documents from message content (type-safe with Zod) */ function extractMentionedDocuments(content: unknown): MentionedDocumentInfo[] { if (!Array.isArray(content)) return []; - const docsPart = content.find( - (part: unknown) => - typeof part === "object" && - part !== null && - "type" in part && - (part as { type: string }).type === "mentioned-documents" - ) as { type: "mentioned-documents"; documents: MentionedDocumentInfo[] } | undefined; + for (const part of content) { + const result = MentionedDocumentsPartSchema.safeParse(part); + if (result.success) { + return result.data.documents; + } + } - return docsPart?.documents || []; + return []; } /** diff --git a/surfsense_web/components/tool-ui/display-image.tsx b/surfsense_web/components/tool-ui/display-image.tsx index cd1c14241..333cd496c 100644 --- a/surfsense_web/components/tool-ui/display-image.tsx +++ b/surfsense_web/components/tool-ui/display-image.tsx @@ -23,7 +23,7 @@ interface DisplayImageResult { id: string; assetId: string; src: string; - alt?: string; // Made optional - parseSerializableImage provides fallback + alt?: string; // Made optional - parseSerializableImage provides fallback title?: string; description?: string; domain?: string; diff --git a/surfsense_web/components/tool-ui/image/index.tsx b/surfsense_web/components/tool-ui/image/index.tsx index 1d28490a3..f872e293f 100644 --- a/surfsense_web/components/tool-ui/image/index.tsx +++ b/surfsense_web/components/tool-ui/image/index.tsx @@ -24,7 +24,7 @@ const SerializableImageSchema = z.object({ id: z.string(), assetId: z.string(), src: z.string(), - alt: z.string().nullish(), // Made optional - will use fallback if missing + alt: z.string().nullish(), // Made optional - will use fallback if missing title: z.string().nullish(), description: z.string().nullish(), href: z.string().nullish(), @@ -48,7 +48,7 @@ export interface ImageProps { id: string; assetId: string; src: string; - alt?: string; // Optional with default fallback + alt?: string; // Optional with default fallback title?: string; description?: string; href?: string; @@ -69,10 +69,10 @@ export function parseSerializableImage(result: unknown): SerializableImage & { a if (!parsed.success) { console.warn("Invalid image data:", parsed.error.issues); - + // Try to extract basic info and return a fallback object const obj = (result && typeof result === "object" ? result : {}) as Record; - + // If we have at least id, assetId, and src, we can still render the image if ( typeof obj.id === "string" && @@ -92,7 +92,7 @@ export function parseSerializableImage(result: unknown): SerializableImage & { a source: undefined, }; } - + throw new Error(`Invalid image: ${parsed.error.issues.map((i) => i.message).join(", ")}`); }