- added task rendering instead of molecule
- fied a lot of errors
- changes experiment page to show generic experiment info
This commit is contained in:
2026-05-25 12:55:46 +03:00
parent 26623dc2c1
commit f3372eb0ad
43 changed files with 5541 additions and 6908 deletions

View File

@@ -1,49 +0,0 @@
import axios, { AxiosError } from "axios";
import type { ConvertSchema } from "Types/ApiCalls/ConvertBackendCallsTypes";
export async function ConvertMoleculeToStandart(
data: ConvertSchema,
): Promise<string | AxiosError> {
try {
const response = await axios.post(
import.meta.env.VITE_MOLECULAR_BACKEND_URL + "/convert",
{
text: data.inputText,
format: data.inputFormat,
convert_3d: data.make_3d,
add_hydrogen: data.add_h,
optimize_geometry: data.optimize,
},
);
// Response from the FastAPI JSONResponse
return response.data.molfile;
} catch (error) {
//Error handling
if (axios.isAxiosError(error)) {
// Do something with the axios error...
return error;
} else {
throw error;
}
}
}
export async function GetInFormats(): Promise<
{ [key: string]: string } | AxiosError
> {
try {
const response = await axios.get(
import.meta.env.VITE_MOLECULAR_BACKEND_URL + "/informats",
);
return response.data;
} catch (error) {
//Error handling
if (axios.isAxiosError(error)) {
// Do something with the axios error...
return error;
} else {
throw error;
}
}
}

View File

@@ -61,8 +61,6 @@ export const updatePasswordWithRedirect = async (
redirectUri:
successRedirectUrl ||
window.location.origin +
"/" +
import.meta.env.VITE_BASE_PATH +
"/" +
routes.SettingsPage.path +
"?password_updated=true",
@@ -85,7 +83,6 @@ export const SendEmailVerification = async () => {
action: "VERIFY_EMAIL",
redirectUri:
window.location.origin +
import.meta.env.VITE_BASE_PATH +
"/" +
routes.SettingsPage.path +
"?email_sent=true",

View File

@@ -1,58 +1,59 @@
import React, { useRef, useEffect, useState, useMemo } from "react";
import { LoadingOverlay } from "@mantine/core";
import React, { useRef, useEffect, useState } from "react";
import { useUserPreferencesStore } from "Stores/PreferencesStore";
import type { TaskData } from "Types/Experiment/Experiment";
interface IframePluginProps {
pluginUrl: string;
mode: "list" | "editor";
taskData: TaskData<any>;
onUpdate?: (data: any) => void;
index: number;
plugin: string;
mode: "List" | "Editor";
taskData: string;
simProgress: string;
qubits_needed: number;
onUpdate?: (data: string, qubits_needed: number) => void;
}
export const IframePlugin: React.FC<IframePluginProps> = ({
pluginUrl,
index,
plugin,
mode,
taskData,
simProgress,
qubits_needed,
onUpdate,
}) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const theme = useUserPreferencesStore();
const [isIframeReady, setIsIframeReady] = useState(false);
// Memoize the iframe component to prevent recreation when taskData changes
const memoizedIframe = useMemo(() => {
const url = `${pluginUrl}?mode=${mode}`;
return (
<iframe
ref={iframeRef}
src={url}
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
style={{
width: "100%",
display: "flex",
flexDirection: "column",
flexGrow: 1,
border: "none",
}}
title="Plugin"
/>
);
}, [pluginUrl, mode]); // Only recreate when pluginUrl or mode changes
const [isVisible, setIsVisible] = useState(false);
// Send data to iframe when taskData or theme changes, or when iframe becomes ready
useEffect(() => {
if (!isIframeReady || !iframeRef.current?.contentWindow) return;
const message = {
type: "plugin-data",
data: {
taskData,
taskData: taskData,
simProgress: simProgress,
qubits_needed: qubits_needed,
theme: theme.theme,
mode: mode,
},
};
iframeRef.current.contentWindow.postMessage(message, "*");
}, [taskData, theme.theme, isIframeReady]);
setIsVisible(true);
}, [taskData, theme.theme, isIframeReady, mode, simProgress]);
useEffect(() => {
if (isVisible) {
if (!isIframeReady || !iframeRef.current?.contentWindow) return;
const message = {
type: "update",
};
iframeRef.current.contentWindow.postMessage(message, "*");
}
}, [index]);
// Listen for messages from iframe
useEffect(() => {
@@ -65,7 +66,8 @@ export const IframePlugin: React.FC<IframePluginProps> = ({
// Handle plugin updates
if (event.data.type === "plugin-update" && onUpdate) {
onUpdate(event.data.data);
console.log(event.data);
onUpdate(event.data.data, event.data.qubits_needed);
}
};
@@ -73,10 +75,33 @@ export const IframePlugin: React.FC<IframePluginProps> = ({
return () => window.removeEventListener("message", handler);
}, [onUpdate]);
// Reset ready state when URL changes (new plugin or mode)
useEffect(() => {
setIsIframeReady(false);
}, [pluginUrl, mode]);
return memoizedIframe;
return (
<>
<LoadingOverlay visible={!isIframeReady} />
<div
style={{
visibility: isVisible ? "visible" : "hidden",
flexGrow: "1",
maxHeight: "100%",
display: "flex",
}}
>
<iframe
ref={iframeRef}
srcDoc={plugin}
sandbox="allow-scripts allow-popups allow-forms"
style={{
width: "100%",
display: "flex",
flexDirection: "column",
flexGrow: 1,
border: "none",
pointerEvents: mode == "List" ? "none" : "initial",
}}
name={`Ifame#${index}`}
id={`Ifame#${index}`}
></iframe>
</div>
</>
);
};

View File

@@ -0,0 +1,186 @@
import keycloak from "Api/Keycloak/Keycloak";
import axios from "axios";
import type {
CreateExperimentRequest,
ExperimentData,
ExperimentListResponse,
CreateInstanceRequest,
SimpleInstanceData,
UpdateInstanceRequest,
InstanceListResponse,
InstanceData,
StartExperimentRequest,
UpdateExperimentRequest,
CreateExperimentTypeResponse,
ExperimentTypeList,
} from "Types/Experiment/Experiment";
const api = axios.create({
baseURL: `${import.meta.env.VITE_QUANTUM_BACKEND_URL}/experiment`,
headers: { "Content-Type": "application/json" },
});
// Add auth token to requests
api.interceptors.request.use((config) => {
const token = keycloak.token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// ============= EXPERIMENT TYPE ENDPOINTS =============
// 1. Get all experiment types
export const getExperimentTypes = async (): Promise<ExperimentTypeList[]> => {
const response = await api.get<ExperimentTypeList[]>("/types");
return response.data;
};
// 2. Create experiment type (admin only, with file uploads)
export const createExperimentType = async (
name: string,
fileFrontend: File,
fileCompSystem: File,
description?: string,
): Promise<CreateExperimentTypeResponse> => {
const formData = new FormData();
formData.append("name", name);
formData.append("file_frontend", fileFrontend);
formData.append("file_comp_system", fileCompSystem);
if (description) {
formData.append("description", description);
}
const response = await api.post<CreateExperimentTypeResponse>(
"/types",
formData,
{
headers: { "Content-Type": "multipart/form-data" },
},
);
return response.data;
};
// 3. Get frontend HTML file for experiment type
export const getFrontendFile = async (
experiment_type_id: number,
): Promise<string> => {
const response = await api.get<string>("/types/frontend", {
params: { experiment_type_id },
});
return response.data;
};
// ============= EXPERIMENT ENDPOINTS =============
// 5. Create a new experiment
export const createExperiment = async (
data: CreateExperimentRequest,
): Promise<ExperimentData> => {
const response = await api.post<ExperimentData>("", data);
return response.data;
};
// 6. Get single experiment by ID
export const getExperimentById = async (
experiment_id: number,
): Promise<ExperimentData> => {
const response = await api.get<ExperimentData>("", {
params: { experiment_id },
});
return response.data;
};
// 7. Update experiment
export const updateExperiment = async (
data: UpdateExperimentRequest,
): Promise<ExperimentData> => {
const response = await api.put<ExperimentData>("", data);
return response.data;
};
// 8. Get user's experiments (with pagination)
export const getUserExperiments = async ({
page_num = 1,
page_size = 10,
}: {
page_num?: number;
page_size?: number;
}): Promise<ExperimentListResponse> => {
const response = await api.get<ExperimentListResponse>("/user", {
params: { page_num, page_size },
});
return response.data;
};
// 9. Delete experiment
export const deleteExperiment = async (
experiment_id: number,
): Promise<{ message: string }> => {
const response = await api.delete<{ message: string }>("", {
params: { experiment_id },
});
return response.data;
};
// ============= INSTANCE ENDPOINTS =============
// 10. Create instance for an experiment
export const createInstance = async (
data: CreateInstanceRequest,
): Promise<SimpleInstanceData> => {
const response = await api.post<SimpleInstanceData>("/instance", data);
return response.data;
};
// 11. Update instance
export const updateInstance = async (
data: UpdateInstanceRequest,
): Promise<SimpleInstanceData> => {
const response = await api.put<SimpleInstanceData>("/instance", data);
return response.data;
};
// 12. Get all instances of an experiment (with pagination)
export const getExperimentInstances = async (
experiment_id: number,
page_num: number = 1,
page_size: number = 6,
): Promise<InstanceListResponse> => {
page_num = Math.max(page_num, 1);
const response = await api.get<InstanceListResponse>("/instance", {
params: { experiment_id, page_num, page_size },
});
return response.data;
};
// 13. Get single instance by ID
export const getInstanceById = async (
instance_id: number,
): Promise<InstanceData> => {
const response = await api.get<InstanceData>("/instance/id", {
params: { instance_id },
});
return response.data;
};
// 14. Delete instance
export const deleteInstance = async (
instance_id: number,
): Promise<{ message: string }> => {
const response = await api.delete<{ message: string }>("/instance", {
params: { instance_id },
});
return response.data;
};
// ============= SIMULATION ENDPOINTS =============
// 15. Start experiment (run simulation)
export const startExperiment = async (
data: StartExperimentRequest,
): Promise<200> => {
const response = await api.post<200>("/start", data);
return response.data;
};

View File

@@ -25,25 +25,38 @@ export const GetCurrentUserInfo = async (): Promise<UserData | undefined> => {
}
};
// In Api/QuantumBackend/UserManagement.ts
export const UpdateCurrentUserInfo = async (
profile_picture_path: string,
): Promise<UserData | undefined> => {
const response = await axios.put(
`${import.meta.env.VITE_QUANTUM_BACKEND_URL}/user`,
{ profile_picture_path: profile_picture_path },
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${keycloak.token}`,
file: File,
): Promise<{ profile_picture_path: string } | undefined> => {
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch(
`${import.meta.env.VITE_QUANTUM_BACKEND_URL}/user/upload`,
{
method: "POST",
headers: {
Authorization: `Bearer ${keycloak.token}`,
},
body: formData,
},
withCredentials: true, // Important: allows credentials in CORS
},
);
if (response.status === 200) {
return response.data;
);
if (!response.ok) {
throw new Error("Failed to upload profile picture");
}
return await response.json();
} catch (error) {
console.error("Error uploading profile picture:", error);
return undefined;
}
};
// Keep your existing GetCurrentUserInfo function
export const GetUserByEmail = async (
email: string,
): Promise<UserData | undefined> => {