armand0e commited on
Commit
6593c28
·
1 Parent(s): c378ec4

Supabase backend + UI dropdown updates

Browse files
src/app/api/admin/requests/[type]/[id]/comments/route.ts CHANGED
@@ -19,7 +19,7 @@ export async function POST(
19
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
20
  }
21
 
22
- const existing = getRequest(type, params.id);
23
  if (!existing) {
24
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
25
  }
@@ -30,7 +30,7 @@ export async function POST(
30
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
31
  }
32
 
33
- const comment = addAdminComment(type, params.id, text, userId);
34
  const response = NextResponse.json({ ok: true, comment });
35
  if (shouldSetCookie) {
36
  response.cookies.set({ ...userCookieOptions(), value: userId });
 
19
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
20
  }
21
 
22
+ const existing = await getRequest(type, params.id);
23
  if (!existing) {
24
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
25
  }
 
30
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
31
  }
32
 
33
+ const comment = await addAdminComment(type, params.id, text, userId);
34
  const response = NextResponse.json({ ok: true, comment });
35
  if (shouldSetCookie) {
36
  response.cookies.set({ ...userCookieOptions(), value: userId });
src/app/api/admin/requests/[type]/[id]/route.ts CHANGED
@@ -23,7 +23,7 @@ export async function PATCH(
23
  return NextResponse.json({ error: "Invalid status" }, { status: 400 });
24
  }
25
 
26
- const ok = updateRequestStatus(type, params.id, status);
27
  if (!ok) {
28
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
29
  }
@@ -50,7 +50,7 @@ export async function DELETE(
50
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
51
  }
52
 
53
- const ok = deleteRequest(type, params.id);
54
  if (!ok) {
55
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
56
  }
 
23
  return NextResponse.json({ error: "Invalid status" }, { status: 400 });
24
  }
25
 
26
+ const ok = await updateRequestStatus(type, params.id, status);
27
  if (!ok) {
28
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
29
  }
 
50
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
51
  }
52
 
53
+ const ok = await deleteRequest(type, params.id);
54
  if (!ok) {
55
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
56
  }
src/app/api/dataset/route.ts CHANGED
@@ -8,7 +8,7 @@ import { getOrCreateUserId, userCookieOptions } from "@/lib/userIdentity";
8
 
9
  export async function GET() {
10
  try {
11
- const requests = getDatasetRequests();
12
  return NextResponse.json(requests);
13
  } catch (error) {
14
  console.error("Error fetching dataset requests:", error);
@@ -29,7 +29,7 @@ export async function POST(request: NextRequest) {
29
  );
30
  }
31
 
32
- const newRequest = addDatasetRequest({
33
  sourceModel,
34
  datasetSize: datasetSize || "250x",
35
  reasoningDepth: reasoningDepth || "high",
@@ -62,7 +62,7 @@ export async function PATCH(request: NextRequest) {
62
  request.headers.get("x-real-ip") ||
63
  "anonymous";
64
 
65
- const result = upvoteDataset(id, ip);
66
 
67
  if (!result.success) {
68
  return NextResponse.json(
 
8
 
9
  export async function GET() {
10
  try {
11
+ const requests = await getDatasetRequests();
12
  return NextResponse.json(requests);
13
  } catch (error) {
14
  console.error("Error fetching dataset requests:", error);
 
29
  );
30
  }
31
 
32
+ const newRequest = await addDatasetRequest({
33
  sourceModel,
34
  datasetSize: datasetSize || "250x",
35
  reasoningDepth: reasoningDepth || "high",
 
62
  request.headers.get("x-real-ip") ||
63
  "anonymous";
64
 
65
+ const result = await upvoteDataset(id, ip);
66
 
67
  if (!result.success) {
68
  return NextResponse.json(
src/app/api/distillation/route.ts CHANGED
@@ -8,7 +8,7 @@ import { getOrCreateUserId, userCookieOptions } from "@/lib/userIdentity";
8
 
9
  export async function GET() {
10
  try {
11
- const requests = getDistillationRequests();
12
  return NextResponse.json(requests);
13
  } catch (error) {
14
  console.error("Error fetching distillation requests:", error);
@@ -29,7 +29,7 @@ export async function POST(request: NextRequest) {
29
  );
30
  }
31
 
32
- const newRequest = addDistillationRequest({
33
  sourceDataset,
34
  studentModel,
35
  additionalNotes: additionalNotes || "",
@@ -60,7 +60,7 @@ export async function PATCH(request: NextRequest) {
60
  request.headers.get("x-real-ip") ||
61
  "anonymous";
62
 
63
- const result = upvoteDistillation(id, ip);
64
 
65
  if (!result.success) {
66
  return NextResponse.json(
 
8
 
9
  export async function GET() {
10
  try {
11
+ const requests = await getDistillationRequests();
12
  return NextResponse.json(requests);
13
  } catch (error) {
14
  console.error("Error fetching distillation requests:", error);
 
29
  );
30
  }
31
 
32
+ const newRequest = await addDistillationRequest({
33
  sourceDataset,
34
  studentModel,
35
  additionalNotes: additionalNotes || "",
 
60
  request.headers.get("x-real-ip") ||
61
  "anonymous";
62
 
63
+ const result = await upvoteDistillation(id, ip);
64
 
65
  if (!result.success) {
66
  return NextResponse.json(
src/app/api/requests/[type]/[id]/comments/[commentId]/route.ts CHANGED
@@ -22,7 +22,7 @@ export async function PATCH(
22
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
23
  }
24
 
25
- const thread = getThread(type, params.id);
26
  const comment = thread.comments.find((c) => c.id === params.commentId);
27
  if (!comment) {
28
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
@@ -40,7 +40,7 @@ export async function PATCH(
40
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
41
  }
42
 
43
- const updated = updateComment(type, params.id, params.commentId, text);
44
  if (!updated) {
45
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
46
  }
@@ -75,7 +75,7 @@ export async function DELETE(
75
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
76
  }
77
 
78
- const thread = getThread(type, params.id);
79
  const comment = thread.comments.find((c) => c.id === params.commentId);
80
  if (!comment) {
81
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
@@ -89,7 +89,7 @@ export async function DELETE(
89
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
90
  }
91
 
92
- const ok = deleteComment(type, params.id, params.commentId);
93
  if (!ok) {
94
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
95
  }
 
22
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
23
  }
24
 
25
+ const thread = await getThread(type, params.id);
26
  const comment = thread.comments.find((c) => c.id === params.commentId);
27
  if (!comment) {
28
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
 
40
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
41
  }
42
 
43
+ const updated = await updateComment(type, params.id, params.commentId, text);
44
  if (!updated) {
45
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
46
  }
 
75
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
76
  }
77
 
78
+ const thread = await getThread(type, params.id);
79
  const comment = thread.comments.find((c) => c.id === params.commentId);
80
  if (!comment) {
81
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
 
89
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
90
  }
91
 
92
+ const ok = await deleteComment(type, params.id, params.commentId);
93
  if (!ok) {
94
  return NextResponse.json({ error: "Comment not found" }, { status: 404 });
95
  }
src/app/api/requests/[type]/[id]/comments/route.ts CHANGED
@@ -20,7 +20,7 @@ export async function POST(
20
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
21
  }
22
 
23
- const existing = getRequest(type, params.id);
24
  if (!existing) {
25
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
26
  }
@@ -33,7 +33,7 @@ export async function POST(
33
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
34
  }
35
 
36
- const comment = addUserComment(type, params.id, text, author, userId);
37
  const response = NextResponse.json({ ok: true, comment });
38
  if (shouldSetCookie) {
39
  response.cookies.set({ ...userCookieOptions(), value: userId });
 
20
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
21
  }
22
 
23
+ const existing = await getRequest(type, params.id);
24
  if (!existing) {
25
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
26
  }
 
33
  return NextResponse.json({ error: "Comment body is required" }, { status: 400 });
34
  }
35
 
36
+ const comment = await addUserComment(type, params.id, text, author, userId);
37
  const response = NextResponse.json({ ok: true, comment });
38
  if (shouldSetCookie) {
39
  response.cookies.set({ ...userCookieOptions(), value: userId });
src/app/api/requests/[type]/[id]/route.ts CHANGED
@@ -20,7 +20,7 @@ export async function GET(
20
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
21
  }
22
 
23
- const requestItem = getRequest(type, params.id);
24
  if (!requestItem) {
25
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
26
  }
@@ -30,7 +30,7 @@ export async function GET(
30
  const canEditRequest = admin || (Boolean(ownerId) && ownerId === userId);
31
  const canDeleteRequest = canEditRequest;
32
 
33
- const thread = getThread(type, params.id);
34
  const threadWithPerms = {
35
  ...thread,
36
  comments: thread.comments.map((c) => {
@@ -72,7 +72,7 @@ export async function PATCH(
72
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
73
  }
74
 
75
- const existing = getRequest(type, params.id);
76
  if (!existing) {
77
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
78
  }
@@ -87,12 +87,12 @@ export async function PATCH(
87
  const body = await request.json();
88
  const updated =
89
  type === "distillation"
90
- ? updateDistillationRequest(params.id, {
91
  sourceDataset: typeof body?.sourceDataset === "string" ? body.sourceDataset : undefined,
92
  studentModel: typeof body?.studentModel === "string" ? body.studentModel : undefined,
93
  additionalNotes: typeof body?.additionalNotes === "string" ? body.additionalNotes : undefined,
94
  })
95
- : updateDatasetRequest(params.id, {
96
  sourceModel: typeof body?.sourceModel === "string" ? body.sourceModel : undefined,
97
  datasetSize: typeof body?.datasetSize === "string" ? body.datasetSize : undefined,
98
  reasoningDepth: typeof body?.reasoningDepth === "string" ? body.reasoningDepth : undefined,
@@ -127,7 +127,7 @@ export async function DELETE(
127
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
128
  }
129
 
130
- const existing = getRequest(type, params.id);
131
  if (!existing) {
132
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
133
  }
@@ -139,7 +139,7 @@ export async function DELETE(
139
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
140
  }
141
 
142
- const ok = deleteRequest(type, params.id);
143
  if (!ok) {
144
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
145
  }
 
20
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
21
  }
22
 
23
+ const requestItem = await getRequest(type, params.id);
24
  if (!requestItem) {
25
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
26
  }
 
30
  const canEditRequest = admin || (Boolean(ownerId) && ownerId === userId);
31
  const canDeleteRequest = canEditRequest;
32
 
33
+ const thread = await getThread(type, params.id);
34
  const threadWithPerms = {
35
  ...thread,
36
  comments: thread.comments.map((c) => {
 
72
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
73
  }
74
 
75
+ const existing = await getRequest(type, params.id);
76
  if (!existing) {
77
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
78
  }
 
87
  const body = await request.json();
88
  const updated =
89
  type === "distillation"
90
+ ? await updateDistillationRequest(params.id, {
91
  sourceDataset: typeof body?.sourceDataset === "string" ? body.sourceDataset : undefined,
92
  studentModel: typeof body?.studentModel === "string" ? body.studentModel : undefined,
93
  additionalNotes: typeof body?.additionalNotes === "string" ? body.additionalNotes : undefined,
94
  })
95
+ : await updateDatasetRequest(params.id, {
96
  sourceModel: typeof body?.sourceModel === "string" ? body.sourceModel : undefined,
97
  datasetSize: typeof body?.datasetSize === "string" ? body.datasetSize : undefined,
98
  reasoningDepth: typeof body?.reasoningDepth === "string" ? body.reasoningDepth : undefined,
 
127
  return NextResponse.json({ error: "Invalid request type" }, { status: 400 });
128
  }
129
 
130
+ const existing = await getRequest(type, params.id);
131
  if (!existing) {
132
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
133
  }
 
139
  return NextResponse.json({ error: "Forbidden" }, { status: 403 });
140
  }
141
 
142
+ const ok = await deleteRequest(type, params.id);
143
  if (!ok) {
144
  return NextResponse.json({ error: "Request not found" }, { status: 404 });
145
  }
src/app/page.tsx CHANGED
@@ -53,6 +53,11 @@ interface OpenRouterModel {
53
  name: string;
54
  }
55
 
 
 
 
 
 
56
  const STUDENT_MODELS = [
57
  "Qwen3-4B",
58
  "Qwen3-8B",
@@ -64,6 +69,17 @@ const STUDENT_MODELS = [
64
  "Other",
65
  ];
66
 
 
 
 
 
 
 
 
 
 
 
 
67
  const REASONING_DEPTHS = ["low", "medium", "high"];
68
  const DATASET_SIZES = ["100x", "250x", "500x", "1000x", "3000x", "11000x"];
69
  const TOPICS = [
@@ -94,6 +110,10 @@ export default function Home() {
94
  const [loadingDatasets, setLoadingDatasets] = useState(false);
95
  const [loadingModels, setLoadingModels] = useState(false);
96
 
 
 
 
 
97
  // Distillation form state
98
  const [sourceDataset, setSourceDataset] = useState("");
99
  const [sourceDatasetOther, setSourceDatasetOther] = useState("");
@@ -115,6 +135,36 @@ export default function Home() {
115
  fetchOpenrouterModels();
116
  }, []);
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  async function fetchRequests() {
119
  try {
120
  const [distillRes, datasetRes] = await Promise.all([
@@ -530,16 +580,19 @@ export default function Home() {
530
  <div className="space-y-2">
531
  <Label htmlFor="sourceModel">Source Model *</Label>
532
  <Combobox
533
- options={[...openrouterModels, { id: "Other", name: "Other" }]}
534
  value={sourceModel}
535
  onValueChange={(v) => {
536
  setSourceModel(v);
537
- if (v !== "Other") setSourceModelOther("");
 
 
 
 
538
  }}
539
- placeholder="Select an OpenRouter model"
540
  searchPlaceholder="Search models..."
541
  emptyMessage="No models found"
542
- loading={loadingModels}
543
  />
544
  </div>
545
  <div className="space-y-2">
@@ -560,13 +613,18 @@ export default function Home() {
560
  </div>
561
  {sourceModel === "Other" && (
562
  <div className="space-y-2">
563
- <Label htmlFor="sourceModelOther">Source Model (Other) *</Label>
564
- <input
565
- id="sourceModelOther"
566
- 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"
567
  value={sourceModelOther}
568
- onChange={(e) => setSourceModelOther(e.target.value)}
569
- placeholder="Type the model name"
 
 
 
 
 
 
570
  />
571
  </div>
572
  )}
 
53
  name: string;
54
  }
55
 
56
+ interface HuggingFaceModel {
57
+ id: string;
58
+ name: string;
59
+ }
60
+
61
  const STUDENT_MODELS = [
62
  "Qwen3-4B",
63
  "Qwen3-8B",
 
69
  "Other",
70
  ];
71
 
72
+ const SOURCE_MODELS = [
73
+ "google/gemini-3-flash-preview",
74
+ "openai/gpt-4o-mini",
75
+ "openai/gpt-4o",
76
+ "anthropic/claude-3-5-sonnet",
77
+ "deepseek/deepseek-r1",
78
+ "Qwen/Qwen2.5-72B-Instruct",
79
+ "meta-llama/Llama-3.1-8B-Instruct",
80
+ "Other",
81
+ ];
82
+
83
  const REASONING_DEPTHS = ["low", "medium", "high"];
84
  const DATASET_SIZES = ["100x", "250x", "500x", "1000x", "3000x", "11000x"];
85
  const TOPICS = [
 
110
  const [loadingDatasets, setLoadingDatasets] = useState(false);
111
  const [loadingModels, setLoadingModels] = useState(false);
112
 
113
+ const [huggingfaceModels, setHuggingfaceModels] = useState<HuggingFaceModel[]>([]);
114
+ const [hfModelQuery, setHfModelQuery] = useState("");
115
+ const [loadingHfModels, setLoadingHfModels] = useState(false);
116
+
117
  // Distillation form state
118
  const [sourceDataset, setSourceDataset] = useState("");
119
  const [sourceDatasetOther, setSourceDatasetOther] = useState("");
 
135
  fetchOpenrouterModels();
136
  }, []);
137
 
138
+ useEffect(() => {
139
+ let cancelled = false;
140
+ const q = hfModelQuery.trim();
141
+
142
+ const timer = setTimeout(async () => {
143
+ setLoadingHfModels(true);
144
+ try {
145
+ const res = await fetch(`/api/huggingface-models?q=${encodeURIComponent(q)}&limit=20`);
146
+ const data = await res.json();
147
+ if (!cancelled) {
148
+ setHuggingfaceModels(Array.isArray(data) ? data : []);
149
+ }
150
+ } catch (error) {
151
+ console.error("Error fetching Hugging Face models:", error);
152
+ if (!cancelled) {
153
+ setHuggingfaceModels([]);
154
+ }
155
+ } finally {
156
+ if (!cancelled) {
157
+ setLoadingHfModels(false);
158
+ }
159
+ }
160
+ }, 250);
161
+
162
+ return () => {
163
+ cancelled = true;
164
+ clearTimeout(timer);
165
+ };
166
+ }, [hfModelQuery]);
167
+
168
  async function fetchRequests() {
169
  try {
170
  const [distillRes, datasetRes] = await Promise.all([
 
580
  <div className="space-y-2">
581
  <Label htmlFor="sourceModel">Source Model *</Label>
582
  <Combobox
583
+ options={SOURCE_MODELS.map((m) => ({ id: m, name: m }))}
584
  value={sourceModel}
585
  onValueChange={(v) => {
586
  setSourceModel(v);
587
+ if (v !== "Other") {
588
+ setSourceModelOther("");
589
+ setHfModelQuery("");
590
+ setHuggingfaceModels([]);
591
+ }
592
  }}
593
+ placeholder="Select a model"
594
  searchPlaceholder="Search models..."
595
  emptyMessage="No models found"
 
596
  />
597
  </div>
598
  <div className="space-y-2">
 
613
  </div>
614
  {sourceModel === "Other" && (
615
  <div className="space-y-2">
616
+ <Label htmlFor="sourceModelOther">Hugging Face Model *</Label>
617
+ <Combobox
618
+ options={huggingfaceModels}
 
619
  value={sourceModelOther}
620
+ onValueChange={setSourceModelOther}
621
+ placeholder="Search safetensors models"
622
+ searchPlaceholder="Type to search models..."
623
+ emptyMessage={hfModelQuery.trim() ? "No models found" : "Start typing to search"}
624
+ loading={loadingHfModels}
625
+ searchValue={hfModelQuery}
626
+ onSearchValueChange={setHfModelQuery}
627
+ disableLocalFilter
628
  />
629
  </div>
630
  )}
src/components/ui/combobox.tsx CHANGED
@@ -19,6 +19,9 @@ interface ComboboxProps {
19
  searchPlaceholder?: string;
20
  emptyMessage?: string;
21
  loading?: boolean;
 
 
 
22
  }
23
 
24
  export function Combobox({
@@ -29,11 +32,17 @@ export function Combobox({
29
  searchPlaceholder = "Search...",
30
  emptyMessage = "No results found.",
31
  loading = false,
 
 
 
32
  }: ComboboxProps) {
33
  const [open, setOpen] = React.useState(false);
34
- const [search, setSearch] = React.useState("");
 
 
35
 
36
  const filteredOptions = React.useMemo(() => {
 
37
  if (!search) return options;
38
  const lower = search.toLowerCase();
39
  return options.filter(
@@ -41,7 +50,7 @@ export function Combobox({
41
  option.name.toLowerCase().includes(lower) ||
42
  option.id.toLowerCase().includes(lower)
43
  );
44
- }, [options, search]);
45
 
46
  const selectedOption = options.find((opt) => opt.id === value);
47
 
@@ -65,7 +74,14 @@ export function Combobox({
65
  <Input
66
  placeholder={searchPlaceholder}
67
  value={search}
68
- onChange={(e) => setSearch(e.target.value)}
 
 
 
 
 
 
 
69
  className="h-9"
70
  />
71
  </div>
@@ -86,7 +102,11 @@ export function Combobox({
86
  onClick={() => {
87
  onValueChange(option.id === value ? "" : option.id);
88
  setOpen(false);
89
- setSearch("");
 
 
 
 
90
  }}
91
  className={cn(
92
  "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
 
19
  searchPlaceholder?: string;
20
  emptyMessage?: string;
21
  loading?: boolean;
22
+ searchValue?: string;
23
+ onSearchValueChange?: (value: string) => void;
24
+ disableLocalFilter?: boolean;
25
  }
26
 
27
  export function Combobox({
 
32
  searchPlaceholder = "Search...",
33
  emptyMessage = "No results found.",
34
  loading = false,
35
+ searchValue,
36
+ onSearchValueChange,
37
+ disableLocalFilter = false,
38
  }: ComboboxProps) {
39
  const [open, setOpen] = React.useState(false);
40
+ const [internalSearch, setInternalSearch] = React.useState("");
41
+
42
+ const search = searchValue ?? internalSearch;
43
 
44
  const filteredOptions = React.useMemo(() => {
45
+ if (disableLocalFilter) return options;
46
  if (!search) return options;
47
  const lower = search.toLowerCase();
48
  return options.filter(
 
50
  option.name.toLowerCase().includes(lower) ||
51
  option.id.toLowerCase().includes(lower)
52
  );
53
+ }, [disableLocalFilter, options, search]);
54
 
55
  const selectedOption = options.find((opt) => opt.id === value);
56
 
 
74
  <Input
75
  placeholder={searchPlaceholder}
76
  value={search}
77
+ onChange={(e) => {
78
+ const next = e.target.value;
79
+ if (onSearchValueChange) {
80
+ onSearchValueChange(next);
81
+ } else {
82
+ setInternalSearch(next);
83
+ }
84
+ }}
85
  className="h-9"
86
  />
87
  </div>
 
102
  onClick={() => {
103
  onValueChange(option.id === value ? "" : option.id);
104
  setOpen(false);
105
+ if (onSearchValueChange) {
106
+ onSearchValueChange("");
107
+ } else {
108
+ setInternalSearch("");
109
+ }
110
  }}
111
  className={cn(
112
  "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
src/lib/store.ts CHANGED
@@ -1,6 +1,4 @@
1
- import { v4 as uuidv4 } from "uuid";
2
- import fs from "fs";
3
- import path from "path";
4
 
5
  export interface DistillationRequest {
6
  id: string;
@@ -53,357 +51,428 @@ interface Store {
53
  threads: Record<string, DiscussionThread>;
54
  }
55
 
56
- const DATA_DIR = process.env.NODE_ENV === "production" ? "/data" : "./data";
57
- const DATA_FILE = path.join(DATA_DIR, "requests.json");
 
58
 
59
- function ensureDataDir() {
60
- if (!fs.existsSync(DATA_DIR)) {
61
- fs.mkdirSync(DATA_DIR, { recursive: true });
62
- }
 
 
 
 
 
 
 
 
63
  }
64
 
65
- function loadStore(): Store {
66
- ensureDataDir();
67
- try {
68
- if (fs.existsSync(DATA_FILE)) {
69
- const data = fs.readFileSync(DATA_FILE, "utf-8");
70
- const parsed = JSON.parse(data) as Partial<Store>;
71
- const parsedThreads = (parsed as any).threads;
72
- const threads: Record<string, DiscussionThread> =
73
- parsedThreads && typeof parsedThreads === "object" && !Array.isArray(parsedThreads)
74
- ? Object.fromEntries(
75
- Object.entries(parsedThreads as Record<string, any>).map(([key, t]) => {
76
- const requestType: RequestType = t?.requestType === "dataset" ? "dataset" : "distillation";
77
- const thread: DiscussionThread = {
78
- key: String(t?.key ?? key),
79
- requestType,
80
- requestId: String(t?.requestId ?? ""),
81
- comments: Array.isArray(t?.comments)
82
- ? t.comments.map((c: any) => ({
83
- id: String(c?.id ?? uuidv4()),
84
- body: String(c?.body ?? ""),
85
- author: String(c?.author ?? (c?.role === "user" ? "Anonymous" : "TeichAI")),
86
- role: c?.role === "user" ? "user" : "admin",
87
- ownerId: String(c?.ownerId ?? ""),
88
- createdAt: String(c?.createdAt ?? new Date().toISOString()),
89
- editedAt: c?.editedAt ? String(c.editedAt) : undefined,
90
- }))
91
- : [],
92
- };
93
- return [key, thread] as const;
94
- })
95
- )
96
- : {};
97
- const store: Store = {
98
- distillationRequests: Array.isArray(parsed.distillationRequests)
99
- ? (parsed.distillationRequests as any[]).map((r) => ({
100
- id: String(r.id ?? uuidv4()),
101
- sourceDataset: String(r.sourceDataset ?? r.teacherModel ?? ""),
102
- studentModel: String(r.studentModel ?? ""),
103
- additionalNotes: String(r.additionalNotes ?? ""),
104
- upvotes: typeof r.upvotes === "number" ? r.upvotes : 0,
105
- votedIps: Array.isArray(r.votedIps) ? r.votedIps.map(String) : [],
106
- ownerId: String(r.ownerId ?? ""),
107
- createdAt: String(r.createdAt ?? new Date().toISOString()),
108
- status: (r.status === "in_progress" || r.status === "completed") ? r.status : "pending",
109
- }))
110
- : [],
111
- datasetRequests: Array.isArray(parsed.datasetRequests)
112
- ? (parsed.datasetRequests as any[]).map((r) => ({
113
- id: String(r.id ?? uuidv4()),
114
- sourceModel: String(r.sourceModel ?? ""),
115
- datasetSize: String(r.datasetSize ?? "250x"),
116
- reasoningDepth: String(r.reasoningDepth ?? "high"),
117
- topics: Array.isArray(r.topics) ? r.topics.map(String) : [],
118
- additionalNotes: String(r.additionalNotes ?? ""),
119
- upvotes: typeof r.upvotes === "number" ? r.upvotes : 0,
120
- votedIps: Array.isArray(r.votedIps) ? r.votedIps.map(String) : [],
121
- ownerId: String(r.ownerId ?? ""),
122
- createdAt: String(r.createdAt ?? new Date().toISOString()),
123
- status: (r.status === "in_progress" || r.status === "completed") ? r.status : "pending",
124
- }))
125
- : [],
126
- threads,
127
- };
128
- return store;
129
- }
130
- } catch (error) {
131
- console.error("Error loading store:", error);
132
- }
133
- return { distillationRequests: [], datasetRequests: [], threads: {} };
134
  }
135
 
136
- function saveStore(store: Store) {
137
- ensureDataDir();
138
- fs.writeFileSync(DATA_FILE, JSON.stringify(store, null, 2));
 
 
 
 
 
 
 
139
  }
140
 
141
- export function getDistillationRequests(): DistillationRequest[] {
142
- const store = loadStore();
143
- return store.distillationRequests.sort((a, b) => b.upvotes - a.upvotes);
 
 
 
 
 
 
 
 
 
 
144
  }
145
 
146
- export function getDatasetRequests(): DatasetRequest[] {
147
- const store = loadStore();
148
- return store.datasetRequests.sort((a, b) => b.upvotes - a.upvotes);
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
- export function getRequest(type: RequestType, id: string): DistillationRequest | DatasetRequest | null {
152
- const store = loadStore();
153
- if (type === "distillation") {
154
- return store.distillationRequests.find((r) => r.id === id) ?? null;
 
 
 
 
 
 
 
 
155
  }
156
- return store.datasetRequests.find((r) => r.id === id) ?? null;
 
 
157
  }
158
 
159
- export function updateDistillationRequest(
160
  id: string,
161
  updates: Partial<Pick<DistillationRequest, "sourceDataset" | "studentModel" | "additionalNotes">>
162
- ): DistillationRequest | null {
163
- const store = loadStore();
164
- const request = store.distillationRequests.find((r) => r.id === id);
165
- if (!request) return null;
166
- if (typeof updates.sourceDataset === "string") request.sourceDataset = updates.sourceDataset;
167
- if (typeof updates.studentModel === "string") request.studentModel = updates.studentModel;
168
- if (typeof updates.additionalNotes === "string") request.additionalNotes = updates.additionalNotes;
169
- saveStore(store);
170
- return request;
 
 
 
 
 
 
 
 
 
 
171
  }
172
 
173
- export function updateDatasetRequest(
174
  id: string,
175
  updates: Partial<Pick<DatasetRequest, "sourceModel" | "datasetSize" | "reasoningDepth" | "topics" | "additionalNotes">>
176
- ): DatasetRequest | null {
177
- const store = loadStore();
178
- const request = store.datasetRequests.find((r) => r.id === id);
179
- if (!request) return null;
180
- if (typeof updates.sourceModel === "string") request.sourceModel = updates.sourceModel;
181
- if (typeof updates.datasetSize === "string") request.datasetSize = updates.datasetSize;
182
- if (typeof updates.reasoningDepth === "string") request.reasoningDepth = updates.reasoningDepth;
183
- if (Array.isArray(updates.topics)) request.topics = updates.topics.map(String);
184
- if (typeof updates.additionalNotes === "string") request.additionalNotes = updates.additionalNotes;
185
- saveStore(store);
186
- return request;
 
 
 
 
 
 
 
 
 
 
187
  }
188
 
189
- export function updateRequestStatus(
190
  type: RequestType,
191
  id: string,
192
  status: "pending" | "in_progress" | "completed"
193
- ): boolean {
194
- const store = loadStore();
195
- if (type === "distillation") {
196
- const request = store.distillationRequests.find((r) => r.id === id);
197
- if (!request) return false;
198
- request.status = status;
199
- saveStore(store);
200
- return true;
 
 
 
 
201
  }
202
 
203
- const request = store.datasetRequests.find((r) => r.id === id);
204
- if (!request) return false;
205
- request.status = status;
206
- saveStore(store);
207
- return true;
208
  }
209
 
210
- export function deleteRequest(type: RequestType, id: string): boolean {
211
- const store = loadStore();
212
- if (type === "distillation") {
213
- const before = store.distillationRequests.length;
214
- store.distillationRequests = store.distillationRequests.filter((r) => r.id !== id);
215
- if (store.distillationRequests.length === before) return false;
216
- } else {
217
- const before = store.datasetRequests.length;
218
- store.datasetRequests = store.datasetRequests.filter((r) => r.id !== id);
219
- if (store.datasetRequests.length === before) return false;
220
- }
221
 
222
- const key = `${type}:${id}`;
223
- if (store.threads[key]) {
224
- delete store.threads[key];
 
 
 
 
 
 
 
 
225
  }
226
- saveStore(store);
227
- return true;
228
  }
229
 
230
- export function getThread(type: RequestType, id: string): DiscussionThread {
231
- const store = loadStore();
232
  const key = `${type}:${id}`;
233
- const existing = store.threads[key];
234
- if (existing && existing.requestType === type && existing.requestId === id) {
235
- return existing;
 
 
 
 
 
 
 
 
236
  }
237
- const thread: DiscussionThread = {
 
238
  key,
239
  requestType: type,
240
  requestId: id,
241
- comments: [],
242
  };
243
- store.threads[key] = thread;
244
- saveStore(store);
245
- return thread;
246
  }
247
 
248
- export function addAdminComment(type: RequestType, id: string, body: string, ownerId: string): DiscussionComment {
249
- const store = loadStore();
250
- const key = `${type}:${id}`;
251
- const thread = store.threads[key] ?? {
252
- key,
253
- requestType: type,
254
- requestId: id,
255
- comments: [],
256
- };
257
- const comment: DiscussionComment = {
258
- id: uuidv4(),
259
- body,
260
- author: "TeichAI",
261
- role: "admin",
262
- ownerId,
263
- createdAt: new Date().toISOString(),
264
- };
265
- thread.comments.push(comment);
266
- store.threads[key] = thread;
267
- saveStore(store);
268
- return comment;
 
 
 
 
269
  }
270
 
271
- export function addUserComment(
272
  type: RequestType,
273
  id: string,
274
  body: string,
275
  author: string | undefined,
276
  ownerId: string
277
- ): DiscussionComment {
278
- const store = loadStore();
279
- const key = `${type}:${id}`;
280
- const thread = store.threads[key] ?? {
281
- key,
282
- requestType: type,
283
- requestId: id,
284
- comments: [],
285
- };
286
- const comment: DiscussionComment = {
287
- id: uuidv4(),
288
- body,
289
- author: author?.trim() ? author.trim() : "Anonymous",
290
- role: "user",
291
- ownerId,
292
- createdAt: new Date().toISOString(),
293
- };
294
- thread.comments.push(comment);
295
- store.threads[key] = thread;
296
- saveStore(store);
297
- return comment;
 
 
 
 
298
  }
299
 
300
- export function updateComment(
301
  type: RequestType,
302
  requestId: string,
303
  commentId: string,
304
  body: string
305
- ): DiscussionComment | null {
306
- const store = loadStore();
307
- const key = `${type}:${requestId}`;
308
- const thread = store.threads[key];
309
- if (!thread) return null;
310
- const comment = thread.comments.find((c) => c.id === commentId);
311
- if (!comment) return null;
312
- comment.body = body;
313
- comment.editedAt = new Date().toISOString();
314
- saveStore(store);
315
- return comment;
 
 
 
 
 
 
 
 
316
  }
317
 
318
- export function deleteComment(
319
  type: RequestType,
320
  requestId: string,
321
  commentId: string
322
- ): boolean {
323
- const store = loadStore();
324
- const key = `${type}:${requestId}`;
325
- const thread = store.threads[key];
326
- if (!thread) return false;
327
- const before = thread.comments.length;
328
- thread.comments = thread.comments.filter((c) => c.id !== commentId);
329
- if (thread.comments.length === before) return false;
330
- store.threads[key] = thread;
331
- saveStore(store);
332
- return true;
 
 
 
 
 
 
 
 
333
  }
334
 
335
- export function addDistillationRequest(
336
  request: Omit<DistillationRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status">
337
- ): DistillationRequest {
338
- const store = loadStore();
339
- const newRequest: DistillationRequest = {
340
- ...request,
341
- id: uuidv4(),
342
- upvotes: 0,
343
- votedIps: [],
344
- createdAt: new Date().toISOString(),
345
- status: "pending",
346
- };
347
- store.distillationRequests.push(newRequest);
348
- saveStore(store);
349
- return newRequest;
 
 
 
 
 
 
 
 
350
  }
351
 
352
- export function addDatasetRequest(
353
  request: Omit<DatasetRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status">
354
- ): DatasetRequest {
355
- const store = loadStore();
356
- const newRequest: DatasetRequest = {
357
- ...request,
358
- id: uuidv4(),
359
- upvotes: 0,
360
- votedIps: [],
361
- createdAt: new Date().toISOString(),
362
- status: "pending",
363
- };
364
- store.datasetRequests.push(newRequest);
365
- saveStore(store);
366
- return newRequest;
 
 
 
 
 
 
 
 
 
 
367
  }
368
 
369
- export function upvoteDistillation(
370
  id: string,
371
  ip: string
372
- ): { success: boolean; upvotes: number; action?: "upvoted" | "unvoted" } {
373
- const store = loadStore();
374
- const request = store.distillationRequests.find((r) => r.id === id);
375
- if (!request) {
376
- return { success: false, upvotes: 0 };
 
 
 
377
  }
378
- if (request.votedIps.includes(ip)) {
379
- request.votedIps = request.votedIps.filter((v) => v !== ip);
380
- request.upvotes = Math.max(0, request.upvotes - 1);
381
- saveStore(store);
382
- return { success: true, upvotes: request.upvotes, action: "unvoted" };
383
  }
384
- request.upvotes += 1;
385
- request.votedIps.push(ip);
386
- saveStore(store);
387
- return { success: true, upvotes: request.upvotes, action: "upvoted" };
 
 
388
  }
389
 
390
- export function upvoteDataset(
391
  id: string,
392
  ip: string
393
- ): { success: boolean; upvotes: number; action?: "upvoted" | "unvoted" } {
394
- const store = loadStore();
395
- const request = store.datasetRequests.find((r) => r.id === id);
396
- if (!request) {
397
- return { success: false, upvotes: 0 };
 
 
 
398
  }
399
- if (request.votedIps.includes(ip)) {
400
- request.votedIps = request.votedIps.filter((v) => v !== ip);
401
- request.upvotes = Math.max(0, request.upvotes - 1);
402
- saveStore(store);
403
- return { success: true, upvotes: request.upvotes, action: "unvoted" };
404
  }
405
- request.upvotes += 1;
406
- request.votedIps.push(ip);
407
- saveStore(store);
408
- return { success: true, upvotes: request.upvotes, action: "upvoted" };
 
 
409
  }
 
1
+ import { supabaseRest } from "@/lib/supabaseRest";
 
 
2
 
3
  export interface DistillationRequest {
4
  id: string;
 
51
  threads: Record<string, DiscussionThread>;
52
  }
53
 
54
+ function normalizeStatus(value: unknown): "pending" | "in_progress" | "completed" {
55
+ return value === "in_progress" || value === "completed" ? value : "pending";
56
+ }
57
 
58
+ function mapDistillationRow(row: any): DistillationRequest {
59
+ return {
60
+ id: String(row?.id ?? ""),
61
+ sourceDataset: String(row?.source_dataset ?? ""),
62
+ studentModel: String(row?.student_model ?? ""),
63
+ additionalNotes: String(row?.additional_notes ?? ""),
64
+ upvotes: typeof row?.upvotes === "number" ? row.upvotes : 0,
65
+ votedIps: Array.isArray(row?.voted_ips) ? row.voted_ips.map(String) : [],
66
+ ownerId: String(row?.owner_id ?? ""),
67
+ createdAt: String(row?.created_at ?? new Date().toISOString()),
68
+ status: normalizeStatus(row?.status),
69
+ };
70
  }
71
 
72
+ function mapDatasetRow(row: any): DatasetRequest {
73
+ return {
74
+ id: String(row?.id ?? ""),
75
+ sourceModel: String(row?.source_model ?? ""),
76
+ datasetSize: String(row?.dataset_size ?? "250x"),
77
+ reasoningDepth: String(row?.reasoning_depth ?? "high"),
78
+ topics: Array.isArray(row?.topics) ? row.topics.map(String) : [],
79
+ additionalNotes: String(row?.additional_notes ?? ""),
80
+ upvotes: typeof row?.upvotes === "number" ? row.upvotes : 0,
81
+ votedIps: Array.isArray(row?.voted_ips) ? row.voted_ips.map(String) : [],
82
+ ownerId: String(row?.owner_id ?? ""),
83
+ createdAt: String(row?.created_at ?? new Date().toISOString()),
84
+ status: normalizeStatus(row?.status),
85
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
 
88
+ function mapCommentRow(row: any): DiscussionComment {
89
+ return {
90
+ id: String(row?.id ?? ""),
91
+ body: String(row?.body ?? ""),
92
+ author: String(row?.author ?? (row?.role === "user" ? "Anonymous" : "TeichAI")),
93
+ role: row?.role === "user" ? "user" : "admin",
94
+ ownerId: String(row?.owner_id ?? ""),
95
+ createdAt: String(row?.created_at ?? new Date().toISOString()),
96
+ editedAt: row?.edited_at ? String(row.edited_at) : undefined,
97
+ };
98
  }
99
 
100
+ export async function getDistillationRequests(): Promise<DistillationRequest[]> {
101
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/distillation_requests", {
102
+ query: {
103
+ select: "*",
104
+ order: "upvotes.desc,created_at.desc",
105
+ },
106
+ });
107
+
108
+ if (error) {
109
+ throw new Error(error.message);
110
+ }
111
+
112
+ return Array.isArray(data) ? data.map(mapDistillationRow) : [];
113
  }
114
 
115
+ export async function getDatasetRequests(): Promise<DatasetRequest[]> {
116
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/dataset_requests", {
117
+ query: {
118
+ select: "*",
119
+ order: "upvotes.desc,created_at.desc",
120
+ },
121
+ });
122
+
123
+ if (error) {
124
+ throw new Error(error.message);
125
+ }
126
+
127
+ return Array.isArray(data) ? data.map(mapDatasetRow) : [];
128
  }
129
 
130
+ export async function getRequest(type: RequestType, id: string): Promise<DistillationRequest | DatasetRequest | null> {
131
+ const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
132
+ const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
133
+ query: {
134
+ select: "*",
135
+ id: `eq.${id}`,
136
+ },
137
+ acceptObject: true,
138
+ });
139
+
140
+ if (error) {
141
+ throw new Error(error.message);
142
  }
143
+
144
+ if (!data) return null;
145
+ return type === "distillation" ? mapDistillationRow(data) : mapDatasetRow(data);
146
  }
147
 
148
+ export async function updateDistillationRequest(
149
  id: string,
150
  updates: Partial<Pick<DistillationRequest, "sourceDataset" | "studentModel" | "additionalNotes">>
151
+ ): Promise<DistillationRequest | null> {
152
+ const body: Record<string, any> = {};
153
+ if (typeof updates.sourceDataset === "string") body.source_dataset = updates.sourceDataset;
154
+ if (typeof updates.studentModel === "string") body.student_model = updates.studentModel;
155
+ if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
156
+
157
+ const { data, error } = await supabaseRest<any>("/rest/v1/distillation_requests", {
158
+ method: "PATCH",
159
+ body: JSON.stringify(body),
160
+ query: { select: "*", id: `eq.${id}` },
161
+ preferReturn: "representation",
162
+ acceptObject: true,
163
+ });
164
+
165
+ if (error) {
166
+ throw new Error(error.message);
167
+ }
168
+
169
+ return data ? mapDistillationRow(data) : null;
170
  }
171
 
172
+ export async function updateDatasetRequest(
173
  id: string,
174
  updates: Partial<Pick<DatasetRequest, "sourceModel" | "datasetSize" | "reasoningDepth" | "topics" | "additionalNotes">>
175
+ ): Promise<DatasetRequest | null> {
176
+ const body: Record<string, any> = {};
177
+ if (typeof updates.sourceModel === "string") body.source_model = updates.sourceModel;
178
+ if (typeof updates.datasetSize === "string") body.dataset_size = updates.datasetSize;
179
+ if (typeof updates.reasoningDepth === "string") body.reasoning_depth = updates.reasoningDepth;
180
+ if (Array.isArray(updates.topics)) body.topics = updates.topics.map(String);
181
+ if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes;
182
+
183
+ const { data, error } = await supabaseRest<any>("/rest/v1/dataset_requests", {
184
+ method: "PATCH",
185
+ body: JSON.stringify(body),
186
+ query: { select: "*", id: `eq.${id}` },
187
+ preferReturn: "representation",
188
+ acceptObject: true,
189
+ });
190
+
191
+ if (error) {
192
+ throw new Error(error.message);
193
+ }
194
+
195
+ return data ? mapDatasetRow(data) : null;
196
  }
197
 
198
+ export async function updateRequestStatus(
199
  type: RequestType,
200
  id: string,
201
  status: "pending" | "in_progress" | "completed"
202
+ ): Promise<boolean> {
203
+ const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
204
+ const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
205
+ method: "PATCH",
206
+ body: JSON.stringify({ status }),
207
+ query: { select: "id", id: `eq.${id}` },
208
+ preferReturn: "representation",
209
+ acceptObject: true,
210
+ });
211
+
212
+ if (error) {
213
+ throw new Error(error.message);
214
  }
215
 
216
+ return Boolean(data?.id);
 
 
 
 
217
  }
218
 
219
+ export async function deleteRequest(type: RequestType, id: string): Promise<boolean> {
220
+ await supabaseRest("/rest/v1/request_comments", {
221
+ method: "DELETE",
222
+ query: {
223
+ request_type: `eq.${type}`,
224
+ request_id: `eq.${id}`,
225
+ },
226
+ });
 
 
 
227
 
228
+ const table = type === "distillation" ? "distillation_requests" : "dataset_requests";
229
+ const { data, error } = await supabaseRest<any>(`/rest/v1/${table}`, {
230
+ method: "DELETE",
231
+ body: null,
232
+ query: { select: "id", id: `eq.${id}` },
233
+ preferReturn: "representation",
234
+ acceptObject: true,
235
+ });
236
+
237
+ if (error) {
238
+ throw new Error(error.message);
239
  }
240
+
241
+ return Boolean(data?.id);
242
  }
243
 
244
+ export async function getThread(type: RequestType, id: string): Promise<DiscussionThread> {
 
245
  const key = `${type}:${id}`;
246
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", {
247
+ query: {
248
+ select: "*",
249
+ request_type: `eq.${type}`,
250
+ request_id: `eq.${id}`,
251
+ order: "created_at.asc",
252
+ },
253
+ });
254
+
255
+ if (error) {
256
+ throw new Error(error.message);
257
  }
258
+
259
+ return {
260
  key,
261
  requestType: type,
262
  requestId: id,
263
+ comments: Array.isArray(data) ? data.map(mapCommentRow) : [],
264
  };
 
 
 
265
  }
266
 
267
+ export async function addAdminComment(type: RequestType, id: string, body: string, ownerId: string): Promise<DiscussionComment> {
268
+ const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
269
+ method: "POST",
270
+ body: JSON.stringify({
271
+ request_type: type,
272
+ request_id: id,
273
+ body,
274
+ author: "TeichAI",
275
+ role: "admin",
276
+ owner_id: ownerId,
277
+ }),
278
+ query: { select: "*" },
279
+ preferReturn: "representation",
280
+ acceptObject: true,
281
+ });
282
+
283
+ if (error) {
284
+ throw new Error(error.message);
285
+ }
286
+
287
+ if (!data) {
288
+ throw new Error("Failed to create comment");
289
+ }
290
+
291
+ return mapCommentRow(data);
292
  }
293
 
294
+ export async function addUserComment(
295
  type: RequestType,
296
  id: string,
297
  body: string,
298
  author: string | undefined,
299
  ownerId: string
300
+ ): Promise<DiscussionComment> {
301
+ const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
302
+ method: "POST",
303
+ body: JSON.stringify({
304
+ request_type: type,
305
+ request_id: id,
306
+ body,
307
+ author: author?.trim() ? author.trim() : "Anonymous",
308
+ role: "user",
309
+ owner_id: ownerId,
310
+ }),
311
+ query: { select: "*" },
312
+ preferReturn: "representation",
313
+ acceptObject: true,
314
+ });
315
+
316
+ if (error) {
317
+ throw new Error(error.message);
318
+ }
319
+
320
+ if (!data) {
321
+ throw new Error("Failed to create comment");
322
+ }
323
+
324
+ return mapCommentRow(data);
325
  }
326
 
327
+ export async function updateComment(
328
  type: RequestType,
329
  requestId: string,
330
  commentId: string,
331
  body: string
332
+ ): Promise<DiscussionComment | null> {
333
+ const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
334
+ method: "PATCH",
335
+ body: JSON.stringify({ body, edited_at: new Date().toISOString() }),
336
+ query: {
337
+ select: "*",
338
+ id: `eq.${commentId}`,
339
+ request_type: `eq.${type}`,
340
+ request_id: `eq.${requestId}`,
341
+ },
342
+ preferReturn: "representation",
343
+ acceptObject: true,
344
+ });
345
+
346
+ if (error) {
347
+ throw new Error(error.message);
348
+ }
349
+
350
+ return data ? mapCommentRow(data) : null;
351
  }
352
 
353
+ export async function deleteComment(
354
  type: RequestType,
355
  requestId: string,
356
  commentId: string
357
+ ): Promise<boolean> {
358
+ const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", {
359
+ method: "DELETE",
360
+ body: null,
361
+ query: {
362
+ select: "id",
363
+ id: `eq.${commentId}`,
364
+ request_type: `eq.${type}`,
365
+ request_id: `eq.${requestId}`,
366
+ },
367
+ preferReturn: "representation",
368
+ acceptObject: true,
369
+ });
370
+
371
+ if (error) {
372
+ throw new Error(error.message);
373
+ }
374
+
375
+ return Boolean(data?.id);
376
  }
377
 
378
+ export async function addDistillationRequest(
379
  request: Omit<DistillationRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status">
380
+ ): Promise<DistillationRequest> {
381
+ const { data, error } = await supabaseRest<any>("/rest/v1/distillation_requests", {
382
+ method: "POST",
383
+ body: JSON.stringify({
384
+ source_dataset: request.sourceDataset,
385
+ student_model: request.studentModel,
386
+ additional_notes: request.additionalNotes,
387
+ owner_id: request.ownerId,
388
+ }),
389
+ query: { select: "*" },
390
+ preferReturn: "representation",
391
+ acceptObject: true,
392
+ });
393
+
394
+ if (error) {
395
+ throw new Error(error.message);
396
+ }
397
+ if (!data) {
398
+ throw new Error("Failed to create request");
399
+ }
400
+ return mapDistillationRow(data);
401
  }
402
 
403
+ export async function addDatasetRequest(
404
  request: Omit<DatasetRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status">
405
+ ): Promise<DatasetRequest> {
406
+ const { data, error } = await supabaseRest<any>("/rest/v1/dataset_requests", {
407
+ method: "POST",
408
+ body: JSON.stringify({
409
+ source_model: request.sourceModel,
410
+ dataset_size: request.datasetSize,
411
+ reasoning_depth: request.reasoningDepth,
412
+ topics: request.topics,
413
+ additional_notes: request.additionalNotes,
414
+ owner_id: request.ownerId,
415
+ }),
416
+ query: { select: "*" },
417
+ preferReturn: "representation",
418
+ acceptObject: true,
419
+ });
420
+
421
+ if (error) {
422
+ throw new Error(error.message);
423
+ }
424
+ if (!data) {
425
+ throw new Error("Failed to create request");
426
+ }
427
+ return mapDatasetRow(data);
428
  }
429
 
430
+ export async function upvoteDistillation(
431
  id: string,
432
  ip: string
433
+ ): Promise<{ success: boolean; upvotes: number; action?: "upvoted" | "unvoted" }> {
434
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/rpc/toggle_upvote_distillation", {
435
+ method: "POST",
436
+ body: JSON.stringify({ request_id: id, voter_ip: ip }),
437
+ });
438
+
439
+ if (error) {
440
+ throw new Error(error.message);
441
  }
442
+
443
+ const row = Array.isArray(data) ? data[0] : null;
444
+ if (!row) {
445
+ return { success: false, upvotes: 0 };
 
446
  }
447
+
448
+ return {
449
+ success: Boolean(row.success),
450
+ upvotes: typeof row.upvotes === "number" ? row.upvotes : 0,
451
+ action: row.action === "upvoted" || row.action === "unvoted" ? row.action : undefined,
452
+ };
453
  }
454
 
455
+ export async function upvoteDataset(
456
  id: string,
457
  ip: string
458
+ ): Promise<{ success: boolean; upvotes: number; action?: "upvoted" | "unvoted" }> {
459
+ const { data, error } = await supabaseRest<any[]>("/rest/v1/rpc/toggle_upvote_dataset", {
460
+ method: "POST",
461
+ body: JSON.stringify({ request_id: id, voter_ip: ip }),
462
+ });
463
+
464
+ if (error) {
465
+ throw new Error(error.message);
466
  }
467
+
468
+ const row = Array.isArray(data) ? data[0] : null;
469
+ if (!row) {
470
+ return { success: false, upvotes: 0 };
 
471
  }
472
+
473
+ return {
474
+ success: Boolean(row.success),
475
+ upvotes: typeof row.upvotes === "number" ? row.upvotes : 0,
476
+ action: row.action === "upvoted" || row.action === "unvoted" ? row.action : undefined,
477
+ };
478
  }