mirror of
https://github.com/catthu/zk-stats-frontend-demo.git
synced 2026-01-09 19:18:01 -05:00
UI refresh, more complete features
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { ButtonHTMLAttributes, ReactNode } from "react"
|
||||
|
||||
const PRIMARY_STYLES = "px-6 py-2 text-sm text-gray-200 bg-blue-700 rounded focus:outline-none focus:bg-blue-700 hover:bg-blue-600"
|
||||
const PRIMARY_STYLES = "px-6 py-2 text-sm text-gray-200 bg-indigo-700 rounded focus:outline-none focus:bg-indigo-700 hover:bg-indigo-600"
|
||||
const SECONDARY_STYLES = "px-6 py-2 text-sm text-gray-700 bg-gray-200 rounded focus:outline-none focus:bg-gray-200 hover:bg-gray-400 hover:text-gray-100"
|
||||
const TERTIARY_STYLES ="px-6 py-2 text-sm text-blue-700 underline focus:outline-none hover:rounded hover:bg-gray-100"
|
||||
const QUARTERY_STYLES = "px-6 py-2 text-sm text-indigo-700 border rounded border-indigo-700 hover:rounded hover:bg-indigo-300"
|
||||
const DISABLED_STYLES = "px-6 py-2 text-sm text-gray-200 bg-gray-400 rounded focus:outline-none"
|
||||
|
||||
export enum ButtonVariant {
|
||||
PRIMARY = 'primary',
|
||||
SECONDARY = 'secondary',
|
||||
TERTIARY = 'tertiary',
|
||||
QUARTERY = 'quartery',
|
||||
DISABLED = 'disabled',
|
||||
}
|
||||
|
||||
@@ -23,6 +25,7 @@ const Button = ({children, variant = ButtonVariant.PRIMARY, ...props}: ButtonPro
|
||||
case ButtonVariant.PRIMARY: return PRIMARY_STYLES
|
||||
case ButtonVariant.SECONDARY: return SECONDARY_STYLES
|
||||
case ButtonVariant.TERTIARY: return TERTIARY_STYLES
|
||||
case ButtonVariant.QUARTERY: return QUARTERY_STYLES
|
||||
case ButtonVariant.DISABLED: return DISABLED_STYLES
|
||||
}
|
||||
return PRIMARY_STYLES;
|
||||
|
||||
@@ -116,19 +116,23 @@ type FormFileProps = {
|
||||
errors?: any; //TODO
|
||||
errorMessage?: string;
|
||||
onChange?: (fileChangeEvent: ChangeEvent<HTMLInputElement>) => void;
|
||||
fileName?: string;
|
||||
children?: ReactNode;
|
||||
} & InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
export const FormFile = React.forwardRef<HTMLInputElement, FormFileProps>(({errors, errorMessage, onChange, ...props}: FormFileProps, ref) => {
|
||||
export const FormFile = React.forwardRef<HTMLInputElement, FormFileProps>(({errors, errorMessage, onChange, fileName, children, ...props}: FormFileProps, ref) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
ref={ref}
|
||||
className=""
|
||||
className={`${children ? 'hidden': ''}`}
|
||||
{...props}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{children && children}
|
||||
{fileName && <span className="mt-2 text-sm text-gray-600">{fileName}</span>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { Breadcrumb } from "@/types/request";
|
||||
import Link from "next/link";
|
||||
|
||||
type HeroProps = {
|
||||
header: string;
|
||||
breadcrumb?: Array<Breadcrumb>;
|
||||
header: React.ReactNode;
|
||||
subheader?: string;
|
||||
action?: React.ReactNode
|
||||
}
|
||||
|
||||
const Hero = (props: HeroProps) => {
|
||||
const { header, subheader, action } = props;
|
||||
const { header, subheader, action, breadcrumb } = props;
|
||||
return (
|
||||
<div className="bg-blue-100 px-4 py-16 md:py-24 lg:py-32 text-center">
|
||||
<h1 className="text-6xl md:text-8xl font-bold text-blue-800 font-inter mb-6">
|
||||
<div className="px-24 py-16 md:py-24 lg:py-32 text-start">
|
||||
{breadcrumb?.length &&
|
||||
<div className="mb-8 text-sm text-slate-900">
|
||||
{breadcrumb.map((path: Breadcrumb) => <Link key={path.label} href={path.href}>{ path.label }</Link>)}
|
||||
</div>
|
||||
}
|
||||
<h1 className="text-6xl md:text-8xl font-bold text-indigo-900 font-inter mb-6">
|
||||
{ header }
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-gray-700 mb-8 font-inter">
|
||||
@@ -19,4 +28,24 @@ const Hero = (props: HeroProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Hero;
|
||||
export const SmallHero = (props: HeroProps) => {
|
||||
const { breadcrumb, header, subheader } = props;
|
||||
// TODO ADD BREADCRUMB
|
||||
return (
|
||||
<div className="px-24 py-8 md:py-8 lg:py-8 text-start">
|
||||
{breadcrumb?.length &&
|
||||
<div className="mb-8 text-sm text-slate-900">
|
||||
{breadcrumb.map((path: Breadcrumb) => <Link key={path.label} href={path.href}> { path.label } ></Link>)}
|
||||
</div>
|
||||
}
|
||||
<h1 className="text-3xl md:text-3xl font-bold text-indigo-900 font-inter mb-2">
|
||||
{ header }
|
||||
</h1>
|
||||
<p className="text-md md:text-xl text-gray-700 mb-4 font-inter">
|
||||
{ subheader }
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hero;
|
||||
|
||||
@@ -12,11 +12,15 @@ type ModalProps = {
|
||||
const Modal = (props: ModalProps) => {
|
||||
const { children, title, onClose } = props;
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="fixed w-3/4 max-h-screen top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 overflow-scroll z-50"
|
||||
className="fixed w-full h-full top-0 right-0 opacity-70 bg-gradient-to-r from-slate-600 to-slate-800"
|
||||
></div>
|
||||
<div
|
||||
className="fixed w-1/2 max-h-screen top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 overflow-scroll z-50"
|
||||
>
|
||||
<div
|
||||
className="bg-white border border-blue-200 rounded w-full my-8"
|
||||
className="bg-white border border-blue-200 rounded-lg w-full my-8"
|
||||
>
|
||||
<div
|
||||
className="fixed right-4 top-10 hover:cursor-pointer"
|
||||
@@ -29,7 +33,7 @@ const Modal = (props: ModalProps) => {
|
||||
>
|
||||
{title &&
|
||||
<div
|
||||
className="text-2xl font-bold self-center"
|
||||
className="text-2xl font-bold self-left"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
@@ -38,6 +42,7 @@ const Modal = (props: ModalProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useUser } from "@/utils/session";
|
||||
import { faCircle, faUser } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { ReactNode, useState } from "react";
|
||||
|
||||
const NavBar = () => {
|
||||
const [ showUserMenu, setShowUserMenu ] = useState<boolean>(false);
|
||||
@@ -11,17 +11,19 @@ const NavBar = () => {
|
||||
}
|
||||
const user = useUser();
|
||||
return (
|
||||
<div>
|
||||
<div className="my-4 px-24 flex justify-between w-full">
|
||||
<div>
|
||||
<div
|
||||
className="m-4 py-2 px-12 rounded bg-slate-800 text-white"
|
||||
>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<NavBarItem>
|
||||
<Link href="/">Home</Link>
|
||||
</div>
|
||||
</NavBarItem>
|
||||
<div className="flex gap-4 items-center">
|
||||
{user && user.username}
|
||||
<div className="flex items-center hover:cursor-pointer" onClick={onUserMenuClick}>
|
||||
<span className="fa-layers fa-fw h-8 w-8" style={{ backgroundColor: 'transparent' }}>
|
||||
<FontAwesomeIcon icon={faCircle} size="2x" className="text-blue-300 mx-auto w-full"/>
|
||||
<FontAwesomeIcon icon={faUser} className="text-blue-800 mx-auto w-full"/>
|
||||
<FontAwesomeIcon icon={faCircle} size="2x" className="text-white mx-auto w-full"/>
|
||||
<FontAwesomeIcon icon={faUser} className="text-gray-800 mx-auto w-full"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,19 +35,26 @@ const NavBar = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const NavBarItem = ({ children }: {children: ReactNode}) => {
|
||||
return (
|
||||
<div
|
||||
className="hover:underline"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type UserMenuProps = {
|
||||
authenticated: boolean;
|
||||
}
|
||||
|
||||
const UserMenu = ({ authenticated }: UserMenuProps) => {
|
||||
return (
|
||||
<div className="flex flex-col absolute right-0 bg-white px-4 py-4">
|
||||
<div className="flex flex-col absolute right-0 bg-slate-800 p-2 m-4 rounded">
|
||||
<UserMenuItem label="Account" path="/" />
|
||||
<hr />
|
||||
<UserMenuItem label="Notifications" path="/" />
|
||||
<hr />
|
||||
<UserMenuItem label="Add a Dataset" path="/datasets/upload" />
|
||||
<hr />
|
||||
{authenticated
|
||||
? <UserMenuItem label="Sign Out" path="/auth/signout" />
|
||||
:
|
||||
@@ -62,7 +71,7 @@ type UserMenuItemProps = {
|
||||
|
||||
const UserMenuItem = ({ label, path }: UserMenuItemProps) => {
|
||||
return (
|
||||
<Link href={path} className="px-8 py-4">
|
||||
<Link href={path} className="px-8 py-4 hover:text-slate-800 hover:bg-indigo-50 rounded">
|
||||
{ label }
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -2,29 +2,29 @@ import { DatasetPreview } from '@/types/dataset';
|
||||
import Link from 'next/link';
|
||||
|
||||
type DatasetCardProps = {
|
||||
previewImage: React.ReactNode; // todo
|
||||
dataset: DatasetPreview;
|
||||
}
|
||||
|
||||
export const DatasetCard = (props: DatasetCardProps) => {
|
||||
const { previewImage, dataset } = props;
|
||||
const { id, title, description } = dataset;
|
||||
const { dataset } = props;
|
||||
const { id, title, description, updatedAt } = dataset;
|
||||
|
||||
|
||||
console.log(dataset)
|
||||
|
||||
return (
|
||||
<Link href={`/datasets/${id}`}>
|
||||
<div className="transform transition duration-100 ease-in-out hover:scale-105 hover:shadow-lg bg-white rounded-lg overflow-hidden border-2 border-gray-100" >
|
||||
<div className="bg-gray-300 h-48 w-full flex items-center justify-center">
|
||||
<div className="transform transition duration-100 ease-in-out hover:scale-105 hover:shadow-lg bg-indigo-100 rounded-lg overflow-hidden" >
|
||||
{/* <div className="bg-gray-300 h-48 w-full flex items-center justify-center">
|
||||
Preview Image
|
||||
{ previewImage }
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="p-4">
|
||||
<div className="font-semibold text-lg mb-2">
|
||||
<div className="font-semibold text-lg text-slate-800 mb-2">
|
||||
{ title }
|
||||
</div>
|
||||
<p className="text-gray-600">{ description }</p>
|
||||
<p className="text-gray-600 text-sm">Uploaded 3 hours ago</p>
|
||||
<p className="text-gray-600 text-sm">1,204 views · 10 minutes</p>
|
||||
<p className="text-gray-400 text-xs my-2">Updated on {updatedAt}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -39,40 +39,53 @@ const NewRequestForm = (props: NewRequestProps) => {
|
||||
<div>
|
||||
<FormWrapper onSubmit={handleSubmit(onSubmit)}>
|
||||
<div
|
||||
className="bg-gray-50 rounded p-4 mb-4 border-2 border-gray-100"
|
||||
className="bg-indigo-100 rounded p-4 mb-4"
|
||||
>
|
||||
<FormItem>
|
||||
<div
|
||||
className="mb-2 text-md font-medium text-gray-900"
|
||||
className="my-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Request Name
|
||||
</div>
|
||||
<FormInput
|
||||
errors={errors}
|
||||
errorMessage={'Just a test error'}
|
||||
className="bg-indigo-100 border border-indigo-300 rounded w-full text-sm block p-2.5"
|
||||
placeholder="The title of your computation request"
|
||||
{...register('title', { required: true })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div
|
||||
className="mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Request Description
|
||||
</div>
|
||||
<FormTextArea
|
||||
errors={errors}
|
||||
errorMessage="text area error"
|
||||
className="bg-indigo-100 border border-indigo-300 rounded w-full text-sm block p-2.5"
|
||||
placeholder="Describe what your computation is about"
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<div
|
||||
className="bg-gray-50 rounded p-4 mb-4 border-2 border-gray-100"
|
||||
className="bg-indigo-100 rounded p-4 mb-4"
|
||||
>
|
||||
<FormItem>
|
||||
<div
|
||||
className="my-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Function to Compute
|
||||
<p className="text-sm">Some help text here about function signature, data types...</p>
|
||||
<p className="text-xs">Some help text here about function signature, data types...</p>
|
||||
|
||||
</div>
|
||||
{/* TODO SUPPORT TAB TO INDENT */}
|
||||
<FormTextArea
|
||||
errors={errors}
|
||||
errorMessage="text area error"
|
||||
className="font-mono"
|
||||
className="bg-indigo-100 border border-indigo-300 rounded w-full text-sm block p-2.5 font-mono"
|
||||
{...register('code', { required: true })}
|
||||
>
|
||||
def computation(s: State, data: list[torch.Tensor]) -> torch.Tensor: Test
|
||||
|
||||
@@ -29,9 +29,6 @@ export const OwnerRequests = (props: OwnerRequestsProps) => {
|
||||
}
|
||||
}, [datasetId, setRequests])
|
||||
|
||||
const onNewRequestSubmit = () => {
|
||||
setActiveView(ActiveView.RequestSubmitted)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
||||
@@ -13,7 +13,11 @@ const ReviewResultModal = (props: ReviewResultModalProps) => {
|
||||
const { onClose, onAccept, result } = props;
|
||||
return (
|
||||
<Modal title="Review Result" onClose={onClose}>
|
||||
<div
|
||||
className="text-gray-500"
|
||||
>
|
||||
Please review the result below.
|
||||
</div>
|
||||
<div className="bg-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold">
|
||||
<div className="bg-gray-100 rounded text-sm font-light p-2">
|
||||
@@ -24,14 +28,16 @@ const ReviewResultModal = (props: ReviewResultModalProps) => {
|
||||
<div
|
||||
className="flex w-full gap-4"
|
||||
>
|
||||
<Button
|
||||
onClick={onAccept}
|
||||
>Submit Result</Button>
|
||||
<Button
|
||||
variant={ButtonVariant.SECONDARY}
|
||||
>
|
||||
Request Further Review
|
||||
</Button>
|
||||
<div className="w-full flex justify-between mt-6">
|
||||
<Button
|
||||
variant={ButtonVariant.QUARTERY}
|
||||
>
|
||||
Request Further Review
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onAccept}
|
||||
>Approve Result</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import Button from "@/components/common/Button";
|
||||
import Button, { ButtonVariant } from "@/components/common/Button";
|
||||
import { FormFile, FormInput } from "@/components/common/Form";
|
||||
import Modal from "@/components/common/Modal";
|
||||
import { useState } from "react";
|
||||
|
||||
type onSubmitResultModalOnSubmit = {
|
||||
result: string;
|
||||
proofFile: File |null;
|
||||
vkFile: File | null;
|
||||
modelOnnxFile: File | null;
|
||||
srsFile: File | null;
|
||||
precalWitnessFile: File | null;
|
||||
settingsFile: File | null;
|
||||
}
|
||||
|
||||
type SubmitResultModalProps = {
|
||||
onSubmit: ({
|
||||
result,
|
||||
proofFile,
|
||||
vkFile,
|
||||
modelOnnxFile,
|
||||
srsFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}: onSubmitResultModalOnSubmit) => void; // TODO remove the null possibility
|
||||
onClose: VoidFunction;
|
||||
@@ -26,36 +24,63 @@ const SubmitResultModal = (props: SubmitResultModalProps) => {
|
||||
const { onClose, onSubmit } = props;
|
||||
const [ result, setResult ] = useState<any>();
|
||||
const [ proofFile, setProofFile ] = useState<File | null>(null);
|
||||
const [ vkFile, setVkFile ] = useState<File | null>(null);
|
||||
const [ modelOnnxFile, setModelOnnxFile ] = useState<File | null>(null);
|
||||
const [ srsFile, setSrsFile ] = useState<File | null>(null);
|
||||
const [ precalWitnessFile, setPrecalWitnessFile ] = useState<File | null>(null);
|
||||
const [ settingsFile, setSettingsFile ] = useState<File | null>(null);
|
||||
|
||||
|
||||
// TODO FINISH EXTRACTING THIS
|
||||
// TODO WHAT HAPPEN WHEN THERE IS MORE THAN ONE RESULT? NEED TO TEST
|
||||
|
||||
if (proofFile) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.target?.result as string)
|
||||
console.log(json['pretty_public_inputs']['rescaled_outputs'].slice(1,).join(','))
|
||||
setResult(json['pretty_public_inputs']['rescaled_outputs'].slice(1,).join(', '))
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error)
|
||||
}
|
||||
}
|
||||
reader.readAsText(proofFile)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Modal title="Submit Result" onClose={onClose}>
|
||||
Completed the computation? Sumbit the result here.
|
||||
<FormInput
|
||||
onChange={(e) => setResult(e.target.value)}
|
||||
/>
|
||||
Sumbit the proof here.
|
||||
<div className="text-gray-500">
|
||||
Completed the computation? Sumbit the result and proof here.
|
||||
</div>
|
||||
<hr />
|
||||
<h2
|
||||
className="text-lg font-bold"
|
||||
>Proof</h2>
|
||||
<div className="text-gray-500">Select the proof <code>(model.pf)</code> file</div>
|
||||
<FormFile onChange={(e) => e.target.files && setProofFile(e.target.files[0])}/>
|
||||
Submit the verification key.
|
||||
<FormFile onChange={(e) => e.target.files && setVkFile(e.target.files[0])}/>
|
||||
Submit the model onnx.
|
||||
<FormFile onChange={(e) => e.target.files && setModelOnnxFile(e.target.files[0])}/>
|
||||
Submit the SRS file.
|
||||
<FormFile onChange={(e) => e.target.files && setSrsFile(e.target.files[0])}/>
|
||||
Submit the settings file.
|
||||
{result && <p>Result: {result}</p>}
|
||||
<hr />
|
||||
<h2
|
||||
className="text-lg font-bold"
|
||||
>Precal Witness</h2>
|
||||
<div className="text-gray-500">Select the precal witness <code>(precal_witness.json)</code> file</div>
|
||||
<FormFile onChange={(e) => e.target.files && setPrecalWitnessFile(e.target.files[0])}/>
|
||||
<hr />
|
||||
<h2
|
||||
className="text-lg font-bold"
|
||||
>Settings</h2>
|
||||
<div className="text-gray-500">Select the settings <code>(settings.json)</code> file</div>
|
||||
<FormFile onChange={(e) => e.target.files && setSettingsFile(e.target.files[0])}/>
|
||||
<Button
|
||||
onClick={() => onSubmit({
|
||||
proofFile,
|
||||
vkFile,
|
||||
modelOnnxFile,
|
||||
srsFile,
|
||||
settingsFile
|
||||
})}
|
||||
>Submit Result</Button>
|
||||
<div className="w-full flex justify-end mt-6">
|
||||
<Button
|
||||
variant={ButtonVariant.PRIMARY}
|
||||
onClick={() => onSubmit({
|
||||
result,
|
||||
proofFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
})}
|
||||
>Submit Result</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import Button, { ButtonVariant } from "@/components/common/Button";
|
||||
import { RawRequest, RequestPreview, RequestStatus, convertRawRequestToFullRequest } from "@/types/request";
|
||||
import { APIEndPoints, api } from "@/utils/api";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import NewRequest from "./NewRequest";
|
||||
import Layout from "../common/Layout";
|
||||
import NewRequest from "./NewRequest";
|
||||
import RequestSubmitted from "./RequestSubmitted";
|
||||
import { FormInput } from "../common/Form";
|
||||
|
||||
type UserRequestsProps = {
|
||||
userId: string; // TODO uuid
|
||||
datasetId: string; // TODO
|
||||
status?: RequestStatus;
|
||||
userId: string;
|
||||
datasetId: string;
|
||||
isDataOwner?: boolean;
|
||||
}
|
||||
|
||||
enum ActiveView {
|
||||
@@ -19,124 +20,171 @@ enum ActiveView {
|
||||
RequestSubmitted = 'Request Submitted',
|
||||
}
|
||||
|
||||
export const UserRequests = (props: UserRequestsProps) => {
|
||||
const { userId, datasetId, status } = props;
|
||||
const [ requests, setRequests] = useState<Array<RequestPreview>>([]);
|
||||
const [ activeView, setActiveView ] = useState<ActiveView>(ActiveView.RequestList);
|
||||
|
||||
const [ filteredRequests, setFilteredRequests ] = useState<Array<RequestPreview>>([]);
|
||||
export const UserRequests = ({ userId, datasetId, isDataOwner }: UserRequestsProps) => {
|
||||
const [requests, setRequests] = useState<RequestPreview[]>([]);
|
||||
const [activeView, setActiveView] = useState<ActiveView>(ActiveView.RequestList);
|
||||
const [filteredRequests, setFilteredRequests] = useState<RequestPreview[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (userId && datasetId) {
|
||||
api(APIEndPoints.Requests, { userId, datasetId}).then((response) => {
|
||||
setRequests(response.map((request: RawRequest) => convertRawRequestToFullRequest(request)));
|
||||
setFilteredRequests(response.map((request: RawRequest) => convertRawRequestToFullRequest(request)));
|
||||
})
|
||||
}
|
||||
}, [userId, datasetId, setRequests])
|
||||
const fetchRequests = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await api(APIEndPoints.Requests, {
|
||||
userId: isDataOwner ? undefined : userId,
|
||||
datasetId });
|
||||
const fullRequests = response.map((request: RawRequest) => convertRawRequestToFullRequest(request));
|
||||
setRequests(fullRequests);
|
||||
setFilteredRequests(fullRequests);
|
||||
} catch (err) {
|
||||
setError('Failed to fetch requests. Please try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchRequests();
|
||||
}, [userId, datasetId]);
|
||||
|
||||
const onNewRequestSubmit = () => {
|
||||
setActiveView(ActiveView.RequestSubmitted)
|
||||
}
|
||||
const handleNewRequestSubmit = useCallback(() => {
|
||||
setActiveView(ActiveView.RequestSubmitted);
|
||||
}, []);
|
||||
|
||||
const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
setFilteredRequests(requests.filter(request =>
|
||||
request.title.toLowerCase().includes(searchTerm) ||
|
||||
request.description?.toLowerCase().includes(searchTerm)
|
||||
));
|
||||
}, [requests]);
|
||||
|
||||
const activeRequests = requests.filter(request => !request.isCompleted);
|
||||
const processingRequests = requests.filter(request => request.isAccepted && !request.isCompleted);
|
||||
const completedRequests = requests.filter(request => request.isCompleted);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<RequestActionBar
|
||||
<RequestActionBar
|
||||
isDataOwner={isDataOwner}
|
||||
numberActive={activeRequests.length}
|
||||
numberProcessing={processingRequests.length}
|
||||
numberCompleted={completedRequests.length}
|
||||
onClickNewRequest={() => setActiveView(ActiveView.NewRequest)}
|
||||
onClickAll={() => {
|
||||
onClickActive={() => {
|
||||
setActiveView(ActiveView.RequestList);
|
||||
setFilteredRequests(requests);
|
||||
setFilteredRequests(activeRequests);
|
||||
}}
|
||||
onClickProcessing={() => setFilteredRequests(requests.filter(request => request.isAccepted && !request.isCompleted))}
|
||||
onClickCompleted={() => setFilteredRequests(requests.filter(request => request.isCompleted))}
|
||||
onClickProcessing={() => setFilteredRequests(processingRequests)}
|
||||
onClickCompleted={() => setFilteredRequests(completedRequests)}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
{activeView === ActiveView.RequestList &&
|
||||
<div>
|
||||
{filteredRequests?.map((request) => <RequestRow key={request.id} {...request}/>)}
|
||||
{isLoading && <div className="text-center my-6">Loading...</div>}
|
||||
{error && <div className="text-center my-6 text-red-500">{error}</div>}
|
||||
{!isLoading && !error && activeView === ActiveView.RequestList && (
|
||||
<div className="space-y-4">
|
||||
{filteredRequests.map((request) => (
|
||||
<RequestRow key={request.id} {...request} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
{activeView === ActiveView.NewRequest && <NewRequest userId={userId} datasetId={datasetId} onSubmit={onNewRequestSubmit}/>}
|
||||
)}
|
||||
{activeView === ActiveView.NewRequest && (
|
||||
<NewRequest userId={userId} datasetId={datasetId} onSubmit={handleNewRequestSubmit} />
|
||||
)}
|
||||
{activeView === ActiveView.RequestSubmitted && <RequestSubmitted />}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
type RequestActionBarProps = {
|
||||
isDataOwner?: boolean;
|
||||
numberActive: number;
|
||||
numberProcessing: number;
|
||||
numberCompleted: number;
|
||||
onClickNewRequest: VoidFunction;
|
||||
onClickProcessing:VoidFunction;
|
||||
onClickActive: VoidFunction;
|
||||
onClickProcessing: VoidFunction;
|
||||
onClickCompleted: VoidFunction;
|
||||
onClickAll: VoidFunction;
|
||||
onSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const RequestActionBar = (props: RequestActionBarProps) => {
|
||||
const { onClickNewRequest, onClickCompleted, onClickProcessing, onClickAll } = props;
|
||||
const RequestActionBar = ({
|
||||
isDataOwner,
|
||||
numberActive,
|
||||
numberProcessing,
|
||||
numberCompleted,
|
||||
onClickNewRequest,
|
||||
onClickActive,
|
||||
onClickProcessing,
|
||||
onClickCompleted,
|
||||
onSearch
|
||||
}: RequestActionBarProps) => {
|
||||
return (
|
||||
<div className="flex space-around gap-2 my-6">
|
||||
<Button
|
||||
onClick={onClickNewRequest}
|
||||
variant={ButtonVariant.SECONDARY}
|
||||
>
|
||||
<div className="flex flex-wrap gap-4 my-6 items-center bg-indigo-200 p-4 rounded-lg shadow">
|
||||
{(isDataOwner !== undefined && !isDataOwner) &&
|
||||
<Button onClick={onClickNewRequest} variant={ButtonVariant.PRIMARY}>
|
||||
+ New Request
|
||||
</Button>
|
||||
<Button
|
||||
variant={ButtonVariant.TERTIARY}
|
||||
onClick={onClickAll}
|
||||
>
|
||||
All
|
||||
}
|
||||
<Button variant={ButtonVariant.QUARTERY} onClick={onClickActive}>
|
||||
Active ({numberActive})
|
||||
</Button>
|
||||
<Button
|
||||
variant={ButtonVariant.TERTIARY}
|
||||
onClick={onClickProcessing}
|
||||
>
|
||||
Processing
|
||||
<Button variant={ButtonVariant.QUARTERY} onClick={onClickProcessing}>
|
||||
Processing ({numberProcessing})
|
||||
</Button>
|
||||
<Button
|
||||
variant={ButtonVariant.TERTIARY}
|
||||
onClick={onClickCompleted}
|
||||
>
|
||||
Completed
|
||||
<Button variant={ButtonVariant.QUARTERY} onClick={onClickCompleted}>
|
||||
Completed ({numberCompleted})
|
||||
</Button>
|
||||
<FormInput
|
||||
placeholder="Search requests..."
|
||||
onChange={onSearch}
|
||||
className="flex-grow p-2 rounded border border-indigo-400 bg-indigo-200"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const RequestRow = (props: RequestPreview) => {
|
||||
const { title, description, datasetId, id, isAccepted, isCompleted } = props;
|
||||
const RequestRow = ({
|
||||
title,
|
||||
description,
|
||||
datasetId,
|
||||
id,
|
||||
isAccepted,
|
||||
isCompleted,
|
||||
resultApproved
|
||||
}: RequestPreview) => {
|
||||
let statusColor = 'bg-gray-400';
|
||||
let statusText = 'Awaiting Confirmation'
|
||||
if (isAccepted) {
|
||||
let statusText = 'Awaiting Confirmation';
|
||||
|
||||
if (isAccepted) {
|
||||
statusColor = 'bg-yellow-400';
|
||||
statusText = 'Confirmed, Awaiting Results';
|
||||
}
|
||||
if (isCompleted) {
|
||||
statusColor = 'bg-green-400';
|
||||
statusText = 'Results Ready'
|
||||
statusText = 'Results Ready';
|
||||
}
|
||||
if (resultApproved) {
|
||||
statusColor = 'bg-green-800';
|
||||
statusText = 'Request Completed';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-blue-100 shadow-md rounded-lg p-4 mb-4 transition duration-150 ease-in-out hover:shadow-xl hover:scale-105">
|
||||
<Link href={`/datasets/${datasetId}/requests/${id}`}>
|
||||
<div
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<div
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<h3
|
||||
className="text-lg font-semibold text-gray-800"
|
||||
>{ title }</h3>
|
||||
<div
|
||||
className={`text-sm ${statusColor} rounded px-2 text-white`}
|
||||
>
|
||||
{statusText}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 border-1 border-gray-200 hover:bg-indigo-100 rounded-lg p-4 mb-2">
|
||||
<Link href={`/datasets/${datasetId}/requests/${id}`}>
|
||||
<div className="flex flex-col sm:flex-row gap-4 items-center">
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
<div className={`text-xs ${statusColor} rounded-full px-3 py-1 text-white w-1/6 flex justify-center items-center`}>
|
||||
{statusText}
|
||||
</div>
|
||||
<div
|
||||
className="text-sm text-gray-600"
|
||||
>
|
||||
{ description }
|
||||
<h3 className="text-lg font-semibold text-gray-900 w-2/6">
|
||||
{title}
|
||||
</h3>
|
||||
<div className="text-sm text-gray-600 w-3/6">
|
||||
{description}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import Hero from "@/components/common/Hero";
|
||||
import Hero, { SmallHero } from "@/components/common/Hero";
|
||||
import { APIEndPoints, api } from "@/utils/api";
|
||||
import { GetServerSideProps } from "next";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { UserRequests } from "@/components/requests/UserRequests";
|
||||
import Button from "@/components/common/Button";
|
||||
import { Dataset, convertDatasetResponseToDataset } from "@/types/dataset";
|
||||
@@ -9,6 +9,10 @@ import Layout from "@/components/common/Layout";
|
||||
import NavBar from "@/components/common/NavBar";
|
||||
import { useUser } from "@/utils/session";
|
||||
import { OwnerRequests } from "@/components/requests/OwnerRequests";
|
||||
import Link from "next/link";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faColumns, faFileAlt, faSquare, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FormFile } from "@/components/common/Form";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const { id } = context.params as {
|
||||
@@ -41,11 +45,10 @@ enum MenuTab {
|
||||
Schema = 'schema',
|
||||
Discussions = 'discussions',
|
||||
Requests = 'requests',
|
||||
RequestsOwnerView = 'requests_owner_view'
|
||||
}
|
||||
|
||||
const DatasetPage = ({ dataset }: DatasetPageProps) => {
|
||||
const { id, title, description, testDataUrl, sourceDescription, acknowledgement, ownerId } = dataset;
|
||||
const { id, title, description, testDataUrl, sourceDescription, acknowledgement, ownerId, schema, rows, columns } = dataset;
|
||||
const [ activeTab, setActiveTab ] = useState<MenuTab>(MenuTab.About);
|
||||
|
||||
const user = useUser();
|
||||
@@ -55,13 +58,14 @@ const DatasetPage = ({ dataset }: DatasetPageProps) => {
|
||||
return (
|
||||
<div>
|
||||
<NavBar />
|
||||
<Hero header={ title } subheader={ description }/>
|
||||
<SmallHero header={ title } subheader={ description }/>
|
||||
<DatasetMenu onTabChange={setActiveTab} activeTab={activeTab} isDatasetOwner={isOwnedByUser} />
|
||||
|
||||
{ activeTab === MenuTab.About && <AboutSection description={description} testDataUrl={testDataUrl} sourceDescription={sourceDescription} acknowledgement={acknowledgement} /> }
|
||||
{ activeTab === MenuTab.History && "Just testing" }
|
||||
{ activeTab === MenuTab.Requests && (user?.id ? <UserRequests userId={user?.id} datasetId={id} /> : "Please log in.")}
|
||||
{ activeTab === MenuTab.RequestsOwnerView && <OwnerRequests datasetId={id} />}
|
||||
{ activeTab === MenuTab.About && <AboutSection datasetId={id} description={description} testDataUrl={testDataUrl} sourceDescription={sourceDescription} acknowledgement={acknowledgement} isOwnedByUser={isOwnedByUser} rows={rows} columns={columns}/> }
|
||||
{ activeTab === MenuTab.History && <HistorySection /> }
|
||||
{ activeTab === MenuTab.Discussions && <DiscussionSection />}
|
||||
{ activeTab === MenuTab.Requests && (user?.id ? <UserRequests userId={user?.id} datasetId={id} isDataOwner={isOwnedByUser} /> : "Please log in.")}
|
||||
{ activeTab === MenuTab.Schema && <SchemaSection schema={schema}/>}
|
||||
|
||||
</div>
|
||||
)
|
||||
@@ -76,12 +80,12 @@ type DatasetMenuProps = {
|
||||
const DatasetMenu = ({ onTabChange, activeTab, isDatasetOwner }: DatasetMenuProps) => {
|
||||
return (
|
||||
<div className="py-4">
|
||||
<div className="container mx-auto flex justify-center space-x-8">
|
||||
<div className="container mx-24 flex justify-start border-b-2">
|
||||
<DatasetMenuItemContainer
|
||||
onClick={() => onTabChange(MenuTab.About)}
|
||||
isActive={activeTab === MenuTab.About}
|
||||
>
|
||||
About
|
||||
About this dataset
|
||||
</DatasetMenuItemContainer>
|
||||
<DatasetMenuItemContainer
|
||||
onClick={() => onTabChange(MenuTab.History)}
|
||||
@@ -101,20 +105,12 @@ const DatasetMenu = ({ onTabChange, activeTab, isDatasetOwner }: DatasetMenuProp
|
||||
>
|
||||
Discussions
|
||||
</DatasetMenuItemContainer>
|
||||
{!isDatasetOwner
|
||||
? <DatasetMenuItemContainer
|
||||
<DatasetMenuItemContainer
|
||||
onClick={() => onTabChange(MenuTab.Requests)}
|
||||
isActive={activeTab === MenuTab.Requests}
|
||||
>
|
||||
Your Requests
|
||||
{isDatasetOwner ? 'Active Requests' : 'Your Requests'}
|
||||
</DatasetMenuItemContainer>
|
||||
: <DatasetMenuItemContainer
|
||||
onClick={() => onTabChange(MenuTab.RequestsOwnerView)}
|
||||
isActive={activeTab === MenuTab.RequestsOwnerView}
|
||||
>
|
||||
Active Requests
|
||||
</DatasetMenuItemContainer>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -128,7 +124,7 @@ type DatasetMenuItemContainerProps = {
|
||||
|
||||
const DatasetMenuItemContainer = ({ children, onClick, isActive = false }: DatasetMenuItemContainerProps) => {
|
||||
return (
|
||||
<div className={`${isActive && 'bg-blue-100 rounded'} menu-item text-gray-900 hover:font-semibold relative hover:text-gray-900 hover:cursor-pointer transition-all px-2 py-2`}
|
||||
<div className={`${isActive ? 'text-indigo-700 font-bold border-indigo-700 border-b-2' : 'text-gray-500 hover:text-gray-700'} relative hover:cursor-pointer mr-12 py-4 `}
|
||||
onClick={onClick}
|
||||
>
|
||||
{ children }
|
||||
@@ -156,30 +152,235 @@ const DatasetSection = (props: DatasetSectionProps) => {
|
||||
}
|
||||
|
||||
type AboutSectionProps = {
|
||||
datasetId: string;
|
||||
description: string;
|
||||
testDataUrl?: string;
|
||||
sourceDescription?: string;
|
||||
acknowledgement?: string;
|
||||
isOwnedByUser?: boolean;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
const AboutSection = (props: AboutSectionProps) => {
|
||||
const { description, testDataUrl, sourceDescription, acknowledgement } = props;
|
||||
const { datasetId, description, testDataUrl, sourceDescription, acknowledgement, isOwnedByUser, rows, columns } = props;
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
className="flex flex-col w-full space-between gap-4"
|
||||
>
|
||||
<CryptographicAssets datasetId={datasetId} isOwnedByUser={isOwnedByUser} rows={rows} columns={columns}/>
|
||||
|
||||
<div
|
||||
className="flex flex-col flex-grow"
|
||||
>
|
||||
<div>
|
||||
{ description }
|
||||
</div>
|
||||
{ testDataUrl && (
|
||||
<div className="mx-auto m-4">
|
||||
<Button onClick={() => window.location.href = testDataUrl}>
|
||||
Download Test Data
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{sourceDescription && <DatasetSection title="Data Sources" content={ sourceDescription } />}
|
||||
{acknowledgement && <DatasetSection title="Acknowledgment" content={ acknowledgement } />}
|
||||
</div>
|
||||
<div>
|
||||
{ description }
|
||||
</div>
|
||||
{ testDataUrl && (
|
||||
<div className="mx-auto m-4">
|
||||
<Button onClick={() => window.location.href = testDataUrl}>
|
||||
Download Test Data
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{sourceDescription && <DatasetSection title="Data Sources" content={ sourceDescription } />}
|
||||
{acknowledgement && <DatasetSection title="Acknowledgment" content={ acknowledgement } />}
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const DiscussionSection = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
className="flex w-full space-between"
|
||||
>
|
||||
Discussion sections are coming soon.
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const HistorySection = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
className="flex w-full space-between"
|
||||
>
|
||||
History sections are coming soon.
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
type SchemaSectionProps = {
|
||||
schema?: Record<any, string>;
|
||||
}
|
||||
|
||||
type PropertyRowProps = {
|
||||
key: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const SchemaSection = ({ schema }: SchemaSectionProps) => {
|
||||
if (!schema) {
|
||||
return (
|
||||
<Layout>
|
||||
The owner of this dataset has not provided a schema.
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const rows: Array<PropertyRowProps> = [];
|
||||
for (let key in (schema.properties as any)) {
|
||||
//@ts-ignore
|
||||
const property = schema.properties[key];
|
||||
rows.push({
|
||||
key,
|
||||
type: property.type,
|
||||
description: property.description || '',
|
||||
})
|
||||
}
|
||||
|
||||
const PropertyRow = ({ row } : { row: PropertyRowProps }) => {
|
||||
return (
|
||||
<tr key={row.key}>
|
||||
<td
|
||||
className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"
|
||||
>{ row.key }</td>
|
||||
<td
|
||||
className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"
|
||||
>{ row.type }</td>
|
||||
<td
|
||||
className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"
|
||||
>{ row.description }</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
className="flex flex-col w-full space-between"
|
||||
>
|
||||
<div
|
||||
className="mb-8 flex justify-center"
|
||||
>
|
||||
<div>{ schema.title }</div>
|
||||
<div>{ schema.description }</div>
|
||||
</div>
|
||||
<table
|
||||
className="min-w-full divide-y divide-gray-200 p-4"
|
||||
>
|
||||
<thead
|
||||
className="bg-slate-300 text-indigo-800 font-bold"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
className="px-6 py-3 text-left text-xs text-indigo-500 uppercase tracking-wider font-bold"
|
||||
>Property</th>
|
||||
<th
|
||||
className="px-6 py-3 text-left text-xs text-indigo-500 uppercase tracking-wider font-bold"
|
||||
>Type</th>
|
||||
<th
|
||||
className="px-6 py-3 text-left text-xs text-indigo-500 uppercase tracking-wider font-bold"
|
||||
>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{rows.map(row => (
|
||||
<PropertyRow key={row.key} row={row} />
|
||||
))
|
||||
}
|
||||
</table>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
type CryptographicAssetsProps = {
|
||||
datasetId: string;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
isOwnedByUser?: boolean;
|
||||
}
|
||||
|
||||
const CryptographicAssets = ({ datasetId, isOwnedByUser, rows, columns } : CryptographicAssetsProps) => {
|
||||
const [ doesDataCommitmentExist, setDoesDataCommitmentExist ] = useState<boolean | null>(null);
|
||||
const [ dataCommitmentFile, setDataCommitmentFile ] = useState<File | null>(null);
|
||||
const dataCommitmentInputRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
api(APIEndPoints.CheckFileExists, {
|
||||
bucketName: 'proof_assets',
|
||||
filePath: `${datasetId}`,
|
||||
fileName: 'data_commitment.json'
|
||||
}).then(result => setDoesDataCommitmentExist(result))
|
||||
}, [api])
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target?.files ? event.target.files[0] : null;
|
||||
if (file) {
|
||||
setDataCommitmentFile(file)
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = async () => {
|
||||
if (dataCommitmentFile) {
|
||||
await api(APIEndPoints.UploadDataCommitment, {datasetId, dataCommitmentFile });
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-slate-500 my-2">
|
||||
<div className="flex flex-row gap-10">
|
||||
{doesDataCommitmentExist === null && 'Loading...'}
|
||||
{doesDataCommitmentExist === false &&
|
||||
<div className="flex gap-2 items-center text-red-400">
|
||||
<FontAwesomeIcon icon={faWarning}/> <span className="text-sm">Missing data commitment</span>
|
||||
{isOwnedByUser &&
|
||||
<FormFile
|
||||
//@ts-ignore
|
||||
ref={dataCommitmentInputRef}
|
||||
onChange={handleChange}
|
||||
fileName={dataCommitmentFile ? dataCommitmentFile.name : undefined}
|
||||
>
|
||||
<button
|
||||
className={`mx-2 px-2 py-1 text-xs text-slate-50 rounded ${dataCommitmentFile ? 'bg-indigo-700' : 'bg-indigo-300'}`}
|
||||
onClick={() =>
|
||||
dataCommitmentFile
|
||||
? handleUpload()
|
||||
//@ts-ignore
|
||||
: dataCommitmentInputRef?.current?.click()}
|
||||
>
|
||||
{dataCommitmentFile ? 'Upload this file' : 'Choose data commitment file'}
|
||||
</button>
|
||||
</FormFile>}
|
||||
</div>}
|
||||
{doesDataCommitmentExist === true &&
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => api(APIEndPoints.DownloadDataCommitment, { datasetId })}>
|
||||
<div className="flex gap-2 items-center">
|
||||
<FontAwesomeIcon icon={faFileAlt} /> <span className="text-sm">Data Commitment</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="flex gap-2 items-center">
|
||||
{(rows && columns)
|
||||
? <><FontAwesomeIcon icon={faColumns} /> <span className="text-sm">{rows} rows x {columns} columns</span></>
|
||||
: <><FontAwesomeIcon icon={faColumns} className="text-red-400"/> <span className="text-sm text-red-400">Missing number of rows or columns</span></>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DatasetPage;
|
||||
@@ -1,9 +1,9 @@
|
||||
import Button, { ButtonVariant } from "@/components/common/Button";
|
||||
import Hero from "@/components/common/Hero";
|
||||
import Hero, { SmallHero } from "@/components/common/Hero";
|
||||
import Layout from "@/components/common/Layout";
|
||||
import NavBar from "@/components/common/NavBar";
|
||||
import { Dataset, convertDatasetResponseToDataset } from "@/types/dataset";
|
||||
import { FullRequest, convertRawRequestToFullRequest } from "@/types/request";
|
||||
import { Breadcrumb, FullRequest, convertRawRequestToFullRequest } from "@/types/request";
|
||||
import { APIEndPoints, api } from "@/utils/api";
|
||||
import { useUser } from "@/utils/session";
|
||||
import { GetServerSideProps } from "next";
|
||||
@@ -12,7 +12,9 @@ import SubmitResultModal from "../../../../components/requests/SubmitResultModal
|
||||
import ReviewResultModal from "@/components/requests/ReviewResultModal";
|
||||
import Link from "next/link";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFileAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheckCircle, faCircleCheck, faFileAlt, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { extractResult } from "@/utils/ezkl";
|
||||
import { generateVerifierNotebook } from "@/utils/notebook";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const { id, requestId } = context.params as {
|
||||
@@ -52,7 +54,9 @@ type RequestDetailProps = {
|
||||
const RequestDetail = (props: RequestDetailProps) => {
|
||||
const { dataset, request } = props;
|
||||
const { id, title, description, testDataUrl, sourceDescription, acknowledgement, ownerId } = dataset;
|
||||
const { isAccepted, isCompleted } = request;
|
||||
const { isAccepted, isCompleted, resultApproved } = request;
|
||||
const [ isReviewResultModalOpen, setIsReviewResultModalOpen ] = useState<boolean>(false);
|
||||
const [ submitResultModalOpen, setSubmitResultModalOpen ] = useState<boolean>(false);
|
||||
|
||||
const [statusColor, setStatusColor] = useState<string>("bg-gray-400");
|
||||
const [statusText, setStatusText] = useState<string>("Awaiting Confirmation")
|
||||
@@ -61,20 +65,58 @@ const RequestDetail = (props: RequestDetailProps) => {
|
||||
const isDatasetOwner = ownerId === user?.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (resultApproved) {
|
||||
setStatusColor('bg-green-800');
|
||||
setStatusText('Request Completed');
|
||||
return;
|
||||
}
|
||||
if (isCompleted) {
|
||||
setStatusColor('bg-green-400');
|
||||
setStatusText('Results Ready');
|
||||
return;
|
||||
}
|
||||
if (isAccepted) {
|
||||
setStatusColor('bg-yellow-400');
|
||||
setStatusColor('bg-yellow-400 text-gray-900');
|
||||
setStatusText('Confirmed, Awaiting Results');
|
||||
}
|
||||
}, [isCompleted, isAccepted])
|
||||
|
||||
const onVerifierNotebookDownload = async () => {
|
||||
const notebook = await generateVerifierNotebook(request.code);
|
||||
const notebookBlob = new Blob([notebook], { type: 'application/x-ipynb+json' });
|
||||
const url = URL.createObjectURL(notebookBlob);
|
||||
|
||||
console.log('verifier notebook gen')
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'verifier.ipynb'; // Set the desired file name
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
const breadcrumb: Array<Breadcrumb> = [{
|
||||
label: dataset.title,
|
||||
href: `/datasets/${dataset.id}`
|
||||
}]
|
||||
|
||||
const onAcceptResult = async () => {
|
||||
await api(APIEndPoints.ApproveResult, { requestId: request.id });
|
||||
setIsReviewResultModalOpen(false);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
const onAcceptRequest = async () => {
|
||||
await api(APIEndPoints.AcceptRequest, { requestId: request.id })
|
||||
// setRequest(data);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
const onNotebookDownload = async () => {
|
||||
try {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
await api(APIEndPoints.DownloadComputationNotebook, { requestId: request.id });
|
||||
}
|
||||
catch (error) {
|
||||
const data = await api(APIEndPoints.UploadNotebook, {
|
||||
@@ -82,29 +124,60 @@ const RequestDetail = (props: RequestDetailProps) => {
|
||||
code: request.code
|
||||
})
|
||||
if (data) {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
await api(APIEndPoints.DownloadComputationNotebook, { requestId: request.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmitResult = async ({
|
||||
result,
|
||||
proofFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}: onSubmitResultArgs) => {
|
||||
|
||||
|
||||
if ( proofFile && precalWitnessFile && settingsFile ) {
|
||||
|
||||
await api(APIEndPoints.UploadProofAndAssets, {
|
||||
requestId: request.id,
|
||||
proofFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}).then((data) => {
|
||||
api(APIEndPoints.SubmitRequestResult, {
|
||||
requestId: request.id,
|
||||
result,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// TODO handle errors
|
||||
}
|
||||
setSubmitResultModalOpen(false);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
<NavBar />
|
||||
<Hero header={ title } subheader={ description }/>
|
||||
<Layout>
|
||||
<div className="text-2xl font-bold">
|
||||
Request: {request.title}
|
||||
<div>
|
||||
<div
|
||||
className={`text-sm ${statusColor} rounded px-2 text-white inline-block`}
|
||||
>
|
||||
{statusText}
|
||||
</div>
|
||||
<SmallHero header={
|
||||
<div className="flex gap-4 items-center">
|
||||
{ request.title }
|
||||
<div
|
||||
className={`text-sm ${statusColor} rounded px-2 text-white inline-block`}
|
||||
>
|
||||
{statusText}
|
||||
</div>
|
||||
</div>
|
||||
} subheader={ request.description } breadcrumb={breadcrumb}/>
|
||||
{isReviewResultModalOpen &&
|
||||
<ReviewResultModal onClose={() => setIsReviewResultModalOpen(false)} onAccept={onAcceptResult} result={request.result}/>
|
||||
}
|
||||
{submitResultModalOpen &&
|
||||
<SubmitResultModal onClose={() => setSubmitResultModalOpen(false)} onSubmit={onSubmitResult}/>
|
||||
}
|
||||
<Layout>
|
||||
<div className="my-4">
|
||||
{isDatasetOwner
|
||||
|
||||
@@ -112,33 +185,76 @@ const RequestDetail = (props: RequestDetailProps) => {
|
||||
: <ConsumerActionBar request={request} />
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-row w-full gap-4">
|
||||
<div className="flex-grow">
|
||||
<div className="bg-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold">
|
||||
Request Code
|
||||
<div className="bg-gray-100 rounded text-sm font-light p-2 font-mono whitespace-pre-line">
|
||||
{request.code}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col bg-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold">
|
||||
{isDatasetOwner ? 'Submitted Result' : 'Result'}
|
||||
<div className="flex flex-col w-full gap-4">
|
||||
<div className="flex flex-row w-full gap-4">
|
||||
|
||||
{isCompleted &&
|
||||
<div className="flex flex-col bg-gray-50 border-1 border-gray-200 rounded my-4 p-4 text-indigo-800 w-2/3">
|
||||
<div className="flex items-center font-bold text-lg">
|
||||
{isDatasetOwner ? 'Submitted Result' : 'Result'} {request.resultApproved ? <span className="text-xs text-green-900"> <FontAwesomeIcon icon={faCheckCircle} /> (Verified)</span> : ""}
|
||||
</div>
|
||||
<div className="bg-gray-100 rounded text-sm font-light p-2">
|
||||
{request.result}
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<div className="flex mt-4 gap-2">
|
||||
{!isDatasetOwner &&
|
||||
<Button
|
||||
variant={ButtonVariant.QUARTERY}
|
||||
onClick={() => setIsReviewResultModalOpen(true)}
|
||||
>
|
||||
Review Result
|
||||
</Button>
|
||||
}
|
||||
<Button
|
||||
disabled={!request.isCompleted}
|
||||
variant={request.isCompleted ? ButtonVariant.PRIMARY : ButtonVariant.DISABLED}
|
||||
>Verify Computation</Button>
|
||||
</div>
|
||||
variant={request.isCompleted ? ButtonVariant.QUARTERY : ButtonVariant.DISABLED}
|
||||
onClick={onVerifierNotebookDownload}
|
||||
>Download Verification Notebook</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{isCompleted &&
|
||||
<div className="w-1/3">
|
||||
<CryptographicAssets datasetId={dataset.id} requestId={request.id} resultReady={request.isCompleted}/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 border-1 border-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold text-indigo-800 my-2">
|
||||
Request Code
|
||||
</div>
|
||||
<div className="bg-gray-100 rounded text-sm font-light p-6 font-mono whitespace-pre-line text-gray-800 border-1 border-gray-200">
|
||||
{request.code}
|
||||
</div>
|
||||
<div className="flex gap-2 mt-4 mb-2">
|
||||
{(isDatasetOwner && !isAccepted) &&
|
||||
<div>
|
||||
<Button
|
||||
variant={ButtonVariant.QUARTERY}
|
||||
onClick={onAcceptRequest}
|
||||
>
|
||||
Accept Computation Request
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
{(isDatasetOwner && isAccepted && !isCompleted) &&
|
||||
<div>
|
||||
<Button
|
||||
variant={ButtonVariant.QUARTERY}
|
||||
onClick={() => setSubmitResultModalOpen(true)}
|
||||
>
|
||||
Submit Result
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<Button
|
||||
onClick={onNotebookDownload}
|
||||
variant={ButtonVariant.QUARTERY}
|
||||
>Download Jupyter Notebook</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-1/4">
|
||||
<CryptographicAssets requestId={request.id} resultReady={request.isCompleted}/>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
@@ -152,21 +268,16 @@ type ActionBarProps = {
|
||||
}
|
||||
|
||||
type onSubmitResultArgs = {
|
||||
result: string;
|
||||
proofFile: File | null, // TODO remove null
|
||||
vkFile: File | null, // TODO remove null
|
||||
modelOnnxFile: File | null, // TODO remove null
|
||||
srsFile: File | null, // TODO remove null
|
||||
precalWitnessFile: File | null, // TODO remove null
|
||||
settingsFile: File | null, // TODO remove null
|
||||
}
|
||||
|
||||
const extractResult = (proofFile: File) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const OwnerActionBar = (props: ActionBarProps) => {
|
||||
const { datasetId, request: initialRequest } = props;
|
||||
const [ request, setRequest ] = useState<FullRequest>(initialRequest);
|
||||
const { isAccepted } = request;
|
||||
const { isAccepted, isCompleted, resultApproved } = request;
|
||||
const [ submitResultModalOpen, setSubmitResultModalOpen ] = useState<boolean>(false);
|
||||
|
||||
const onAcceptRequest = async () => {
|
||||
@@ -185,90 +296,71 @@ const OwnerActionBar = (props: ActionBarProps) => {
|
||||
// TODO don't upload srsFile, make them select from a list
|
||||
|
||||
const onSubmitResult = async ({
|
||||
result,
|
||||
proofFile,
|
||||
vkFile,
|
||||
modelOnnxFile,
|
||||
srsFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}: onSubmitResultArgs) => {
|
||||
// TODO extract results instead (and maybe upload?? or don't??)
|
||||
// await api(APIEndPoints.SubmitRequestResult, { requestId: request.id, result })
|
||||
|
||||
if ( proofFile && vkFile && modelOnnxFile && srsFile && settingsFile ) {
|
||||
|
||||
if ( proofFile && precalWitnessFile && settingsFile ) {
|
||||
|
||||
await api(APIEndPoints.UploadProofAndAssets, {
|
||||
datasetId,
|
||||
requestId: request.id,
|
||||
proofFile,
|
||||
vkFile,
|
||||
modelOnnxFile,
|
||||
srsFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}).then((data) => {
|
||||
api(APIEndPoints.SubmitRequestResult, {
|
||||
requestId: request.id,
|
||||
result,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// TODO handle errors
|
||||
}
|
||||
setSubmitResultModalOpen(false);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
const onNotebookDownload = async () => {
|
||||
try {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
}
|
||||
catch (error) {
|
||||
const data = await api(APIEndPoints.UploadNotebook, {
|
||||
requestId: request.id,
|
||||
code: request.code
|
||||
})
|
||||
if (data) {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{submitResultModalOpen &&
|
||||
<SubmitResultModal onClose={() => setSubmitResultModalOpen(false)} onSubmit={onSubmitResult}/>
|
||||
}
|
||||
<div
|
||||
className="flex gap-4"
|
||||
>
|
||||
{!isAccepted &&
|
||||
<div>
|
||||
<Button
|
||||
onClick={onAcceptRequest}
|
||||
>
|
||||
Accept Computation Request
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
{isAccepted &&
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => setSubmitResultModalOpen(true)}
|
||||
>Submit Result</Button>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<Button
|
||||
onClick={onNotebookDownload}
|
||||
variant={ButtonVariant.SECONDARY}
|
||||
>Download Jupyter Notebook</Button>
|
||||
</div>
|
||||
{!isAccepted &&
|
||||
<div className="flex w-full rounded-lg bg-gray-500 py-8 px-6 text-stone-50">
|
||||
This computation request is awaiting your review.
|
||||
</div>
|
||||
}
|
||||
{(isAccepted && !isCompleted) &&
|
||||
<div className="flex w-full rounded-lg bg-yellow-400 py-8 px-6 text-stone-50 font-bold">
|
||||
You have accepted this computation request. Please download the Jupyter Notebook and follow the instructions to run the computation.
|
||||
</div>
|
||||
}
|
||||
{(isCompleted && !resultApproved) &&
|
||||
<div className="flex w-full rounded-lg bg-green-400 py-8 px-6 text-stone-50 font-bold">
|
||||
You submitted the result for this computation and are currently awaiting verification.
|
||||
</div>
|
||||
}
|
||||
{(resultApproved) &&
|
||||
<div className="flex w-full rounded-lg bg-green-800 py-8 px-6 text-stone-50 font-bold">
|
||||
This request has been completed.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ConsumerActionBar = (props: ActionBarProps) => {
|
||||
const { request } = props;
|
||||
const { isCompleted } = request;
|
||||
const { isCompleted, isAccepted, resultApproved } = request;
|
||||
const [ isReviewResultModalOpen, setIsReviewResultModalOpen ] = useState<boolean>(false);
|
||||
|
||||
const onNotebookDownload = async () => {
|
||||
try {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
await api(APIEndPoints.DownloadComputationNotebook, { requestId: request.id });
|
||||
}
|
||||
catch (error) {
|
||||
const data = await api(APIEndPoints.UploadNotebook, {
|
||||
@@ -276,27 +368,66 @@ const ConsumerActionBar = (props: ActionBarProps) => {
|
||||
code: request.code
|
||||
})
|
||||
if (data) {
|
||||
await api(APIEndPoints.DownloadNotebook, { requestId: request.id });
|
||||
await api(APIEndPoints.DownloadComputationNotebook, { requestId: request.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onAcceptResult = () => {
|
||||
|
||||
const onAcceptResult = async () => {
|
||||
await api(APIEndPoints.ApproveResult, { requestId: request.id });
|
||||
setIsReviewResultModalOpen(false);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
const onClickVerifyComputation = () => {
|
||||
|
||||
}
|
||||
|
||||
const onVerifierNotebookDownload = async () => {
|
||||
const notebook = await generateVerifierNotebook(request.code);
|
||||
const notebookBlob = new Blob([notebook], { type: 'application/x-ipynb+json' });
|
||||
const url = URL.createObjectURL(notebookBlob);
|
||||
|
||||
console.log('verifier notebook gen')
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'verifier.ipynb'; // Set the desired file name
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex gap-4"
|
||||
>
|
||||
{!isAccepted &&
|
||||
<div className="flex w-full rounded-lg bg-gray-500 py-8 px-6 text-stone-50">
|
||||
The owner of this dataset has not reviewed this request yet.
|
||||
</div>
|
||||
}
|
||||
{(isAccepted && !isCompleted) &&
|
||||
<div className="flex w-full rounded-lg bg-yellow-400 py-8 px-6 text-gray-900 font-bold">
|
||||
This request has been accepted by the dataset owner. The result and proof will be uploaded here later.
|
||||
</div>
|
||||
}
|
||||
{(isCompleted && !resultApproved) &&
|
||||
<div className="flex w-full rounded-lg bg-green-400 py-8 px-6 text-gray-50 font-bold items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCircleCheck} /> This dataset owner has submitted the result and is awaiting a review.
|
||||
</div>
|
||||
}
|
||||
{resultApproved &&
|
||||
<div className="flex w-full rounded-lg bg-green-800 py-8 px-6 text-gray-50 font-bold items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCircleCheck} /> This request has been completed.
|
||||
</div>
|
||||
}
|
||||
{isReviewResultModalOpen &&
|
||||
<ReviewResultModal onClose={() => setIsReviewResultModalOpen(false)} onAccept={onAcceptResult} result={request.result}/>
|
||||
}
|
||||
{isCompleted &&
|
||||
{/* {isCompleted &&
|
||||
<>
|
||||
<div>
|
||||
<Button
|
||||
@@ -305,41 +436,101 @@ const ConsumerActionBar = (props: ActionBarProps) => {
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
onClick={onClickVerifyComputation}
|
||||
onClick={onVerifierNotebookDownload}
|
||||
>
|
||||
Verify Computation
|
||||
Download Verification Notebook
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<div>
|
||||
<Button
|
||||
onClick={onNotebookDownload}
|
||||
variant={ButtonVariant.SECONDARY}
|
||||
>Download Jupyter Notebook</Button>
|
||||
</div>
|
||||
} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type CryptographicAssetsProps = {
|
||||
datasetId: string;
|
||||
requestId: string;
|
||||
resultReady?: boolean;
|
||||
}
|
||||
|
||||
const CryptographicAssets = ({ requestId, resultReady = true } : CryptographicAssetsProps) => {
|
||||
const CryptographicAssets = ({ datasetId, requestId, resultReady = true } : CryptographicAssetsProps) => {
|
||||
const [ doesDataCommitmentExist, setDoesDataCommitmentExist ] = useState<boolean | null>(null);
|
||||
const [ doesProofExist, setDoesProofExist ] = useState<boolean | null>(null);
|
||||
const [ doesPrecalWitnessExist, setDoesPrecalWitnessExist ] = useState<boolean | null>(null);
|
||||
const [ doSettingsExist, setDoSettingsExist ] = useState<boolean | null>(null);
|
||||
useEffect(() => {
|
||||
api(APIEndPoints.CheckFileExists, {
|
||||
bucketName: 'proof_assets',
|
||||
filePath: `${datasetId}`,
|
||||
fileName: 'data_commitment.json'
|
||||
}).then(result => setDoesDataCommitmentExist(result))
|
||||
api(APIEndPoints.CheckFileExists, {
|
||||
bucketName: 'proof_assets',
|
||||
filePath: `${datasetId}/${requestId}`,
|
||||
fileName: 'model.pf'
|
||||
}).then(result => setDoesProofExist(result))
|
||||
api(APIEndPoints.CheckFileExists, {
|
||||
bucketName: 'proof_assets',
|
||||
filePath: `${datasetId}/${requestId}`,
|
||||
fileName: 'precal_witness.json'
|
||||
}).then(result => setDoesPrecalWitnessExist(result))
|
||||
api(APIEndPoints.CheckFileExists, {
|
||||
bucketName: 'proof_assets',
|
||||
filePath: `${datasetId}/${requestId}`,
|
||||
fileName: 'settings.json'
|
||||
}).then(result => setDoSettingsExist(result))
|
||||
})
|
||||
return (
|
||||
<div className="bg-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold">
|
||||
<div className="bg-gray-50 border-1 border-gray-200 rounded my-4 p-4">
|
||||
<div className="text-lg font-bold text-indigo-800 my-2">
|
||||
Cryptographic Assets
|
||||
</div>
|
||||
{resultReady ? (
|
||||
<div className="flex flex-col">
|
||||
<Link href=""><FontAwesomeIcon icon={faFileAlt} /> Proof</Link>
|
||||
<Link href=""><FontAwesomeIcon icon={faFileAlt} /> Verification Key</Link>
|
||||
<Link href=""><FontAwesomeIcon icon={faFileAlt} /> Model Onnx</Link>
|
||||
<Link href=""><FontAwesomeIcon icon={faFileAlt} /> Srs</Link>
|
||||
<Link href=""><FontAwesomeIcon icon={faFileAlt} /> Settings</Link>
|
||||
{doesDataCommitmentExist
|
||||
? <div
|
||||
className="hover:cursor-pointer"
|
||||
onClick={() => api(APIEndPoints.DownloadDataCommitment, { datasetId })}><FontAwesomeIcon icon={faFileAlt}
|
||||
/> Data Commitment</div>
|
||||
: <div>
|
||||
{doesDataCommitmentExist === null
|
||||
? <span><FontAwesomeIcon icon={faFileAlt} /> Checking...</span>
|
||||
: <span className="text-red-400"><FontAwesomeIcon icon={faWarning} /> Missing data commitment</span>}
|
||||
</div>
|
||||
}
|
||||
{doesProofExist
|
||||
? <div
|
||||
className="hover:cursor-pointer"
|
||||
onClick={() => api(APIEndPoints.DownloadProofAndAssets, {datasetId, requestId, file: 'proof'})}
|
||||
><FontAwesomeIcon icon={faFileAlt} /> Proof</div>
|
||||
: <div>
|
||||
{doesProofExist === null
|
||||
? <span><FontAwesomeIcon icon={faFileAlt} /> Checking...</span>
|
||||
: <span className="text-red-400"><FontAwesomeIcon icon={faWarning} /> Missing proof</span>}
|
||||
</div>
|
||||
}
|
||||
{doesPrecalWitnessExist
|
||||
? <div
|
||||
className="hover:cursor-pointer"
|
||||
onClick={() => api(APIEndPoints.DownloadProofAndAssets, {datasetId, requestId, file: 'precal'})}
|
||||
><FontAwesomeIcon icon={faFileAlt} /> Precal witness</div>
|
||||
: <div>
|
||||
{doesPrecalWitnessExist === null
|
||||
? <span><FontAwesomeIcon icon={faFileAlt} /> Checking...</span>
|
||||
: <span className="text-red-400"><FontAwesomeIcon icon={faWarning} /> Missing precal witness</span>}
|
||||
</div>
|
||||
}
|
||||
{doSettingsExist
|
||||
? <div
|
||||
className="hover:cursor-pointer"
|
||||
onClick={() => api(APIEndPoints.DownloadProofAndAssets, {datasetId, requestId, file: 'settings'})}
|
||||
><FontAwesomeIcon icon={faFileAlt} /> Settings</div>
|
||||
: <div>
|
||||
{doSettingsExist === null
|
||||
? <span><FontAwesomeIcon icon={faFileAlt} /> Checking...</span>
|
||||
: <span className="text-red-400"><FontAwesomeIcon icon={faWarning} /> Missing settings</span>}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
|
||||
@@ -2,19 +2,20 @@ import { FormInput } from "@/components/common/Form";
|
||||
import Hero from "@/components/common/Hero";
|
||||
import Layout from "@/components/common/Layout";
|
||||
import { DatasetCard } from "@/components/datasets/DatasetCard";
|
||||
import { DatasetPreview } from "@/types/dataset";
|
||||
import { Dataset, DatasetPreview, DatasetResponse, convertDatasetResponseToDataset } from "@/types/dataset";
|
||||
import { APIEndPoints, api } from "@/utils/api";
|
||||
import { useUser } from "@/utils/session";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const Datasets = () => {
|
||||
const [ datasets, setDatasets ] = useState<Array<DatasetPreview>>([]);
|
||||
const [ filteredDatasets, setFilteredDatasets ] = useState<Array<DatasetPreview>>([]);
|
||||
const [ datasets, setDatasets ] = useState<Array<Dataset>>([]);
|
||||
const [ filteredDatasets, setFilteredDatasets ] = useState<Array<Dataset>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
api(APIEndPoints.Datasets).then((response) => {
|
||||
setDatasets(response);
|
||||
setFilteredDatasets(response);
|
||||
const convertedDatasets = response.map((dataset: DatasetResponse) => convertDatasetResponseToDataset(dataset))
|
||||
setDatasets(convertedDatasets);
|
||||
setFilteredDatasets(convertedDatasets);
|
||||
})
|
||||
|
||||
}, [setDatasets])
|
||||
@@ -22,8 +23,9 @@ const Datasets = () => {
|
||||
const user = useUser();
|
||||
|
||||
const DatasetSearchBar = (
|
||||
<div className="mx-auto w-2/4">
|
||||
<div className="w-3/4">
|
||||
<FormInput
|
||||
placeholder="Dataset name, column name, etc."
|
||||
onChange={(e) => {
|
||||
setFilteredDatasets(
|
||||
datasets.filter(dataset =>
|
||||
@@ -37,15 +39,15 @@ const Datasets = () => {
|
||||
return (
|
||||
<div>
|
||||
<Hero
|
||||
header='Datasets'
|
||||
subheader="Search for a dataset to explore"
|
||||
header='Browse datasets'
|
||||
subheader="Explore datasets from our community. Get your questions about the data answered."
|
||||
action={DatasetSearchBar}
|
||||
/>
|
||||
{datasets && (
|
||||
<Layout>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{filteredDatasets.map((dataset) => (
|
||||
<DatasetCard previewImage='' dataset={dataset} key={dataset.id} />
|
||||
<DatasetCard dataset={dataset} key={dataset.id} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import FormWrapper, { FormFile, FormInput, FormItem, FormRadioSelect, FormTextAr
|
||||
import Hero from "@/components/common/Hero";
|
||||
import Layout from "@/components/common/Layout";
|
||||
import NavBar from "@/components/common/NavBar";
|
||||
import { Dataset } from "@/types/dataset";
|
||||
import { Dataset, Schema } from "@/types/dataset";
|
||||
import { APIEndPoints, api } from "@/utils/api";
|
||||
import { generateDataCommitment, initialize } from "@/utils/ezkl";
|
||||
import { useUser } from "@/utils/session";
|
||||
@@ -35,13 +35,20 @@ enum GenerateDataCommitmentOptions {
|
||||
BROWSER = 'browser',
|
||||
}
|
||||
|
||||
enum SchemaOptions {
|
||||
TEXT = 'text',
|
||||
JSON_FILE = 'json_file'
|
||||
}
|
||||
|
||||
const UploadDataset = () => {
|
||||
|
||||
const [ selectedDataCommitmentOption, setSelectedDataCommitmentOption ] = useState<GenerateDataCommitmentOptions>(GenerateDataCommitmentOptions.NOTEBOOK)
|
||||
const [ schemaOption, setSchemaOption ] = useState<SchemaOptions>(SchemaOptions.TEXT);
|
||||
const [ dataFile, setDataFile ] = useState<File | null>();
|
||||
const [ dataCommitmentFile, setDataCommitmentFile ] = useState<File | null>();
|
||||
const [ redirecting, setRedirecting ] = useState<boolean>(false);
|
||||
const [ datasetId, setDatasetId ] = useState<string | null>();
|
||||
const [ schema, setSchema ] = useState<Schema | null>(null);
|
||||
|
||||
const { register, handleSubmit, formState } = useForm<Dataset>();
|
||||
const { errors } = formState;
|
||||
@@ -61,6 +68,9 @@ const UploadDataset = () => {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
ownerId: user?.id,
|
||||
schema,
|
||||
rows: data.rows,
|
||||
columns: data.columns,
|
||||
})
|
||||
if (dataCommitmentFile) {
|
||||
await api(APIEndPoints.UploadDataCommitment, {
|
||||
@@ -110,6 +120,30 @@ const UploadDataset = () => {
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div
|
||||
className="mb-2 text-md font-medium text-gray-900"
|
||||
>
|
||||
Number of columns in this dataset
|
||||
</div>
|
||||
<FormInput
|
||||
errors={errors}
|
||||
errorMessage={'Just a test error'}
|
||||
{...register('rows', { required: false })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<div
|
||||
className="mb-2 text-md font-medium text-gray-900"
|
||||
>
|
||||
Number of rows in this dataset (excluding the headers)
|
||||
</div>
|
||||
<FormInput
|
||||
errors={errors}
|
||||
errorMessage={'Just a test error'}
|
||||
{...register('columns', { required: false })}
|
||||
/>
|
||||
</FormItem>
|
||||
</Card>
|
||||
<Card>
|
||||
<FormItem>
|
||||
@@ -118,11 +152,45 @@ const UploadDataset = () => {
|
||||
>
|
||||
Data Schema
|
||||
</div>
|
||||
<FormTextArea
|
||||
errors={errors}
|
||||
errorMessage="text area error"
|
||||
{...register('schema', { required: true })}
|
||||
/>
|
||||
<FormRadioSelect
|
||||
options={[
|
||||
{
|
||||
value: SchemaOptions.TEXT,
|
||||
label: 'Paste the JSON schema',
|
||||
},
|
||||
{
|
||||
value: SchemaOptions.JSON_FILE,
|
||||
label: 'Upload JSON schema file',
|
||||
},
|
||||
]}
|
||||
onChange={(o) => setSchemaOption(o.value as SchemaOptions)} // TODO fix
|
||||
/>
|
||||
{schemaOption === SchemaOptions.TEXT &&
|
||||
<FormTextArea
|
||||
errors={errors}
|
||||
errorMessage="text area error"
|
||||
{...register('schema', { required: true })}
|
||||
/>
|
||||
}
|
||||
{schemaOption === SchemaOptions.JSON_FILE &&
|
||||
<FormFile onChange={(e) => {
|
||||
// TODO also option to copy paste
|
||||
if (e.target.files) {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
//@ts-ignore
|
||||
const json = JSON.parse(event.target.result);
|
||||
setSchema(json);
|
||||
} catch (error) {
|
||||
console.error("Error parsing JSON:", error);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}} />
|
||||
}
|
||||
</FormItem>
|
||||
</Card>
|
||||
<Card>
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
@import 'tailwindcss/utilities';
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
|
||||
|
||||
body {
|
||||
background-color: rgb(238 242 255);
|
||||
}
|
||||
|
||||
/* Custom styles for the hover underline effect */
|
||||
.menu-item::after {
|
||||
content: '';
|
||||
|
||||
371
public/templates/prover.ipynb
Normal file
371
public/templates/prover.ipynb
Normal file
File diff suppressed because one or more lines are too long
@@ -1,185 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install zkstats\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"from zkstats.core import (\n",
|
||||
" prover_gen_settings,\n",
|
||||
" prover_gen_proof,\n",
|
||||
" verifier_setup,\n",
|
||||
" verifier_verify,\n",
|
||||
")\n",
|
||||
"from zkstats.computation import computation_to_model\n",
|
||||
"\n",
|
||||
"cwd = os.getcwd()\n",
|
||||
"\n",
|
||||
"# FIXME: fill this in with the path to your data\n",
|
||||
"data_path = f\"{cwd}/data.json\"\n",
|
||||
"\n",
|
||||
"# Paths to the output files\n",
|
||||
"output_dir = f\"{cwd}/out\"\n",
|
||||
"os.makedirs(output_dir, exist_ok=True)\n",
|
||||
"model_onnx_path = f\"{output_dir}/model.onnx\"\n",
|
||||
"compiled_model_path = f\"{output_dir}/model.compiled\"\n",
|
||||
"\n",
|
||||
"pk_path = f\"{output_dir}/model.pk\"\n",
|
||||
"vk_path = f\"{output_dir}/model.vk\"\n",
|
||||
"proof_path = f\"{output_dir}/model.pf\"\n",
|
||||
"settings_path = f\"{output_dir}/settings.json\"\n",
|
||||
"witness_path = f\"{output_dir}/witness.json\"\n",
|
||||
"comb_data_path = f\"{output_dir}/comb_data.json\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## User-defined Computation\n",
|
||||
"\n",
|
||||
"A computation should be of type `TComputation`. For example, the following code snippet defines a computation that computes the sum of the private data.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"def computation(state: State, x: list[torch.Tensor]):\n",
|
||||
" out_0 = state.median(x[0])\n",
|
||||
" out_1 = state.median(x[1])\n",
|
||||
" return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"FIXME: The following code snippet is entirely from the user. You MUST check\n",
|
||||
"1. the code only performs zkstats-related operations.\n",
|
||||
"2. the computation must not leak any information about the private data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is just a dummy computation. Replace it with user's computation\n",
|
||||
"#\n",
|
||||
"# import torch\n",
|
||||
"# from zkstats.computation import State\n",
|
||||
"#\n",
|
||||
"# def computation(state: State, x: list[torch.Tensor]):\n",
|
||||
"# x_0 = x[0]\n",
|
||||
"# out_0 = state.median(x_0)\n",
|
||||
"# out_1 = state.median(x_0)\n",
|
||||
"# return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Generate settings and setup with user's computation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_, model = computation_to_model(computation)\n",
|
||||
"prover_gen_settings(\n",
|
||||
" [data_path],\n",
|
||||
" comb_data_path,\n",
|
||||
" model,\n",
|
||||
" model_onnx_path,\n",
|
||||
" \"default\",\n",
|
||||
" \"resources\",\n",
|
||||
" settings_path,\n",
|
||||
")\n",
|
||||
"verifier_setup(model_onnx_path, compiled_model_path, settings_path, vk_path, pk_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Generate proof with your data and user's computation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prover_gen_proof(\n",
|
||||
" model_onnx_path,\n",
|
||||
" comb_data_path,\n",
|
||||
" witness_path,\n",
|
||||
" compiled_model_path,\n",
|
||||
" settings_path,\n",
|
||||
" proof_path,\n",
|
||||
" pk_path,\n",
|
||||
")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Verify the proof to ensure it is correct"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 43,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"verifier_verify(proof_path, settings_path, vk_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the file paths. You should share the following files back to the user for them to verify the proof. You **SHOULD NOT** share more files otherwise data might be leaked."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model_onnx_path, settings_path, proof_path, vk_path\n",
|
||||
"print(\"Model onnx:\\t\\t\", model_onnx_path)\n",
|
||||
"print(\"Settings:\\t\\t\", settings_path)\n",
|
||||
"print(\"Proof:\\t\\t\\t\", proof_path)\n",
|
||||
"print(\"Verification key:\\t\", vk_path)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
255
public/templates/verifier.ipynb
Normal file
255
public/templates/verifier.ipynb
Normal file
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import os\n",
|
||||
"from zkstats.core import (\n",
|
||||
" setup,\n",
|
||||
")\n",
|
||||
"from zkstats.computation import computation_to_model\n",
|
||||
"\n",
|
||||
"cwd = os.getcwd()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Paths to the output files\n",
|
||||
"output_dir = f\"{cwd}/out\"\n",
|
||||
"os.makedirs(output_dir, exist_ok=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Shared by the data provider beforehand\n",
|
||||
"data_shape = {'x': 7, 'y': 7}\n",
|
||||
"data_commitment_path = f\"{output_dir}/data_commitment.json\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"User select the columns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# FIXME: this should be provided by users\n",
|
||||
"# selected_columns = [\"x\", \"y\"]\n",
|
||||
"selected_columns = [\"x\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## User-defined Computation\n",
|
||||
"\n",
|
||||
"A computation should be of type `TComputation`. For example, the following code snippet defines a computation that computes the sum of the private data.\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"def computation(state: State, x: list[torch.Tensor]):\n",
|
||||
" out_0 = state.median(x[0])\n",
|
||||
" out_1 = state.median(x[1])\n",
|
||||
" return state.mean(torch.cat([out_0.unsqueeze(0), out_1.unsqueeze(0)]).reshape(-1,1))\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"FIXME: The following code snippet is entirely from the user. You MUST check\n",
|
||||
"1. the code only performs zkstats-related operations.\n",
|
||||
"2. the computation must not leak any information about the private data."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# This is just a dummy computation. Replace it with user's computation\n",
|
||||
"import torch\n",
|
||||
"from zkstats.computation import State\n",
|
||||
"\n",
|
||||
"def computation(state: State, x: list[torch.Tensor]):\n",
|
||||
" # out_0 = state.median(x[0])\n",
|
||||
" # out_1 = state.median(x[1])\n",
|
||||
" # # return state.mean(torch.cat([out_0.unsqueeze(0), out_1.unsqueeze(0)]).reshape(-1,1)), out_0\n",
|
||||
" # return out_0, out_1\n",
|
||||
" return state.mean(x[0]), state.median(x[0])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Get proof, settings, and precal_witness from the data provider"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"proof_path = f\"{output_dir}/model.pf\"\n",
|
||||
"settings_path = f\"{output_dir}/settings.json\"\n",
|
||||
"precal_witness_path = f\"{output_dir}/precal_witness.json\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Verify the proof to ensure it is correct\n",
|
||||
"NOTE: The following section is to illustrate what should be done on the user (data consumer) side. This step is not required by the data provider."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages/torch/onnx/symbolic_opset9.py:2174: FutureWarning: 'torch.onnx.symbolic_opset9._cast_Bool' is deprecated in version 2.0 and will be removed in the future. Please Avoid using this function and create a Cast node instead.\n",
|
||||
" return fn(g, to_cast_func(g, input, False), to_cast_func(g, other, False))\n",
|
||||
"/Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages/torch/onnx/utils.py:1703: UserWarning: The exported ONNX model failed ONNX shape inference. The model will not be executable by the ONNX Runtime. If this is unintended and you believe there is a bug, please report an issue at https://github.com/pytorch/pytorch/issues. Error reported by strict ONNX shape inference: [ShapeInferenceError] (op_type:Where, node name: /Where_1): Y has inconsistent type tensor(float) (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/torch/csrc/jit/serialization/export.cpp:1490.)\n",
|
||||
" _C._check_onnx_proto(proto)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"==== setting up ezkl ====\n",
|
||||
"Time setup: 0.5691819190979004 seconds\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[51.5, 46.25]"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import numpy as np\n",
|
||||
"from typing import Dict\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def create_dummy(shape_info: Dict[str, int], dummy_data_path: str) -> None:\n",
|
||||
" \"\"\"\n",
|
||||
" Create a dummy data file with randomized data based on the provided shape information.\n",
|
||||
"\n",
|
||||
" Parameters:\n",
|
||||
" - shape_info (dict): A dictionary where keys are column names and values are the number of elements (shape).\n",
|
||||
" - dummy_data_path (str): The path to save the dummy data file.\n",
|
||||
" \"\"\"\n",
|
||||
" dummy_data = {}\n",
|
||||
" for col, length in shape_info.items():\n",
|
||||
" # Generate random data for each column\n",
|
||||
" dummy_data[col] = np.round(np.random.uniform(0, 100, length), 1).tolist()\n",
|
||||
"\n",
|
||||
" with open(dummy_data_path, 'w') as f:\n",
|
||||
" json.dump(dummy_data, f)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from zkstats.core import verifier_define_calculation, verifier_verify\n",
|
||||
"\n",
|
||||
"verifier_model_path = f\"{output_dir}/verifier_model.onnx\"\n",
|
||||
"verifier_compiled_model_path = f\"{output_dir}/verifier_model.compiled\"\n",
|
||||
"verifier_vk_path = f\"{output_dir}/verifier_model.vk\"\n",
|
||||
"verifier_pk_path = f\"{output_dir}/verifier_model.pk\"\n",
|
||||
"dummy_data_path = f\"{output_dir}/dummy_data.json\"\n",
|
||||
"sel_dummy_data_path = f\"{output_dir}/sel_dummy_data.json\"\n",
|
||||
"\n",
|
||||
"# NOTE: generate the verifier model with the `precal_witness_path` provided by the prover\n",
|
||||
"_, verifier_model = computation_to_model(computation, precal_witness_path, isProver=False)\n",
|
||||
"# Determine which srs to use with the logrows in the settings.json\n",
|
||||
"with open(settings_path, \"r\") as f:\n",
|
||||
" settings = json.load(f)\n",
|
||||
"logrows = int(settings[\"run_args\"][\"logrows\"])\n",
|
||||
"srs_path = f'~/.ezkl/srs/kzg{logrows}.srs'\n",
|
||||
"\n",
|
||||
"# create dummy data with the same shape as the original data\n",
|
||||
"create_dummy(data_shape, dummy_data_path)\n",
|
||||
"# generate the verifier model given the dummy data and the selected columns\n",
|
||||
"verifier_define_calculation(dummy_data_path, selected_columns, sel_dummy_data_path, verifier_model, verifier_model_path)\n",
|
||||
"# generate the verification key\n",
|
||||
"setup(verifier_model_path, verifier_compiled_model_path, settings_path, verifier_vk_path, verifier_pk_path)\n",
|
||||
"# verify the proof\n",
|
||||
"verifier_verify(proof_path, settings_path, verifier_vk_path, selected_columns, data_commitment_path)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Print the file paths. You should share the following files back to the user for them to verify the proof. You **SHOULD NOT** share more files otherwise data might be leaked."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model onnx:\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/verifier_model.onnx\n",
|
||||
"Settings:\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/settings.json\n",
|
||||
"Proof:\t\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/model.pf\n",
|
||||
"Verification key:\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/verifier_model.vk\n",
|
||||
"Srs path:\t\t ~/.ezkl/srs/kzg11.srs\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"Model onnx:\\t\\t\", verifier_model_path)\n",
|
||||
"print(\"Settings:\\t\\t\", settings_path)\n",
|
||||
"print(\"Proof:\\t\\t\\t\", proof_path)\n",
|
||||
"print(\"Verification key:\\t\", verifier_vk_path)\n",
|
||||
"print(\"Srs path:\\t\\t\", srs_path)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -13,6 +13,12 @@ const config: Config = {
|
||||
'gradient-conic':
|
||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||
},
|
||||
colors: {
|
||||
|
||||
},
|
||||
borderWidth: {
|
||||
"1": "1px",
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
@@ -11,7 +11,7 @@ export type DatasetPreview = {
|
||||
}
|
||||
|
||||
export type Dataset = DatasetPreview & {
|
||||
schema?: []; // TODO
|
||||
schema?: Record<string, any>;
|
||||
testDataUrl?: string;
|
||||
sourceDescription?: string;
|
||||
acknowledgement?: string;
|
||||
@@ -19,6 +19,8 @@ export type Dataset = DatasetPreview & {
|
||||
glossary?: []; // TODO
|
||||
ownerId?: string;
|
||||
//discussionId?: number; // No discussion id here, just make it a foreign key on the discussion table
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
export type UserDatasetRequests = Array<UserDatasetRequest>;
|
||||
@@ -35,6 +37,9 @@ export type DatasetResponse = {
|
||||
description: string;
|
||||
updated_at: string;
|
||||
owner_id: string;
|
||||
schema?: Record<string, any>;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
export const convertDatasetResponseToDataset = (datasetResponse: DatasetResponse): Dataset => {
|
||||
@@ -45,5 +50,22 @@ export const convertDatasetResponseToDataset = (datasetResponse: DatasetResponse
|
||||
description: datasetResponse.description,
|
||||
updatedAt: datasetResponse.updated_at,
|
||||
ownerId: datasetResponse.owner_id,
|
||||
schema: datasetResponse.schema,
|
||||
rows: datasetResponse.rows,
|
||||
columns: datasetResponse.columns,
|
||||
}
|
||||
}
|
||||
|
||||
export type Schema = {
|
||||
$schema: string;
|
||||
title: string;
|
||||
type: string;
|
||||
properties: {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
description?: string;
|
||||
format?: string;
|
||||
};
|
||||
};
|
||||
required: string[];
|
||||
}
|
||||
|
||||
@@ -53,3 +53,8 @@ export const convertRawRequestToFullRequest = (rawRequest: RawRequest): FullRequ
|
||||
resultApproved: rawRequest.result_approved,
|
||||
}
|
||||
}
|
||||
|
||||
export type Breadcrumb = {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
273
utils/api.tsx
273
utils/api.tsx
@@ -3,7 +3,8 @@
|
||||
import useMockData from "@/mocks";
|
||||
import { NewRequest } from "@/types/request";
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
import { generateJupyterNotebook } from "./notebook";
|
||||
import { generateProverNotebook } from "./notebook";
|
||||
import { Schema } from "@/types/dataset";
|
||||
|
||||
const useMock = process.env.NEXT_PUBLIC_USE_MOCK === 'true';
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
@@ -21,11 +22,17 @@ export enum APIEndPoints {
|
||||
UploadDataCommitment = 'upload_data_commitment',
|
||||
Requests = 'requests',
|
||||
|
||||
DownloadNotebook = 'download_notebook',
|
||||
DownloadComputationNotebook = 'download_computation_notebook',
|
||||
DownloadVerificationNotebook = 'download_verification_notebook',
|
||||
UploadNotebook = 'upload_noteobook',
|
||||
AcceptRequest = 'accept_request',
|
||||
SubmitRequestResult = 'submit_request_result',
|
||||
UploadProofAndAssets = 'upload_proof_and_assets',
|
||||
DownloadProofAndAssets = 'download_proof_and_assets',
|
||||
ApproveResult = 'accept_result',
|
||||
|
||||
CheckFileExists = 'check_file_exists',
|
||||
DownloadDataCommitment = 'download_data_commitment',
|
||||
|
||||
SignUp = 'signup',
|
||||
SignInWithPassword = 'siginin_with_password',
|
||||
@@ -41,6 +48,9 @@ type AddDatasetOptions = {
|
||||
title: string;
|
||||
description: string;
|
||||
ownerId: string;
|
||||
schema: Schema | null;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
}
|
||||
|
||||
type UploadDataCommitmentOptions = {
|
||||
@@ -77,7 +87,11 @@ type GetRequestsOptions = {
|
||||
datasetId: string;
|
||||
}
|
||||
|
||||
type DownloadNotebookOptions = {
|
||||
type DownloadComputationNotebookOptions = {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
type DownloadVerificationNotebookOptions = {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
@@ -99,12 +113,30 @@ type UploadProofAndAssetsOptions = {
|
||||
datasetId: string;
|
||||
requestId: string;
|
||||
proofFile: File,
|
||||
vkFile: File,
|
||||
modelOnnxFile: File,
|
||||
srsFile: File,
|
||||
precalWitnessFile: File,
|
||||
settingsFile: File,
|
||||
}
|
||||
|
||||
type DownloadProofAndAssetsOptions = {
|
||||
datasetId: string;
|
||||
requestId: string;
|
||||
file: 'all' | 'proof' | 'precal' | 'settings'
|
||||
}
|
||||
|
||||
type ApproveResultOptions = {
|
||||
requestId: string; // TODO make it so that only request sender can approve
|
||||
}
|
||||
|
||||
type CheckFileExistsOptions = {
|
||||
bucketName: string;
|
||||
filePath: string;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
type DownloadDataCommitmentOptions = {
|
||||
datasetId: string;
|
||||
}
|
||||
|
||||
export type Options = undefined
|
||||
| GetDatasetOptions
|
||||
| AddDatasetOptions
|
||||
@@ -114,9 +146,14 @@ export type Options = undefined
|
||||
| NewRequestOptions
|
||||
| GetRequestOptions
|
||||
| GetRequestsOptions
|
||||
| DownloadNotebookOptions
|
||||
| DownloadComputationNotebookOptions
|
||||
| DownloadVerificationNotebookOptions
|
||||
| SubmitRequestResultOptions
|
||||
| UploadProofAndAssetsOptions
|
||||
| DownloadProofAndAssetsOptions
|
||||
| ApproveResultOptions
|
||||
| CheckFileExistsOptions
|
||||
| DownloadDataCommitmentOptions
|
||||
|
||||
|
||||
export const api = async (
|
||||
@@ -138,11 +175,17 @@ export const api = async (
|
||||
case APIEndPoints.AddRequest: return addRequest(options as NewRequestOptions);
|
||||
case APIEndPoints.Requests: return getRequests(options as GetRequestsOptions);
|
||||
// Data owner operations
|
||||
case APIEndPoints.DownloadNotebook: return downloadNotebook(options as DownloadNotebookOptions);
|
||||
case APIEndPoints.DownloadComputationNotebook: return downloadComputationNotebook(options as DownloadComputationNotebookOptions);
|
||||
case APIEndPoints.DownloadVerificationNotebook: return downloadVerificationNotebook(options as DownloadVerificationNotebookOptions);
|
||||
case APIEndPoints.UploadNotebook: return uploadNotebook(options as UploadNotebookOptions);
|
||||
case APIEndPoints.AcceptRequest: return acceptRequest(options as AcceptRequestOptions);
|
||||
case APIEndPoints.SubmitRequestResult: return submitRequestResult(options as SubmitRequestResultOptions);
|
||||
case APIEndPoints.UploadProofAndAssets: return uploadProofAndAssets(options as UploadProofAndAssetsOptions); // TODO options
|
||||
case APIEndPoints.UploadProofAndAssets: return uploadProofAndAssets(options as UploadProofAndAssetsOptions);
|
||||
case APIEndPoints.DownloadProofAndAssets: return downloadProofAndAssets(options as DownloadProofAndAssetsOptions);
|
||||
case APIEndPoints.ApproveResult: return approveResult(options as ApproveResultOptions);
|
||||
// Cryptographic assets
|
||||
case APIEndPoints.CheckFileExists: return checkFileExists(options as CheckFileExistsOptions);
|
||||
case APIEndPoints.DownloadDataCommitment: return downloadDataCommitment(options as DownloadDataCommitmentOptions);
|
||||
// AUTH
|
||||
case APIEndPoints.SignUp: return signUp(options as SignUpOptions);
|
||||
case APIEndPoints.SignInWithPassword: return signInWithPassword(options as SignInWithPasswordOptions);
|
||||
@@ -179,6 +222,9 @@ const addDataset = async (options: AddDatasetOptions) => {
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
owner_id: options.ownerId,
|
||||
schema: options.schema,
|
||||
rows: options.rows,
|
||||
columns: options.columns
|
||||
})
|
||||
if (data) {
|
||||
return data;
|
||||
@@ -225,7 +271,7 @@ const addRequest = async (options: NewRequest) => {
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID();
|
||||
const notebook = await generateJupyterNotebook(options.code);
|
||||
const notebook = await generateProverNotebook(options.code);
|
||||
const notebookBlob = new Blob([notebook], { type: 'application/x-ipynb+json' })
|
||||
|
||||
const { data, error } = await supabase
|
||||
@@ -276,7 +322,7 @@ const getRequests = async(options: GetRequestsOptions) => {
|
||||
}
|
||||
}
|
||||
|
||||
const downloadNotebook = async ({ requestId }: DownloadNotebookOptions) => {
|
||||
const downloadComputationNotebook = async ({ requestId }: DownloadComputationNotebookOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
@@ -318,13 +364,55 @@ const downloadNotebook = async ({ requestId }: DownloadNotebookOptions) => {
|
||||
|
||||
}
|
||||
|
||||
const downloadVerificationNotebook = async ({ requestId }: DownloadVerificationNotebookOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
|
||||
const { data } = supabase.storage
|
||||
.from('computations')
|
||||
.getPublicUrl(`${requestId}-verification.ipynb`);
|
||||
|
||||
const { publicUrl } = data;
|
||||
|
||||
fetch(publicUrl)
|
||||
.then(response => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(blob => {
|
||||
// Create an object URL for the blob object
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// Set the href to the object URL
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = `${requestId}-verification.ipynb`; // Set the download attribute with the desired file name
|
||||
|
||||
// Append the anchor to the body to make it clickable
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
// Trigger the download by clicking the anchor
|
||||
anchor.click();
|
||||
|
||||
// Clean up by revoking the object URL and removing the anchor element
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
anchor.remove();
|
||||
})
|
||||
.catch(error => console.error('Download failed:', error));
|
||||
|
||||
}
|
||||
|
||||
const uploadNotebook = async ({ requestId, code } : UploadNotebookOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
|
||||
const notebook = await generateJupyterNotebook(code);
|
||||
const notebook = await generateProverNotebook(code);
|
||||
const notebookBlob = new Blob([notebook], { type: 'application/x-ipynb+json' })
|
||||
|
||||
const { data, error } = await supabase.storage
|
||||
@@ -403,9 +491,7 @@ const uploadProofAndAssets = async ({
|
||||
datasetId,
|
||||
requestId,
|
||||
proofFile,
|
||||
vkFile,
|
||||
modelOnnxFile,
|
||||
srsFile,
|
||||
precalWitnessFile,
|
||||
settingsFile
|
||||
}: UploadProofAndAssetsOptions) => {
|
||||
if (!supabase) {
|
||||
@@ -415,9 +501,7 @@ const uploadProofAndAssets = async ({
|
||||
|
||||
const promises = [
|
||||
supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/model.pf`, proofFile),
|
||||
supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/model.vk`, vkFile),
|
||||
supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/model.onnx`, modelOnnxFile),
|
||||
//supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/model.pf`, proofFile) // TODO ???
|
||||
supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/precal_witness.json`, precalWitnessFile),
|
||||
supabase.storage.from('proof_assets').upload(`${datasetId}/${requestId}/settings.json`, settingsFile)
|
||||
];
|
||||
// const { data, error } = await supabase.storage
|
||||
@@ -428,6 +512,159 @@ const uploadProofAndAssets = async ({
|
||||
// }
|
||||
}
|
||||
|
||||
const downloadProofAndAssets = async ({
|
||||
datasetId,
|
||||
requestId,
|
||||
file
|
||||
}: DownloadProofAndAssetsOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
|
||||
const getPath = (file: 'all' | 'proof' | 'precal' | 'settings') => {
|
||||
switch (file) {
|
||||
case ('all'): return `${datasetId}/${requestId}`;
|
||||
case ('proof'): return `${datasetId}/${requestId}/model.pf`;
|
||||
case ('precal'): return `${datasetId}/${requestId}/precal_witness.json`;
|
||||
case ('settings'): return `${datasetId}/${requestId}/settings.json`;
|
||||
default: return `${datasetId}/${requestId}`;
|
||||
}
|
||||
}
|
||||
|
||||
const { data } = supabase.storage
|
||||
.from('proof_assets')
|
||||
.getPublicUrl(getPath(file));
|
||||
|
||||
const { publicUrl } = data;
|
||||
|
||||
fetch(publicUrl)
|
||||
.then(response => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(blob => {
|
||||
// Create an object URL for the blob object
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// Set the href to the object URL
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = `${datasetId}-datacommitment.json`; // Set the download attribute with the desired file name
|
||||
|
||||
// Append the anchor to the body to make it clickable
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
// Trigger the download by clicking the anchor
|
||||
anchor.click();
|
||||
|
||||
// Clean up by revoking the object URL and removing the anchor element
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
anchor.remove();
|
||||
})
|
||||
.catch(error => console.error('Download failed:', error));
|
||||
}
|
||||
|
||||
const approveResult = async ({
|
||||
requestId,
|
||||
}: ApproveResultOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
const { data, error } = await supabase
|
||||
.from('computations')
|
||||
.update({
|
||||
result_approved: true,
|
||||
})
|
||||
.match({
|
||||
id: requestId
|
||||
})
|
||||
|
||||
if (data) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
const checkFileExists = async({
|
||||
bucketName,
|
||||
filePath,
|
||||
fileName
|
||||
}: CheckFileExistsOptions) => {
|
||||
|
||||
// DO I NEED BUCKET NAME HERE?
|
||||
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const { data, error } = await supabase
|
||||
.storage
|
||||
.from(bucketName)
|
||||
.list(filePath)
|
||||
|
||||
console.log('dddd', bucketName, filePath, data)
|
||||
|
||||
if (error) {
|
||||
console.error('Error listing files:', error)
|
||||
return false
|
||||
}
|
||||
|
||||
return data.some(file => file.name === fileName)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const downloadDataCommitment = async ({
|
||||
datasetId,
|
||||
}: DownloadDataCommitmentOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
// TODO error
|
||||
}
|
||||
|
||||
const { data } = supabase.storage
|
||||
.from('proof_assets')
|
||||
.getPublicUrl(`${datasetId}/data_commitment.json`);
|
||||
|
||||
const { publicUrl } = data;
|
||||
|
||||
fetch(publicUrl)
|
||||
.then(response => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error('Network response was not ok.');
|
||||
})
|
||||
.then(blob => {
|
||||
// Create an object URL for the blob object
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
// Set the href to the object URL
|
||||
anchor.href = blobUrl;
|
||||
anchor.download = `${datasetId}-datacommitment.json`; // Set the download attribute with the desired file name
|
||||
|
||||
// Append the anchor to the body to make it clickable
|
||||
document.body.appendChild(anchor);
|
||||
|
||||
// Trigger the download by clicking the anchor
|
||||
anchor.click();
|
||||
|
||||
// Clean up by revoking the object URL and removing the anchor element
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
anchor.remove();
|
||||
})
|
||||
.catch(error => console.error('Download failed:', error));
|
||||
}
|
||||
|
||||
const signUp = async ({ username, email, password }: SignUpOptions) => {
|
||||
if (!supabase) {
|
||||
return;
|
||||
|
||||
@@ -111,3 +111,23 @@ function generateDataCommitmentForColumn(column: number[], scale: number) {
|
||||
const deserializedOutput = deserialize(output)
|
||||
return deserializedOutput[0][0] as string
|
||||
}
|
||||
|
||||
export const extractResult = (proofFile: File) => {
|
||||
if (proofFile) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const json = JSON.parse(e.target?.result as string)
|
||||
console.log(json['pretty_public_inputs']['rescaled_outputs'].slice(1,))
|
||||
return(json['pretty_public_inputs']['rescaled_outputs'].slice(1,))
|
||||
} catch (error) {
|
||||
console.log('b')
|
||||
console.error('Error parsing JSON:', error)
|
||||
return '';
|
||||
}
|
||||
}
|
||||
reader.readAsText(proofFile)
|
||||
}
|
||||
console.log('a')
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
const REQUEST_TEMPLATE_URL = '/templates/run_request.ipynb';
|
||||
const CODE_CELL = 2;
|
||||
const PROVER_TEMPLATE_URL = '/templates/prover.ipynb';
|
||||
const PROVER_CODE_CELL = 5;
|
||||
|
||||
export const generateJupyterNotebook = async (code: string) => {
|
||||
export const generateProverNotebook = async (code: string) => {
|
||||
const splitCode = code.split("\n").map((line) => `${line}\n`);
|
||||
const response = await fetch(REQUEST_TEMPLATE_URL);
|
||||
const response = await fetch(PROVER_TEMPLATE_URL);
|
||||
const template = await response.json();
|
||||
const codeCell = template.cells[CODE_CELL];
|
||||
const codeCell = template.cells[PROVER_CODE_CELL];
|
||||
codeCell.source = splitCode;
|
||||
return JSON.stringify(template);
|
||||
}
|
||||
}
|
||||
|
||||
const VERIFIER_TEMPLATE_URL = '/templates/verifier.ipynb';
|
||||
const VERIFIER_CODE_CELL = 5;
|
||||
|
||||
export const generateVerifierNotebook = async ( code: string) => {
|
||||
const splitCode = code.split("\n").map((line) => `${line}\n`);
|
||||
const response = await fetch(VERIFIER_TEMPLATE_URL);
|
||||
const template = await response.json();
|
||||
const codeCell = template.cells[VERIFIER_CODE_CELL];
|
||||
codeCell.source = splitCode;
|
||||
return JSON.stringify(template);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user