From fdd36686e8a6b3f27a36ae395fa5f141cd83cd0e Mon Sep 17 00:00:00 2001 From: Ayush8923 <80516839+Ayush8923@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:34:38 +0530 Subject: [PATCH 1/8] feat(document): collection document preview handling for preview --- app/(main)/text-to-speech/page.tsx | 3 +- .../document/[document_id]/preview/route.ts | 72 +++++ app/components/evaluations/DatasetsTab.tsx | 24 +- .../knowledge-base/CollectionDetail.tsx | 2 +- app/components/knowledge-base/CsvPreview.tsx | 106 ++++++ .../knowledge-base/DocumentPreviewModal.tsx | 303 ++++++++++++++---- app/components/text-to-speech/DatasetsTab.tsx | 24 +- .../text-to-speech/TTSDatasetCard.tsx | 2 +- app/components/text-to-speech/index.ts | 13 + app/lib/utils/csv.ts | 36 +++ app/lib/utils/documentPreview.ts | 99 ++++++ 11 files changed, 563 insertions(+), 121 deletions(-) create mode 100644 app/api/document/[document_id]/preview/route.ts create mode 100644 app/components/knowledge-base/CsvPreview.tsx create mode 100644 app/components/text-to-speech/index.ts create mode 100644 app/lib/utils/csv.ts create mode 100644 app/lib/utils/documentPreview.ts diff --git a/app/(main)/text-to-speech/page.tsx b/app/(main)/text-to-speech/page.tsx index e68f9766..ed774b9a 100644 --- a/app/(main)/text-to-speech/page.tsx +++ b/app/(main)/text-to-speech/page.tsx @@ -15,8 +15,7 @@ import { useToast } from "@/app/hooks/useToast"; import { useAuth } from "@/app/lib/context/AuthContext"; import { useApp } from "@/app/lib/context/AppContext"; import { apiFetch } from "@/app/lib/apiClient"; -import DatasetsTab from "@/app/components/text-to-speech/DatasetsTab"; -import EvaluationsTab from "@/app/components/text-to-speech/EvaluationsTab"; +import { DatasetsTab, EvaluationsTab } from "@/app/components/text-to-speech"; import { TTSTab, TextSample, diff --git a/app/api/document/[document_id]/preview/route.ts b/app/api/document/[document_id]/preview/route.ts new file mode 100644 index 00000000..445d0271 --- /dev/null +++ b/app/api/document/[document_id]/preview/route.ts @@ -0,0 +1,72 @@ +import { NextResponse } from "next/server"; +import { apiClient } from "@/app/lib/apiClient"; + +/** + * Same-origin proxy for document content. + * + * Object-storage signed URLs are typically public for GET, but they (a) live + * on a different origin so the browser blocks fetch() via CORS, and (b) often + * set Content-Disposition: attachment so the browser downloads instead of + * rendering. Both problems disappear when the bytes come from our own origin. + * + * The route fetches the document's signed URL server-side and streams the + * bytes back with the upstream Content-Type intact (no attachment header). + */ + +interface DocumentDetail { + signed_url?: string; + fname?: string; +} + +interface DocumentDetailEnvelope { + data?: DocumentDetail; + signed_url?: string; + fname?: string; +} + +export async function GET( + request: Request, + { params }: { params: Promise<{ document_id: string }> }, +) { + const { document_id } = await params; + try { + const { data } = await apiClient( + request, + `/api/v1/documents/${document_id}?include_url=true`, + ); + const detail = (data as DocumentDetailEnvelope) || {}; + const signedUrl = detail.data?.signed_url || detail.signed_url; + if (!signedUrl) { + return NextResponse.json( + { error: "Document has no signed URL" }, + { status: 404 }, + ); + } + + const upstream = await fetch(signedUrl); + if (!upstream.ok) { + return NextResponse.json( + { error: `Failed to fetch document (status ${upstream.status})` }, + { status: upstream.status }, + ); + } + + const contentType = + upstream.headers.get("Content-Type") || "application/octet-stream"; + return new Response(upstream.body, { + status: 200, + headers: { + "Content-Type": contentType, + "Cache-Control": "private, max-age=300", + }, + }); + } catch (error: unknown) { + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : String(error), + }, + { status: 500 }, + ); + } +} diff --git a/app/components/evaluations/DatasetsTab.tsx b/app/components/evaluations/DatasetsTab.tsx index cb2ec9bc..676e0300 100644 --- a/app/components/evaluations/DatasetsTab.tsx +++ b/app/components/evaluations/DatasetsTab.tsx @@ -8,6 +8,7 @@ import { DatabaseIcon, PlusIcon } from "@/app/components/icons"; import { Button, Modal } from "@/app/components/ui"; import { useToast } from "@/app/hooks/useToast"; import { DatasetListSkeleton } from "@/app/components"; +import { parseCsvRow } from "@/app/lib/utils/csv"; import DatasetCard from "./DatasetCard"; import CreateDatasetForm from "./CreateDatasetForm"; import ViewDatasetModal from "./ViewDatasetModal"; @@ -34,29 +35,6 @@ export interface DatasetsTabProps { toast: ReturnType; } -const parseCsvRow = (line: string): string[] => { - const result: string[] = []; - let current = ""; - let inQuotes = false; - for (let i = 0; i < line.length; i++) { - if (line[i] === '"') { - if (inQuotes && line[i + 1] === '"') { - current += '"'; - i++; - } else { - inQuotes = !inQuotes; - } - } else if (line[i] === "," && !inQuotes) { - result.push(current.trim()); - current = ""; - } else { - current += line[i]; - } - } - result.push(current.trim()); - return result; -}; - export default function DatasetsTab({ leftPanelWidth, datasetName, diff --git a/app/components/knowledge-base/CollectionDetail.tsx b/app/components/knowledge-base/CollectionDetail.tsx index 4ad6190c..4c42fb6c 100644 --- a/app/components/knowledge-base/CollectionDetail.tsx +++ b/app/components/knowledge-base/CollectionDetail.tsx @@ -125,7 +125,7 @@ export default function CollectionDetail({ {documents.length > 0 && ( - ); - })} + +
+ + setIsFrameLoading(false)} + />
+ + + ); +} + +interface DocumentSidebarProps { + documents: Document[]; + previewDoc: Document | null; + onSelectDocument: (doc: Document) => void; +} -
- {previewDoc?.signed_url ? ( -