Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 { ParsedCsv, parseCsv } from "@/app/lib/utils/csv";
import { CsvPreviewProps } 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.
Comment thread
Ayush8923 marked this conversation as resolved.
</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