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..84e7ead0
--- /dev/null
+++ b/app/api/document/[document_id]/preview/route.ts
@@ -0,0 +1,50 @@
+import { NextResponse } from "next/server";
+import { apiClient } from "@/app/lib/apiClient";
+import { DocumentDetailEnvelope } from "@/app/lib/types/document";
+
+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/analytics/BreakdownPanel.tsx b/app/components/analytics/BreakdownPanel.tsx
index 1a8d0f3a..8f694d88 100644
--- a/app/components/analytics/BreakdownPanel.tsx
+++ b/app/components/analytics/BreakdownPanel.tsx
@@ -138,7 +138,7 @@ export default function BreakdownPanel({
{groupHeader}-wise {metricLabel}
-
+
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 && (