Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions app/(main)/text-to-speech/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
50 changes: 50 additions & 0 deletions app/api/document/[document_id]/preview/route.ts
Original file line number Diff line number Diff line change
@@ -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 },
);
}
Comment thread
Ayush8923 marked this conversation as resolved.

const upstream = await fetch(signedUrl);
Comment thread
Ayush8923 marked this conversation as resolved.
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 },
);
}
}
2 changes: 1 addition & 1 deletion app/components/analytics/BreakdownPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export default function BreakdownPanel({
{groupHeader}-wise {metricLabel}
</h4>
</header>
<div className="flex-1 min-h-0 max-h-80 overflow-y-auto custom-scroll-accent">
<div className="flex-1 min-h-0 max-h-80 overflow-y-auto">
<table className="w-full text-sm">
<thead className="sticky top-0 bg-accent-primary/10 backdrop-blur-3xl">
<tr className="text-[11px] uppercase tracking-wide text-black">
Expand Down
24 changes: 1 addition & 23 deletions app/components/evaluations/DatasetsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -34,29 +35,6 @@ export interface DatasetsTabProps {
toast: ReturnType<typeof useToast>;
}

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,
Expand Down
2 changes: 1 addition & 1 deletion app/components/knowledge-base/CollectionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default function CollectionDetail({
</h3>
{documents.length > 0 && (
<Button
variant="outline"
variant="primary"
size="sm"
onClick={() => onPreviewDocument(documents[0])}
>
Expand Down
101 changes: 101 additions & 0 deletions app/components/knowledge-base/CsvPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client";

import { useEffect, useState } from "react";
import { Loader } from "@/app/components/ui";
import { useAuth } from "@/app/lib/context/AuthContext";
import { apiFetchResponse } from "@/app/lib/apiClient";
import { parseCsv } from "@/app/lib/utils/csv";
import { CsvPreviewProps, ParsedCsv } from "@/app/lib/types/document";

export default function CsvPreview({ url }: CsvPreviewProps) {
const { activeKey } = useAuth();
const apiKey = activeKey?.key ?? "";
const [data, setData] = useState<ParsedCsv | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
setData(null);
apiFetchResponse(url, apiKey)
.then((r) => {
if (!r.ok) throw new Error(`Server returned ${r.status}`);
return r.text();
})
.then((text) => {
if (!cancelled) setData(parseCsv(text));
})
.catch((e: Error) => {
if (!cancelled) setError(e.message || "Couldn't load CSV");
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [url, apiKey]);

if (loading) {
return (
<div className="flex items-center justify-center h-full">
<Loader size="md" message="Loading CSV…" />
</div>
);
}
if (error) {
return (
<div className="flex flex-col items-center justify-center h-full gap-2 px-6 text-center">
<p className="text-sm text-text-secondary">
Couldn&apos;t load CSV preview.
</p>
<p className="text-xs text-text-secondary">{error}</p>
</div>
);
}
if (!data || data.headers.length === 0) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-sm text-text-secondary">CSV is empty.</p>
</div>
);
}

return (
<div className="h-full overflow-auto bg-bg-primary">
<table className="w-full text-sm border-collapse">
<thead className="sticky top-0 z-10 bg-bg-secondary border-b border-border">
<tr>
{data.headers.map((h, i) => (
<th
key={i}
className="text-left px-3 py-2 font-semibold text-text-primary border-r border-border last:border-r-0 whitespace-nowrap"
>
{h || `Column ${i + 1}`}
</th>
))}
</tr>
</thead>
<tbody>
{data.rows.map((row, ri) => (
<tr
key={ri}
className="border-b border-border last:border-b-0 hover:bg-bg-secondary/40"
>
{data.headers.map((_, ci) => (
<td
key={ci}
className="px-3 py-2 text-text-secondary border-r border-border last:border-r-0 align-top"
>
{row[ci] ?? ""}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}
Loading
Loading