Skip to content

Commit 99d7ad0

Browse files
committed
feat: add run experiment for publishing page and functionality
1 parent 821086a commit 99d7ad0

7 files changed

Lines changed: 205 additions & 7 deletions

File tree

backend/main.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from fastapi.middleware.cors import CORSMiddleware
77

88
from backend.config.paths import METHODS_TS_FILE, get_tmp_thread_files
9-
from backend.utils.data_uploader import handle_upload
9+
from backend.utils.data_uploader import handle_ranking_upload, handle_upload
1010
from backend.utils.methods_handler import Methods
1111
from backend.utils.task import celery_app, process_and_cleanup_task
1212

@@ -48,6 +48,30 @@ async def handle_submit(
4848
}
4949

5050

51+
@app.post("/api/ranking-upload")
52+
async def handle_ranking_submit(
53+
file: UploadFile,
54+
email: str = Form(...),
55+
experiment: str = Form(...),
56+
):
57+
task_uid = str(shortuuid.uuid())
58+
59+
handle_ranking_upload(task_uid, file)
60+
process_and_cleanup_task.apply_async(
61+
args=[task_uid, email, experiment, file.filename],
62+
task_id=task_uid,
63+
queue="celery",
64+
)
65+
66+
return {
67+
"task_uid": task_uid,
68+
"experiment": experiment,
69+
"filename": file.filename,
70+
"email": email,
71+
"message": "Data uploaded, processing started",
72+
}
73+
74+
5175
@app.get("/api/status/{task_uid}")
5276
async def get_task_status(task_uid: str):
5377
result = celery_app.AsyncResult(task_uid)
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
dll,method
22
ManuallyCollected.dll,BinSearchMain
3-
ManuallyCollected.dll,BinSearchMain

backend/utils/data_uploader.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import json
2+
import os
3+
import shutil
24
from collections import defaultdict
35

46
from fastapi import UploadFile
57

6-
from backend.config.paths import LAUNCH_INFO_FILE, MODEL_ONNX_FILE, get_thread_filepath
8+
from backend.config.paths import (
9+
LAUNCH_INFO_FILE,
10+
MODEL_ONNX_FILE,
11+
RANKING_LAUNCH_INFO_FILE,
12+
get_thread_filepath,
13+
)
714
from backend.file_utils.csv_methods_writer import write_launch_info_to_csv
815
from backend.file_utils.files import save_upload_file
916
from backend.utils.methods_handler import Methods
@@ -25,3 +32,13 @@ def handle_upload(
2532
parsed_methods=launch_methods,
2633
output_file=get_thread_filepath(uid, LAUNCH_INFO_FILE),
2734
)
35+
36+
37+
def handle_ranking_upload(uid: str, file: UploadFile) -> None:
38+
save_upload_file(file, get_thread_filepath(uid, MODEL_ONNX_FILE))
39+
40+
dest = get_thread_filepath(uid, LAUNCH_INFO_FILE)
41+
42+
os.makedirs(os.path.dirname(dest), exist_ok=True)
43+
44+
shutil.copy2(RANKING_LAUNCH_INFO_FILE, dest)

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import HomePage from './pages/HomePage';
33
import ExperimentPage from './pages/ExperimentPage';
44
import ModelRankingPage from './pages/ModelRankingPage';
55
import ModelInterfacePage from './pages/ModelInterfacePage';
6+
import PublishExperimentPage from './pages/PublishExperimentPage';
67

78
function App() {
89
return (
@@ -11,6 +12,7 @@ function App() {
1112
<Route path="/" element={<HomePage />} />
1213
<Route path="/experiment" element={<ExperimentPage />} />
1314
<Route path="/ranking" element={<ModelRankingPage />} />
15+
<Route path="/ranking/publish" element={<PublishExperimentPage />} />
1416
<Route path="/interface" element={<ModelInterfacePage />} />
1517
</Routes>
1618
</BrowserRouter>

frontend/src/pages/HomePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const CARDS = [
2828
'Explore the leaderboard of all evaluated models ranked by their symbolic execution performance metrics across the benchmark dataset.',
2929
action: 'View Ranking',
3030
path: '/ranking',
31-
available: false,
31+
available: true,
3232
borderColor: '#faad14',
3333
},
3434
{

frontend/src/pages/ModelRankingPage.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ const ModelRankingPage: React.FC = () => {
1313
title="Model Ranking"
1414
subTitle="The leaderboard is coming soon. Check back after running your first experiments."
1515
extra={
16-
<Button type="primary" onClick={() => navigate('/')}>
17-
Back to Home
18-
</Button>
16+
<div>
17+
<Button type="primary" onClick={() => navigate('/')}>
18+
Back to Home
19+
</Button>
20+
<Button type="primary" onClick={() => navigate('/publish')}>
21+
Add your experiment to leaderboard
22+
</Button>
23+
</div>
1924
}
2025
/>
2126
</div>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { Form, Button, Input, Typography, message, Space } from 'antd';
4+
import { ArrowLeftOutlined } from '@ant-design/icons';
5+
import UploadModel from '../components/components/UploadModel';
6+
7+
const { Title } = Typography;
8+
9+
interface FieldType {
10+
email: string;
11+
experiment: string;
12+
}
13+
14+
const PublishExperimentPage: React.FC = () => {
15+
const navigate = useNavigate();
16+
const [file, setFile] = useState<File | null>(null);
17+
const [currentTaskUid, setCurrentTaskUid] = useState<string | null>(null);
18+
const [isCancelling, setIsCancelling] = useState(false);
19+
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
20+
21+
useEffect(() => {
22+
if (!currentTaskUid) return;
23+
24+
pollRef.current = setInterval(async () => {
25+
try {
26+
const res = await fetch(`http://localhost:8000/api/status/${currentTaskUid}`);
27+
if (!res.ok) return;
28+
const data = await res.json();
29+
if (data.status === 'SUCCESS' || data.status === 'FAILURE') {
30+
setCurrentTaskUid(null);
31+
}
32+
} catch {
33+
// ignore transient errors
34+
}
35+
}, 5000);
36+
37+
return () => {
38+
if (pollRef.current) clearInterval(pollRef.current);
39+
};
40+
}, [currentTaskUid]);
41+
42+
const onFinish = async (values: FieldType) => {
43+
if (!file) {
44+
message.error('Please upload a model file first.');
45+
return;
46+
}
47+
48+
setCurrentTaskUid(null);
49+
50+
const formData = new FormData();
51+
formData.append('file', file);
52+
formData.append('experiment', values.experiment);
53+
formData.append('email', values.email);
54+
55+
try {
56+
const res = await fetch('http://localhost:8000/api/ranking-upload', {
57+
method: 'POST',
58+
body: formData,
59+
});
60+
if (!res.ok) throw new Error('Upload failed');
61+
const data = await res.json();
62+
setCurrentTaskUid(data.task_uid);
63+
message.success(data.message);
64+
} catch (err) {
65+
console.error(err);
66+
message.error('Submission failed');
67+
}
68+
};
69+
70+
const onCancel = async () => {
71+
if (!currentTaskUid) return;
72+
setIsCancelling(true);
73+
try {
74+
const res = await fetch(`http://localhost:8000/api/cancel/${currentTaskUid}`, {
75+
method: 'POST',
76+
});
77+
if (!res.ok) throw new Error('Cancel failed');
78+
setCurrentTaskUid(null);
79+
message.success('Experiment cancelled');
80+
} catch (err) {
81+
console.error(err);
82+
message.error('Failed to cancel experiment');
83+
} finally {
84+
setIsCancelling(false);
85+
}
86+
};
87+
88+
return (
89+
<div className="min-h-screen bg-gray-50">
90+
<div className="max-w-2xl mx-auto px-6 py-10">
91+
<Button
92+
icon={<ArrowLeftOutlined />}
93+
type="text"
94+
onClick={() => navigate('/ranking')}
95+
style={{ marginBottom: 20, paddingLeft: 0, color: '#595959' }}
96+
>
97+
Back to Ranking
98+
</Button>
99+
100+
<Form
101+
name="publish-experiment"
102+
layout="vertical"
103+
onFinish={onFinish}
104+
autoComplete="off"
105+
>
106+
<Title level={2} style={{ textAlign: 'center' }}>
107+
Publish Experiment
108+
</Title>
109+
110+
<Form.Item
111+
label="Your experiment name"
112+
name="experiment"
113+
rules={[{ required: true, message: 'Enter experiment name' }]}
114+
>
115+
<Input />
116+
</Form.Item>
117+
118+
<Form.Item label={null} style={{ textAlign: 'center' }}>
119+
<UploadModel onFileChange={(f) => setFile(f?.originFileObj || null)} />
120+
</Form.Item>
121+
122+
<Form.Item
123+
label="Email"
124+
name="email"
125+
rules={[
126+
{ required: true, message: 'Enter email address' },
127+
{ type: 'email', message: 'Enter a valid email address' },
128+
]}
129+
>
130+
<Input />
131+
</Form.Item>
132+
133+
<Form.Item label={null}>
134+
<Space>
135+
<Button type="primary" htmlType="submit">
136+
Submit
137+
</Button>
138+
{currentTaskUid && (
139+
<Button danger onClick={onCancel} loading={isCancelling}>
140+
Cancel experiment
141+
</Button>
142+
)}
143+
</Space>
144+
</Form.Item>
145+
</Form>
146+
</div>
147+
</div>
148+
);
149+
};
150+
151+
export default PublishExperimentPage;

0 commit comments

Comments
 (0)