| import { supabaseRest } from "@/lib/supabaseRest"; | |
| export interface DistillationRequest { | |
| id: string; | |
| sourceDataset: string; | |
| studentModel: string; | |
| submitterName?: string; | |
| additionalNotes: string; | |
| upvotes: number; | |
| votedIps: string[]; | |
| ownerId: string; | |
| createdAt: string; | |
| status: "pending" | "in_progress" | "completed"; | |
| } | |
| export interface DatasetRequest { | |
| id: string; | |
| sourceModel: string; | |
| submitterName?: string; | |
| datasetSize: string; | |
| reasoningDepth: string; | |
| topics: string[]; | |
| additionalNotes: string; | |
| upvotes: number; | |
| votedIps: string[]; | |
| ownerId: string; | |
| createdAt: string; | |
| status: "pending" | "in_progress" | "completed"; | |
| } | |
| export type RequestType = "distillation" | "dataset"; | |
| export interface DiscussionComment { | |
| id: string; | |
| body: string; | |
| author: string; | |
| role: "admin" | "user"; | |
| ownerId: string; | |
| createdAt: string; | |
| editedAt?: string; | |
| } | |
| export interface DiscussionThread { | |
| key: string; | |
| requestType: RequestType; | |
| requestId: string; | |
| comments: DiscussionComment[]; | |
| } | |
| interface Store { | |
| distillationRequests: DistillationRequest[]; | |
| datasetRequests: DatasetRequest[]; | |
| threads: Record<string, DiscussionThread>; | |
| } | |
| function normalizeStatus(value: unknown): "pending" | "in_progress" | "completed" { | |
| return value === "in_progress" || value === "completed" ? value : "pending"; | |
| } | |
| function mapDistillationRow(row: any): DistillationRequest { | |
| const submitterNameRaw = typeof row?.submitter_name === "string" ? row.submitter_name.trim() : ""; | |
| return { | |
| id: String(row?.id ?? ""), | |
| sourceDataset: String(row?.source_dataset ?? ""), | |
| studentModel: String(row?.student_model ?? ""), | |
| submitterName: submitterNameRaw ? submitterNameRaw : undefined, | |
| additionalNotes: String(row?.additional_notes ?? ""), | |
| upvotes: typeof row?.upvotes === "number" ? row.upvotes : 0, | |
| votedIps: Array.isArray(row?.voted_ips) ? row.voted_ips.map(String) : [], | |
| ownerId: String(row?.owner_id ?? ""), | |
| createdAt: String(row?.created_at ?? new Date().toISOString()), | |
| status: normalizeStatus(row?.status), | |
| }; | |
| } | |
| function mapDatasetRow(row: any): DatasetRequest { | |
| const submitterNameRaw = typeof row?.submitter_name === "string" ? row.submitter_name.trim() : ""; | |
| return { | |
| id: String(row?.id ?? ""), | |
| sourceModel: String(row?.source_model ?? ""), | |
| submitterName: submitterNameRaw ? submitterNameRaw : undefined, | |
| datasetSize: String(row?.dataset_size ?? "250x"), | |
| reasoningDepth: String(row?.reasoning_depth ?? "high"), | |
| topics: Array.isArray(row?.topics) ? row.topics.map(String) : [], | |
| additionalNotes: String(row?.additional_notes ?? ""), | |
| upvotes: typeof row?.upvotes === "number" ? row.upvotes : 0, | |
| votedIps: Array.isArray(row?.voted_ips) ? row.voted_ips.map(String) : [], | |
| ownerId: String(row?.owner_id ?? ""), | |
| createdAt: String(row?.created_at ?? new Date().toISOString()), | |
| status: normalizeStatus(row?.status), | |
| }; | |
| } | |
| function mapCommentRow(row: any): DiscussionComment { | |
| return { | |
| id: String(row?.id ?? ""), | |
| body: String(row?.body ?? ""), | |
| author: String(row?.author ?? (row?.role === "user" ? "Anonymous" : "TeichAI")), | |
| role: row?.role === "user" ? "user" : "admin", | |
| ownerId: String(row?.owner_id ?? ""), | |
| createdAt: String(row?.created_at ?? new Date().toISOString()), | |
| editedAt: row?.edited_at ? String(row.edited_at) : undefined, | |
| }; | |
| } | |
| export async function getDistillationRequests(): Promise<DistillationRequest[]> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/distillation_requests", { | |
| query: { | |
| select: "*", | |
| order: "upvotes.desc,created_at.desc", | |
| }, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return Array.isArray(data) ? data.map(mapDistillationRow) : []; | |
| } | |
| export async function getDatasetRequests(): Promise<DatasetRequest[]> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/dataset_requests", { | |
| query: { | |
| select: "*", | |
| order: "upvotes.desc,created_at.desc", | |
| }, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return Array.isArray(data) ? data.map(mapDatasetRow) : []; | |
| } | |
| export async function getRequest(type: RequestType, id: string): Promise<DistillationRequest | DatasetRequest | null> { | |
| const table = type === "distillation" ? "distillation_requests" : "dataset_requests"; | |
| const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, { | |
| query: { | |
| select: "*", | |
| id: `eq.${id}`, | |
| }, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| if (!row) return null; | |
| return type === "distillation" ? mapDistillationRow(row) : mapDatasetRow(row); | |
| } | |
| export async function updateDistillationRequest( | |
| id: string, | |
| updates: Partial<Pick<DistillationRequest, "sourceDataset" | "studentModel" | "additionalNotes">> | |
| ): Promise<DistillationRequest | null> { | |
| const body: Record<string, any> = {}; | |
| if (typeof updates.sourceDataset === "string") body.source_dataset = updates.sourceDataset; | |
| if (typeof updates.studentModel === "string") body.student_model = updates.studentModel; | |
| if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes; | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/distillation_requests", { | |
| method: "PATCH", | |
| body: JSON.stringify(body), | |
| query: { select: "*", id: `eq.${id}` }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| return row ? mapDistillationRow(row) : null; | |
| } | |
| export async function updateDatasetRequest( | |
| id: string, | |
| updates: Partial<Pick<DatasetRequest, "sourceModel" | "datasetSize" | "reasoningDepth" | "topics" | "additionalNotes">> | |
| ): Promise<DatasetRequest | null> { | |
| const body: Record<string, any> = {}; | |
| if (typeof updates.sourceModel === "string") body.source_model = updates.sourceModel; | |
| if (typeof updates.datasetSize === "string") body.dataset_size = updates.datasetSize; | |
| if (typeof updates.reasoningDepth === "string") body.reasoning_depth = updates.reasoningDepth; | |
| if (Array.isArray(updates.topics)) body.topics = updates.topics.map(String); | |
| if (typeof updates.additionalNotes === "string") body.additional_notes = updates.additionalNotes; | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/dataset_requests", { | |
| method: "PATCH", | |
| body: JSON.stringify(body), | |
| query: { select: "*", id: `eq.${id}` }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| return row ? mapDatasetRow(row) : null; | |
| } | |
| export async function updateRequestStatus( | |
| type: RequestType, | |
| id: string, | |
| status: "pending" | "in_progress" | "completed" | |
| ): Promise<boolean> { | |
| const table = type === "distillation" ? "distillation_requests" : "dataset_requests"; | |
| const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, { | |
| method: "PATCH", | |
| body: JSON.stringify({ status }), | |
| query: { select: "id", id: `eq.${id}` }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return Array.isArray(data) ? Boolean(data[0]?.id) : false; | |
| } | |
| export async function deleteRequest(type: RequestType, id: string): Promise<boolean> { | |
| await supabaseRest("/rest/v1/request_comments", { | |
| method: "DELETE", | |
| query: { | |
| request_type: `eq.${type}`, | |
| request_id: `eq.${id}`, | |
| }, | |
| requireServiceRole: true, | |
| }); | |
| const table = type === "distillation" ? "distillation_requests" : "dataset_requests"; | |
| const { data, error } = await supabaseRest<any[]>(`/rest/v1/${table}`, { | |
| method: "DELETE", | |
| body: null, | |
| query: { select: "id", id: `eq.${id}` }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return Array.isArray(data) ? Boolean(data[0]?.id) : false; | |
| } | |
| export async function getThread(type: RequestType, id: string): Promise<DiscussionThread> { | |
| const key = `${type}:${id}`; | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", { | |
| query: { | |
| select: "*", | |
| request_type: `eq.${type}`, | |
| request_id: `eq.${id}`, | |
| order: "created_at.asc", | |
| }, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return { | |
| key, | |
| requestType: type, | |
| requestId: id, | |
| comments: Array.isArray(data) ? data.map(mapCommentRow) : [], | |
| }; | |
| } | |
| export async function addAdminComment(type: RequestType, id: string, body: string, ownerId: string): Promise<DiscussionComment> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| request_type: type, | |
| request_id: id, | |
| body, | |
| author: "TeichAI", | |
| role: "admin", | |
| owner_id: ownerId, | |
| }), | |
| query: { select: "*" }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| if (!row) { | |
| throw new Error("Failed to create comment"); | |
| } | |
| return mapCommentRow(row); | |
| } | |
| export async function addUserComment( | |
| type: RequestType, | |
| id: string, | |
| body: string, | |
| author: string | undefined, | |
| ownerId: string | |
| ): Promise<DiscussionComment> { | |
| const { data, error } = await supabaseRest<any>("/rest/v1/request_comments", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| request_type: type, | |
| request_id: id, | |
| body, | |
| author: author?.trim() ? author.trim() : "Anonymous", | |
| role: "user", | |
| owner_id: ownerId, | |
| }), | |
| query: { select: "*" }, | |
| preferReturn: "representation", | |
| acceptObject: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| if (!data) { | |
| throw new Error("Failed to create comment"); | |
| } | |
| return mapCommentRow(data); | |
| } | |
| export async function updateComment( | |
| type: RequestType, | |
| requestId: string, | |
| commentId: string, | |
| body: string | |
| ): Promise<DiscussionComment | null> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", { | |
| method: "PATCH", | |
| body: JSON.stringify({ body, edited_at: new Date().toISOString() }), | |
| query: { | |
| select: "*", | |
| id: `eq.${commentId}`, | |
| request_type: `eq.${type}`, | |
| request_id: `eq.${requestId}`, | |
| }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| return row ? mapCommentRow(row) : null; | |
| } | |
| export async function deleteComment( | |
| type: RequestType, | |
| requestId: string, | |
| commentId: string | |
| ): Promise<boolean> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/request_comments", { | |
| method: "DELETE", | |
| body: null, | |
| query: { | |
| select: "id", | |
| id: `eq.${commentId}`, | |
| request_type: `eq.${type}`, | |
| request_id: `eq.${requestId}`, | |
| }, | |
| preferReturn: "representation", | |
| requireServiceRole: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| return Array.isArray(data) ? Boolean(data[0]?.id) : false; | |
| } | |
| export async function addDistillationRequest( | |
| request: Omit<DistillationRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status"> | |
| ): Promise<DistillationRequest> { | |
| const { data, error } = await supabaseRest<any>("/rest/v1/distillation_requests", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| source_dataset: request.sourceDataset, | |
| student_model: request.studentModel, | |
| submitter_name: request.submitterName ? request.submitterName : null, | |
| additional_notes: request.additionalNotes, | |
| owner_id: request.ownerId, | |
| }), | |
| query: { select: "*" }, | |
| preferReturn: "representation", | |
| acceptObject: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| if (!data) { | |
| throw new Error("Failed to create request"); | |
| } | |
| return mapDistillationRow(data); | |
| } | |
| export async function addDatasetRequest( | |
| request: Omit<DatasetRequest, "id" | "upvotes" | "votedIps" | "createdAt" | "status"> | |
| ): Promise<DatasetRequest> { | |
| const { data, error } = await supabaseRest<any>("/rest/v1/dataset_requests", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| source_model: request.sourceModel, | |
| submitter_name: request.submitterName ? request.submitterName : null, | |
| dataset_size: request.datasetSize, | |
| reasoning_depth: request.reasoningDepth, | |
| topics: request.topics, | |
| additional_notes: request.additionalNotes, | |
| owner_id: request.ownerId, | |
| }), | |
| query: { select: "*" }, | |
| preferReturn: "representation", | |
| acceptObject: true, | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| if (!data) { | |
| throw new Error("Failed to create request"); | |
| } | |
| return mapDatasetRow(data); | |
| } | |
| export async function upvoteDistillation( | |
| id: string, | |
| ip: string | |
| ): Promise<{ success: boolean; upvotes: number; action?: "upvoted" | "unvoted" }> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/rpc/toggle_upvote_distillation", { | |
| method: "POST", | |
| body: JSON.stringify({ request_id: id, voter_ip: ip }), | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| if (!row) { | |
| return { success: false, upvotes: 0 }; | |
| } | |
| return { | |
| success: Boolean(row.success), | |
| upvotes: typeof row.upvotes === "number" ? row.upvotes : 0, | |
| action: row.action === "upvoted" || row.action === "unvoted" ? row.action : undefined, | |
| }; | |
| } | |
| export async function upvoteDataset( | |
| id: string, | |
| ip: string | |
| ): Promise<{ success: boolean; upvotes: number; action?: "upvoted" | "unvoted" }> { | |
| const { data, error } = await supabaseRest<any[]>("/rest/v1/rpc/toggle_upvote_dataset", { | |
| method: "POST", | |
| body: JSON.stringify({ request_id: id, voter_ip: ip }), | |
| }); | |
| if (error) { | |
| throw new Error(error.message); | |
| } | |
| const row = Array.isArray(data) ? data[0] : null; | |
| if (!row) { | |
| return { success: false, upvotes: 0 }; | |
| } | |
| return { | |
| success: Boolean(row.success), | |
| upvotes: typeof row.upvotes === "number" ? row.upvotes : 0, | |
| action: row.action === "upvoted" || row.action === "unvoted" ? row.action : undefined, | |
| }; | |
| } | |