|
|
"use client"; |
|
|
|
|
|
import { useState, useEffect } from "react"; |
|
|
import { ArrowUp, Cpu, Database, Plus, Loader2, ChevronRight } from "lucide-react"; |
|
|
import Navbar from "@/components/Navbar"; |
|
|
import Footer from "@/components/Footer"; |
|
|
import { useRouter } from "next/navigation"; |
|
|
import { Button } from "@/components/ui/button"; |
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; |
|
|
import { Label } from "@/components/ui/label"; |
|
|
import { Textarea } from "@/components/ui/textarea"; |
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; |
|
|
import { Badge } from "@/components/ui/badge"; |
|
|
import { |
|
|
Select, |
|
|
SelectContent, |
|
|
SelectItem, |
|
|
SelectTrigger, |
|
|
SelectValue, |
|
|
} from "@/components/ui/select"; |
|
|
import { Combobox } from "@/components/ui/combobox"; |
|
|
import { toast } from "@/components/ui/toaster"; |
|
|
|
|
|
interface DistillationRequest { |
|
|
id: string; |
|
|
sourceDataset: string; |
|
|
studentModel: string; |
|
|
submitterName?: string; |
|
|
additionalNotes: string; |
|
|
upvotes: number; |
|
|
createdAt: string; |
|
|
status: "pending" | "in_progress" | "completed"; |
|
|
} |
|
|
|
|
|
interface DatasetRequest { |
|
|
id: string; |
|
|
sourceModel: string; |
|
|
submitterName?: string; |
|
|
datasetSize: string; |
|
|
reasoningDepth: string; |
|
|
topics: string[]; |
|
|
additionalNotes: string; |
|
|
upvotes: number; |
|
|
createdAt: string; |
|
|
status: "pending" | "in_progress" | "completed"; |
|
|
} |
|
|
|
|
|
interface TeichAIDataset { |
|
|
id: string; |
|
|
name: string; |
|
|
} |
|
|
|
|
|
interface OpenRouterModel { |
|
|
id: string; |
|
|
name: string; |
|
|
} |
|
|
|
|
|
interface HuggingFaceModel { |
|
|
id: string; |
|
|
name: string; |
|
|
} |
|
|
|
|
|
const STUDENT_MODELS = [ |
|
|
"Qwen3-4B", |
|
|
"Qwen3-4B-Thinking-2507", |
|
|
"Qwen3-4B-Instruct-2507", |
|
|
"Qwen3-8B", |
|
|
"Qwen3-14B", |
|
|
"Qwen3-30B-A3B-Thinking-2507", |
|
|
"Qwen3-32B", |
|
|
"Nemotron-Cascade-14B-Thinking", |
|
|
"Nemotron-Cascade-8B-Thinking", |
|
|
"Other", |
|
|
]; |
|
|
|
|
|
const SOURCE_MODEL_OTHER_HF = "Other (HuggingFace)"; |
|
|
|
|
|
const REASONING_DEPTHS = ["low", "medium", "high"]; |
|
|
const DATASET_SIZES = ["100x", "250x", "500x", "1000x", "3000x", "11000x"]; |
|
|
const TOPICS = [ |
|
|
"Coding", |
|
|
"Math", |
|
|
"Science", |
|
|
"Web Development", |
|
|
"Data Science", |
|
|
"Machine Learning", |
|
|
"Creative Writing", |
|
|
"Reasoning", |
|
|
"Logic", |
|
|
"General Knowledge", |
|
|
]; |
|
|
|
|
|
export default function Home() { |
|
|
const router = useRouter(); |
|
|
const [distillationRequests, setDistillationRequests] = useState<DistillationRequest[]>([]); |
|
|
const [datasetRequests, setDatasetRequests] = useState<DatasetRequest[]>([]); |
|
|
const [loading, setLoading] = useState(true); |
|
|
const [submitting, setSubmitting] = useState(false); |
|
|
const [showDistillForm, setShowDistillForm] = useState(false); |
|
|
const [showDatasetForm, setShowDatasetForm] = useState(false); |
|
|
|
|
|
|
|
|
const [teichaiDatasets, setTeichaiDatasets] = useState<TeichAIDataset[]>([]); |
|
|
const [openrouterModels, setOpenrouterModels] = useState<OpenRouterModel[]>([]); |
|
|
const [loadingDatasets, setLoadingDatasets] = useState(false); |
|
|
const [loadingModels, setLoadingModels] = useState(false); |
|
|
|
|
|
const [huggingfaceModels, setHuggingfaceModels] = useState<HuggingFaceModel[]>([]); |
|
|
const [hfModelQuery, setHfModelQuery] = useState(""); |
|
|
const [loadingHfModels, setLoadingHfModels] = useState(false); |
|
|
|
|
|
const [distillHuggingfaceModels, setDistillHuggingfaceModels] = useState<HuggingFaceModel[]>([]); |
|
|
const [distillHfModelQuery, setDistillHfModelQuery] = useState(""); |
|
|
const [loadingDistillHfModels, setLoadingDistillHfModels] = useState(false); |
|
|
|
|
|
|
|
|
const [sourceDataset, setSourceDataset] = useState(""); |
|
|
const [sourceDatasetOther, setSourceDatasetOther] = useState(""); |
|
|
const [studentModel, setStudentModel] = useState(""); |
|
|
const [studentModelOther, setStudentModelOther] = useState(""); |
|
|
const [distillSubmitterName, setDistillSubmitterName] = useState(""); |
|
|
const [distillNotes, setDistillNotes] = useState(""); |
|
|
|
|
|
|
|
|
const [sourceModel, setSourceModel] = useState(""); |
|
|
const [sourceModelOther, setSourceModelOther] = useState(""); |
|
|
const [datasetSubmitterName, setDatasetSubmitterName] = useState(""); |
|
|
const [datasetSize, setDatasetSize] = useState("250x"); |
|
|
const [reasoningDepth, setReasoningDepth] = useState("high"); |
|
|
const [selectedTopics, setSelectedTopics] = useState<string[]>([]); |
|
|
const [datasetNotes, setDatasetNotes] = useState(""); |
|
|
|
|
|
useEffect(() => { |
|
|
fetchRequests(); |
|
|
fetchTeichaiDatasets(); |
|
|
fetchOpenrouterModels(); |
|
|
}, []); |
|
|
|
|
|
useEffect(() => { |
|
|
let cancelled = false; |
|
|
const q = hfModelQuery.trim(); |
|
|
|
|
|
const timer = setTimeout(async () => { |
|
|
setLoadingHfModels(true); |
|
|
try { |
|
|
const res = await fetch(`/api/huggingface-models?q=${encodeURIComponent(q)}&limit=20`); |
|
|
const data = await res.json(); |
|
|
if (!cancelled) { |
|
|
setHuggingfaceModels(Array.isArray(data) ? data : []); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error("Error fetching Hugging Face models:", error); |
|
|
if (!cancelled) { |
|
|
setHuggingfaceModels([]); |
|
|
} |
|
|
} finally { |
|
|
if (!cancelled) { |
|
|
setLoadingHfModels(false); |
|
|
} |
|
|
} |
|
|
}, 250); |
|
|
|
|
|
return () => { |
|
|
cancelled = true; |
|
|
clearTimeout(timer); |
|
|
}; |
|
|
}, [hfModelQuery]); |
|
|
|
|
|
useEffect(() => { |
|
|
let cancelled = false; |
|
|
const q = distillHfModelQuery.trim(); |
|
|
|
|
|
const timer = setTimeout(async () => { |
|
|
setLoadingDistillHfModels(true); |
|
|
try { |
|
|
const res = await fetch(`/api/huggingface-models?q=${encodeURIComponent(q)}&limit=20`); |
|
|
const data = await res.json(); |
|
|
if (!cancelled) { |
|
|
setDistillHuggingfaceModels(Array.isArray(data) ? data : []); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error("Error fetching Hugging Face models:", error); |
|
|
if (!cancelled) { |
|
|
setDistillHuggingfaceModels([]); |
|
|
} |
|
|
} finally { |
|
|
if (!cancelled) { |
|
|
setLoadingDistillHfModels(false); |
|
|
} |
|
|
} |
|
|
}, 250); |
|
|
|
|
|
return () => { |
|
|
cancelled = true; |
|
|
clearTimeout(timer); |
|
|
}; |
|
|
}, [distillHfModelQuery]); |
|
|
|
|
|
async function fetchRequests() { |
|
|
try { |
|
|
const [distillRes, datasetRes] = await Promise.all([ |
|
|
fetch("/api/distillation"), |
|
|
fetch("/api/dataset"), |
|
|
]); |
|
|
const distillData = await distillRes.json(); |
|
|
const datasetData = await datasetRes.json(); |
|
|
setDistillationRequests(Array.isArray(distillData) ? distillData : []); |
|
|
setDatasetRequests(Array.isArray(datasetData) ? datasetData : []); |
|
|
} catch (error) { |
|
|
console.error("Error fetching requests:", error); |
|
|
} finally { |
|
|
setLoading(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function fetchTeichaiDatasets() { |
|
|
setLoadingDatasets(true); |
|
|
try { |
|
|
const res = await fetch("/api/teichai-datasets"); |
|
|
const data = await res.json(); |
|
|
setTeichaiDatasets(Array.isArray(data) ? data : []); |
|
|
} catch (error) { |
|
|
console.error("Error fetching TeichAI datasets:", error); |
|
|
} finally { |
|
|
setLoadingDatasets(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function fetchOpenrouterModels() { |
|
|
setLoadingModels(true); |
|
|
try { |
|
|
const res = await fetch("/api/openrouter-models"); |
|
|
const data = await res.json(); |
|
|
setOpenrouterModels(Array.isArray(data) ? data : []); |
|
|
} catch (error) { |
|
|
console.error("Error fetching OpenRouter models:", error); |
|
|
} finally { |
|
|
setLoadingModels(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function handleDistillSubmit(e: React.FormEvent) { |
|
|
e.preventDefault(); |
|
|
const resolvedSourceDataset = sourceDataset === "Other" ? sourceDatasetOther.trim() : sourceDataset; |
|
|
const resolvedStudentModel = studentModel === "Other" ? studentModelOther.trim() : studentModel; |
|
|
const resolvedSubmitterName = distillSubmitterName.trim(); |
|
|
if (!resolvedSourceDataset || !resolvedStudentModel) { |
|
|
toast({ title: "Error", description: "Please fill in required fields", variant: "destructive" }); |
|
|
return; |
|
|
} |
|
|
setSubmitting(true); |
|
|
try { |
|
|
const res = await fetch("/api/distillation", { |
|
|
method: "POST", |
|
|
headers: { "Content-Type": "application/json" }, |
|
|
body: JSON.stringify({ |
|
|
sourceDataset: resolvedSourceDataset, |
|
|
studentModel: resolvedStudentModel, |
|
|
submitterName: resolvedSubmitterName || undefined, |
|
|
additionalNotes: distillNotes, |
|
|
}), |
|
|
}); |
|
|
if (res.ok) { |
|
|
toast({ title: "Success", description: "Distillation request submitted!" }); |
|
|
setSourceDataset(""); |
|
|
setSourceDatasetOther(""); |
|
|
setStudentModel(""); |
|
|
setStudentModelOther(""); |
|
|
setDistillHfModelQuery(""); |
|
|
setDistillHuggingfaceModels([]); |
|
|
setDistillSubmitterName(""); |
|
|
setDistillNotes(""); |
|
|
setShowDistillForm(false); |
|
|
fetchRequests(); |
|
|
} else { |
|
|
throw new Error("Failed to submit"); |
|
|
} |
|
|
} catch (error) { |
|
|
toast({ title: "Error", description: "Failed to submit request", variant: "destructive" }); |
|
|
} finally { |
|
|
setSubmitting(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function handleDatasetSubmit(e: React.FormEvent) { |
|
|
e.preventDefault(); |
|
|
const resolvedSourceModel = |
|
|
sourceModel === SOURCE_MODEL_OTHER_HF ? sourceModelOther.trim() : sourceModel; |
|
|
const resolvedSubmitterName = datasetSubmitterName.trim(); |
|
|
if (!resolvedSourceModel) { |
|
|
toast({ title: "Error", description: "Please select a source model", variant: "destructive" }); |
|
|
return; |
|
|
} |
|
|
setSubmitting(true); |
|
|
try { |
|
|
const res = await fetch("/api/dataset", { |
|
|
method: "POST", |
|
|
headers: { "Content-Type": "application/json" }, |
|
|
body: JSON.stringify({ |
|
|
sourceModel: resolvedSourceModel, |
|
|
submitterName: resolvedSubmitterName || undefined, |
|
|
datasetSize, |
|
|
reasoningDepth, |
|
|
topics: selectedTopics, |
|
|
additionalNotes: datasetNotes, |
|
|
}), |
|
|
}); |
|
|
if (res.ok) { |
|
|
toast({ title: "Success", description: "Dataset request submitted!" }); |
|
|
setSourceModel(openrouterModels[0]?.id || ""); |
|
|
setSourceModelOther(""); |
|
|
setHfModelQuery(""); |
|
|
setHuggingfaceModels([]); |
|
|
setDatasetSubmitterName(""); |
|
|
setDatasetSize("250x"); |
|
|
setReasoningDepth("high"); |
|
|
setSelectedTopics([]); |
|
|
setDatasetNotes(""); |
|
|
setShowDatasetForm(false); |
|
|
fetchRequests(); |
|
|
} else { |
|
|
throw new Error("Failed to submit"); |
|
|
} |
|
|
} catch (error) { |
|
|
toast({ title: "Error", description: "Failed to submit request", variant: "destructive" }); |
|
|
} finally { |
|
|
setSubmitting(false); |
|
|
} |
|
|
} |
|
|
|
|
|
async function handleUpvote(type: "distillation" | "dataset", id: string) { |
|
|
try { |
|
|
const endpoint = type === "distillation" ? "/api/distillation" : "/api/dataset"; |
|
|
const res = await fetch(endpoint, { |
|
|
method: "PATCH", |
|
|
headers: { "Content-Type": "application/json" }, |
|
|
body: JSON.stringify({ id }), |
|
|
}); |
|
|
const data = await res.json(); |
|
|
|
|
|
if (!res.ok) { |
|
|
if (res.status === 404) { |
|
|
toast({ title: "Not found", description: "This request no longer exists", variant: "destructive" }); |
|
|
return; |
|
|
} |
|
|
toast({ title: "Error", description: data?.error || "Failed to vote", variant: "destructive" }); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (type === "distillation") { |
|
|
setDistillationRequests((prev) => |
|
|
prev.map((r) => (r.id === id ? { ...r, upvotes: data.upvotes } : r)) |
|
|
); |
|
|
} else { |
|
|
setDatasetRequests((prev) => |
|
|
prev.map((r) => (r.id === id ? { ...r, upvotes: data.upvotes } : r)) |
|
|
); |
|
|
} |
|
|
|
|
|
if (data.action === "unvoted") { |
|
|
toast({ title: "Vote removed", description: "Your vote has been removed" }); |
|
|
} else { |
|
|
toast({ title: "Voted!", description: "Your vote has been recorded" }); |
|
|
} |
|
|
} catch (error) { |
|
|
toast({ title: "Error", description: "Failed to vote", variant: "destructive" }); |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleTopic(topic: string) { |
|
|
setSelectedTopics((prev) => |
|
|
prev.includes(topic) ? prev.filter((t) => t !== topic) : [...prev, topic] |
|
|
); |
|
|
} |
|
|
|
|
|
function getStatusBadge(status: string) { |
|
|
switch (status) { |
|
|
case "completed": |
|
|
return <Badge variant="success">Completed</Badge>; |
|
|
case "in_progress": |
|
|
return <Badge variant="warning">In Progress</Badge>; |
|
|
default: |
|
|
return <Badge variant="secondary">Pending</Badge>; |
|
|
} |
|
|
} |
|
|
|
|
|
function formatDate(dateStr: string) { |
|
|
return new Date(dateStr).toLocaleDateString("en-US", { |
|
|
month: "short", |
|
|
day: "numeric", |
|
|
year: "numeric", |
|
|
}); |
|
|
} |
|
|
|
|
|
function goToDiscussion(type: "distillation" | "dataset", id: string) { |
|
|
router.push(`/requests/${type}/${id}`); |
|
|
} |
|
|
|
|
|
return ( |
|
|
<main className="min-h-screen bg-background"> |
|
|
<Navbar /> |
|
|
|
|
|
{/* Hero */} |
|
|
<section className="relative overflow-hidden pt-24 pb-16"> |
|
|
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,rgba(255,76,0,0.10),transparent_55%)] dark:bg-[radial-gradient(circle_at_top,rgba(255,76,0,0.16),transparent_55%)]" /> |
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6"> |
|
|
<div className="max-w-3xl"> |
|
|
<p className="mb-3 text-sm font-medium text-primary">Community Requests</p> |
|
|
<h1 className="mb-6 text-4xl font-bold leading-tight tracking-tight text-foreground md:text-5xl"> |
|
|
Request Model Distillations & Datasets |
|
|
</h1> |
|
|
<p className="mb-8 text-lg leading-relaxed text-muted-foreground"> |
|
|
Submit your requests for new distilled models or reasoning datasets. Vote on requests |
|
|
from other community members to help us prioritize what to build next. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
{/* Main Content */} |
|
|
<section className="py-8"> |
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6"> |
|
|
<Tabs defaultValue="distillation" className="w-full"> |
|
|
<TabsList className="mb-6 grid w-full grid-cols-2"> |
|
|
<TabsTrigger value="distillation" className="flex items-center gap-2"> |
|
|
<Cpu className="h-4 w-4" /> |
|
|
Model Distillation |
|
|
</TabsTrigger> |
|
|
<TabsTrigger value="dataset" className="flex items-center gap-2"> |
|
|
<Database className="h-4 w-4" /> |
|
|
Dataset |
|
|
</TabsTrigger> |
|
|
</TabsList> |
|
|
|
|
|
{/* Distillation Tab */} |
|
|
<TabsContent value="distillation"> |
|
|
<div className="mb-6 flex items-center justify-between"> |
|
|
<h2 className="text-xl font-semibold text-foreground">Distillation Requests</h2> |
|
|
<Button onClick={() => setShowDistillForm(!showDistillForm)}> |
|
|
<Plus className="h-4 w-4" /> |
|
|
New Request |
|
|
</Button> |
|
|
</div> |
|
|
|
|
|
{showDistillForm && ( |
|
|
<Card className="mb-6"> |
|
|
<CardHeader> |
|
|
<CardTitle>Request a Distilled Model</CardTitle> |
|
|
<CardDescription> |
|
|
Select one of our existing datasets to distill into a student model |
|
|
</CardDescription> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<form onSubmit={handleDistillSubmit} className="space-y-4"> |
|
|
<div className="grid gap-4 md:grid-cols-2"> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="dataset">Source Dataset *</Label> |
|
|
<Combobox |
|
|
options={[...teichaiDatasets, { id: "Other", name: "Other" }]} |
|
|
value={sourceDataset} |
|
|
onValueChange={(v) => { |
|
|
setSourceDataset(v); |
|
|
if (v !== "Other") setSourceDatasetOther(""); |
|
|
}} |
|
|
placeholder="Select a TeichAI dataset" |
|
|
searchPlaceholder="Search datasets..." |
|
|
emptyMessage="No datasets found" |
|
|
loading={loadingDatasets} |
|
|
/> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="student">Student Model *</Label> |
|
|
<Select |
|
|
value={studentModel} |
|
|
onValueChange={(v) => { |
|
|
setStudentModel(v); |
|
|
if (v !== "Other") { |
|
|
setStudentModelOther(""); |
|
|
setDistillHfModelQuery(""); |
|
|
setDistillHuggingfaceModels([]); |
|
|
} else { |
|
|
setStudentModelOther(""); |
|
|
setDistillHfModelQuery(""); |
|
|
setDistillHuggingfaceModels([]); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<SelectTrigger> |
|
|
<SelectValue placeholder="Select student model" /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
{STUDENT_MODELS.map((model) => ( |
|
|
<SelectItem key={model} value={model}> |
|
|
{model} |
|
|
</SelectItem> |
|
|
))} |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
</div> |
|
|
{sourceDataset === "Other" && ( |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="datasetOther">Source Dataset (Other) *</Label> |
|
|
<input |
|
|
id="datasetOther" |
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" |
|
|
value={sourceDatasetOther} |
|
|
onChange={(e) => setSourceDatasetOther(e.target.value)} |
|
|
placeholder="Type the dataset name" |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
{studentModel === "Other" && ( |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="studentOther">Hugging Face Student Model *</Label> |
|
|
<Combobox |
|
|
options={distillHuggingfaceModels} |
|
|
value={studentModelOther} |
|
|
onValueChange={setStudentModelOther} |
|
|
placeholder="Search safetensors models" |
|
|
searchPlaceholder="Type to search models..." |
|
|
emptyMessage={distillHfModelQuery.trim() ? "No models found" : "Start typing to search"} |
|
|
loading={loadingDistillHfModels} |
|
|
searchValue={distillHfModelQuery} |
|
|
onSearchValueChange={setDistillHfModelQuery} |
|
|
disableLocalFilter |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="distillSubmitterName">Name (optional)</Label> |
|
|
<input |
|
|
id="distillSubmitterName" |
|
|
title="Name" |
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" |
|
|
value={distillSubmitterName} |
|
|
onChange={(e) => setDistillSubmitterName(e.target.value)} |
|
|
placeholder="Your name (optional)" |
|
|
/> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="notes">Additional Notes</Label> |
|
|
<Textarea |
|
|
id="notes" |
|
|
placeholder="Any specific requirements or context..." |
|
|
value={distillNotes} |
|
|
onChange={(e) => setDistillNotes(e.target.value)} |
|
|
/> |
|
|
</div> |
|
|
<div className="flex gap-2"> |
|
|
<Button type="submit" disabled={submitting}> |
|
|
{submitting && <Loader2 className="h-4 w-4 animate-spin" />} |
|
|
Submit Request |
|
|
</Button> |
|
|
<Button type="button" variant="outline" onClick={() => setShowDistillForm(false)}> |
|
|
Cancel |
|
|
</Button> |
|
|
</div> |
|
|
</form> |
|
|
</CardContent> |
|
|
</Card> |
|
|
)} |
|
|
|
|
|
{loading ? ( |
|
|
<div className="flex justify-center py-12"> |
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> |
|
|
</div> |
|
|
) : distillationRequests.length === 0 ? ( |
|
|
<Card className="p-12 text-center"> |
|
|
<p className="text-muted-foreground">No distillation requests yet. Be the first!</p> |
|
|
</Card> |
|
|
) : ( |
|
|
<div className="space-y-4"> |
|
|
{distillationRequests.map((request) => ( |
|
|
<Card |
|
|
key={request.id} |
|
|
className="group cursor-pointer transition-all hover:shadow-md" |
|
|
onClick={() => goToDiscussion("distillation", request.id)} |
|
|
role="link" |
|
|
tabIndex={0} |
|
|
onKeyDown={(e) => { |
|
|
if (e.key === "Enter" || e.key === " ") { |
|
|
e.preventDefault(); |
|
|
goToDiscussion("distillation", request.id); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<CardContent className="p-5"> |
|
|
<div className="flex items-start gap-4"> |
|
|
<button |
|
|
onClick={(e) => { |
|
|
e.stopPropagation(); |
|
|
handleUpvote("distillation", request.id); |
|
|
}} |
|
|
className="flex flex-col items-center gap-1 rounded-lg border border-border bg-muted px-3 py-2 transition-colors hover:border-primary hover:bg-accent" |
|
|
> |
|
|
<ArrowUp className="h-4 w-4 text-primary" /> |
|
|
<span className="text-sm font-semibold">{request.upvotes}</span> |
|
|
</button> |
|
|
<div className="flex-1"> |
|
|
<div className="mb-2 flex flex-wrap items-center gap-2"> |
|
|
<h3 className="font-medium text-foreground"> |
|
|
{request.sourceDataset} → {request.studentModel} |
|
|
</h3> |
|
|
{getStatusBadge(request.status)} |
|
|
</div> |
|
|
{request.submitterName ? ( |
|
|
<p className="mb-2 text-sm text-muted-foreground">By {request.submitterName}</p> |
|
|
) : null} |
|
|
{request.additionalNotes && ( |
|
|
<p className="mb-2 text-sm text-muted-foreground">{request.additionalNotes}</p> |
|
|
)} |
|
|
<p className="text-xs text-muted-foreground"> |
|
|
Requested on {formatDate(request.createdAt)} |
|
|
</p> |
|
|
</div> |
|
|
<div className="flex items-center text-muted-foreground"> |
|
|
<ChevronRight className="h-5 w-5 opacity-50 transition-opacity group-hover:opacity-100" /> |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
))} |
|
|
</div> |
|
|
)} |
|
|
</TabsContent> |
|
|
|
|
|
{/* Dataset Tab */} |
|
|
<TabsContent value="dataset"> |
|
|
<div className="mb-6 flex items-center justify-between"> |
|
|
<h2 className="text-xl font-semibold text-foreground">Dataset</h2> |
|
|
<Button onClick={() => setShowDatasetForm(!showDatasetForm)}> |
|
|
<Plus className="h-4 w-4" /> |
|
|
New Request |
|
|
</Button> |
|
|
</div> |
|
|
|
|
|
{showDatasetForm && ( |
|
|
<Card className="mb-6"> |
|
|
<CardHeader> |
|
|
<CardTitle>Request a Dataset</CardTitle> |
|
|
<CardDescription> |
|
|
Specify which model to generate reasoning data from |
|
|
</CardDescription> |
|
|
</CardHeader> |
|
|
<CardContent> |
|
|
<form onSubmit={handleDatasetSubmit} className="space-y-4"> |
|
|
<div className="grid gap-4 md:grid-cols-2"> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="sourceModel">Source Model *</Label> |
|
|
<Combobox |
|
|
options={[ |
|
|
...openrouterModels, |
|
|
{ id: SOURCE_MODEL_OTHER_HF, name: SOURCE_MODEL_OTHER_HF }, |
|
|
]} |
|
|
value={sourceModel} |
|
|
onValueChange={(v) => { |
|
|
setSourceModel(v); |
|
|
if (v !== SOURCE_MODEL_OTHER_HF) { |
|
|
setSourceModelOther(""); |
|
|
setHfModelQuery(""); |
|
|
setHuggingfaceModels([]); |
|
|
} |
|
|
}} |
|
|
placeholder="Select a model" |
|
|
searchPlaceholder="Search OpenRouter models..." |
|
|
emptyMessage="No models found" |
|
|
loading={loadingModels} |
|
|
/> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="size">Dataset Size</Label> |
|
|
<Select value={datasetSize} onValueChange={setDatasetSize}> |
|
|
<SelectTrigger> |
|
|
<SelectValue /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
{DATASET_SIZES.map((size) => ( |
|
|
<SelectItem key={size} value={size}> |
|
|
{size} samples |
|
|
</SelectItem> |
|
|
))} |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
</div> |
|
|
{sourceModel === SOURCE_MODEL_OTHER_HF && ( |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="sourceModelOther">Hugging Face Model *</Label> |
|
|
<Combobox |
|
|
options={huggingfaceModels} |
|
|
value={sourceModelOther} |
|
|
onValueChange={setSourceModelOther} |
|
|
placeholder="Search safetensors models" |
|
|
searchPlaceholder="Type to search models..." |
|
|
emptyMessage={hfModelQuery.trim() ? "No models found" : "Start typing to search"} |
|
|
loading={loadingHfModels} |
|
|
searchValue={hfModelQuery} |
|
|
onSearchValueChange={setHfModelQuery} |
|
|
disableLocalFilter |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="datasetSubmitterName">Name (optional)</Label> |
|
|
<input |
|
|
id="datasetSubmitterName" |
|
|
title="Name" |
|
|
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" |
|
|
value={datasetSubmitterName} |
|
|
onChange={(e) => setDatasetSubmitterName(e.target.value)} |
|
|
placeholder="Your name (optional)" |
|
|
/> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="depth">Reasoning Depth</Label> |
|
|
<Select value={reasoningDepth} onValueChange={setReasoningDepth}> |
|
|
<SelectTrigger className="w-[200px]"> |
|
|
<SelectValue /> |
|
|
</SelectTrigger> |
|
|
<SelectContent> |
|
|
{REASONING_DEPTHS.map((depth) => ( |
|
|
<SelectItem key={depth} value={depth}> |
|
|
{depth.charAt(0).toUpperCase() + depth.slice(1)} |
|
|
</SelectItem> |
|
|
))} |
|
|
</SelectContent> |
|
|
</Select> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label>Topics (select multiple)</Label> |
|
|
<div className="flex flex-wrap gap-2"> |
|
|
{TOPICS.map((topic) => ( |
|
|
<button |
|
|
key={topic} |
|
|
type="button" |
|
|
onClick={() => toggleTopic(topic)} |
|
|
className={`rounded-full px-3 py-1 text-sm transition-colors ${selectedTopics.includes(topic) |
|
|
? "bg-primary text-primary-foreground" |
|
|
: "bg-muted text-muted-foreground hover:bg-accent" |
|
|
}`} |
|
|
> |
|
|
{topic} |
|
|
</button> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
<div className="space-y-2"> |
|
|
<Label htmlFor="datasetNotes">Additional Notes</Label> |
|
|
<Textarea |
|
|
id="datasetNotes" |
|
|
placeholder="Any specific requirements or context..." |
|
|
value={datasetNotes} |
|
|
onChange={(e) => setDatasetNotes(e.target.value)} |
|
|
/> |
|
|
</div> |
|
|
<div className="flex gap-2"> |
|
|
<Button type="submit" disabled={submitting}> |
|
|
{submitting && <Loader2 className="h-4 w-4 animate-spin" />} |
|
|
Submit Request |
|
|
</Button> |
|
|
<Button type="button" variant="outline" onClick={() => setShowDatasetForm(false)}> |
|
|
Cancel |
|
|
</Button> |
|
|
</div> |
|
|
</form> |
|
|
</CardContent> |
|
|
</Card> |
|
|
)} |
|
|
|
|
|
{loading ? ( |
|
|
<div className="flex justify-center py-12"> |
|
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> |
|
|
</div> |
|
|
) : datasetRequests.length === 0 ? ( |
|
|
<Card className="p-12 text-center"> |
|
|
<p className="text-muted-foreground">No dataset requests yet. Be the first!</p> |
|
|
</Card> |
|
|
) : ( |
|
|
<div className="space-y-4"> |
|
|
{datasetRequests.map((request) => ( |
|
|
<Card |
|
|
key={request.id} |
|
|
className="group cursor-pointer transition-all hover:shadow-md" |
|
|
onClick={() => goToDiscussion("dataset", request.id)} |
|
|
role="link" |
|
|
tabIndex={0} |
|
|
onKeyDown={(e) => { |
|
|
if (e.key === "Enter" || e.key === " ") { |
|
|
e.preventDefault(); |
|
|
goToDiscussion("dataset", request.id); |
|
|
} |
|
|
}} |
|
|
> |
|
|
<CardContent className="p-5"> |
|
|
<div className="flex items-start gap-4"> |
|
|
<button |
|
|
onClick={(e) => { |
|
|
e.stopPropagation(); |
|
|
handleUpvote("dataset", request.id); |
|
|
}} |
|
|
className="flex flex-col items-center gap-1 rounded-lg border border-border bg-muted px-3 py-2 transition-colors hover:border-primary hover:bg-accent" |
|
|
> |
|
|
<ArrowUp className="h-4 w-4 text-primary" /> |
|
|
<span className="text-sm font-semibold">{request.upvotes}</span> |
|
|
</button> |
|
|
<div className="flex-1"> |
|
|
<div className="mb-2 flex flex-wrap items-center gap-2"> |
|
|
<h3 className="font-medium text-foreground"> |
|
|
{request.sourceModel} Dataset ({request.datasetSize}) |
|
|
</h3> |
|
|
{getStatusBadge(request.status)} |
|
|
<Badge variant="outline">{request.reasoningDepth} reasoning</Badge> |
|
|
</div> |
|
|
{request.submitterName ? ( |
|
|
<p className="mb-2 text-sm text-muted-foreground">By {request.submitterName}</p> |
|
|
) : null} |
|
|
{request.topics.length > 0 && ( |
|
|
<div className="mb-2 flex flex-wrap gap-1"> |
|
|
{request.topics.map((topic) => ( |
|
|
<Badge key={topic} variant="secondary" className="text-xs"> |
|
|
{topic} |
|
|
</Badge> |
|
|
))} |
|
|
</div> |
|
|
)} |
|
|
{request.additionalNotes && ( |
|
|
<p className="mb-2 text-sm text-muted-foreground">{request.additionalNotes}</p> |
|
|
)} |
|
|
<p className="text-xs text-muted-foreground"> |
|
|
Requested on {formatDate(request.createdAt)} |
|
|
</p> |
|
|
</div> |
|
|
<div className="flex items-center text-muted-foreground"> |
|
|
<ChevronRight className="h-5 w-5 opacity-50 transition-opacity group-hover:opacity-100" /> |
|
|
</div> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
))} |
|
|
</div> |
|
|
)} |
|
|
</TabsContent> |
|
|
</Tabs> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
{/* Info Section */} |
|
|
<section className="py-16"> |
|
|
<div className="mx-auto max-w-6xl px-4 sm:px-6"> |
|
|
<Card className="overflow-hidden"> |
|
|
<CardContent className="p-8 md:p-12"> |
|
|
<div className="max-w-2xl"> |
|
|
<h2 className="mb-4 text-2xl font-bold text-foreground">How It Works</h2> |
|
|
<ul className="space-y-3 text-muted-foreground"> |
|
|
<li className="flex items-start gap-2"> |
|
|
<span className="font-bold text-primary">1.</span> |
|
|
Submit a request for a model distillation or reasoning dataset |
|
|
</li> |
|
|
<li className="flex items-start gap-2"> |
|
|
<span className="font-bold text-primary">2.</span> |
|
|
Upvote requests from other community members |
|
|
</li> |
|
|
<li className="flex items-start gap-2"> |
|
|
<span className="font-bold text-primary">3.</span> |
|
|
We prioritize requests based on community interest |
|
|
</li> |
|
|
<li className="flex items-start gap-2"> |
|
|
<span className="font-bold text-primary">4.</span> |
|
|
Models and datasets are published on our Hugging Face page |
|
|
</li> |
|
|
<li className="flex items-start gap-2"> |
|
|
<span className="font-bold text-primary">Note:</span> |
|
|
We will do our best to fulfill as many requests as possible, but we can’t guarantee anything due to time and money constraints. |
|
|
</li> |
|
|
</ul> |
|
|
</div> |
|
|
</CardContent> |
|
|
</Card> |
|
|
</div> |
|
|
</section> |
|
|
|
|
|
<Footer /> |
|
|
</main> |
|
|
); |
|
|
} |
|
|
|