diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..92ff5e55 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +venv/**/*.ts +venv/**/*.js \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..e86f1127 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,18 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +formats: all + +python: + install: + - method: pip + path: . + extra_requirements: + - docs \ No newline at end of file diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index ab26a1a6..256ddbb6 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -38,6 +38,9 @@ from ._storage_url import get_storage from .artifact._backend import delete_all_artifacts from .artifact._backend import register_artifact_route from .artifact._backend_to_store import to_artifact_store +from .preferential._study import _SYSTEM_ATTR_PREFERENTIAL_STUDY +from .preferential._study import get_best_trials as get_best_preferential_trials +from .preferential._system_attrs import report_preferences if typing.TYPE_CHECKING: @@ -187,8 +190,12 @@ def create_app( return {"reason": f"study_id={study_id} is not found"} trials = get_trials(storage, study_id) + system_attrs = getattr(summary, "system_attrs", {}) + is_preferential = system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False) # TODO(c-bata): Cache best_trials - if len(summary.directions) == 1: + if is_preferential: + best_trials = get_best_preferential_trials(study_id, storage) + elif len(summary.directions) == 1: if len([t for t in trials if t.state == TrialState.COMPLETE]) == 0: best_trials = [] else: @@ -255,6 +262,25 @@ def create_app( response.status = 204 # No content return {} + @app.post("/api/studies//preference") + @json_api_view + def post_preference(study_id: int) -> dict[str, Any]: + try: + best_trials = [int(d) for d in request.json.get("best_trials", [])] + worst_trials = [int(d) for d in request.json.get("worst_trials", [])] + except ValueError: + response.status = 400 + return {"reason": "best_trials and worst_trials must be an array of integers."} + if len(best_trials) == 0 or len(worst_trials) == 0: + response.status = 400 # Bad request + return {"reason": "You need to set best_trials and worst_trials"} + + preferences = [(best, worst) for best in best_trials for worst in worst_trials] + report_preferences(study_id, storage, preferences) + + response.status = 204 + return {} + @app.post("/api/trials//tell") @json_api_view def tell_trial(trial_id: int) -> dict[str, Any]: @@ -284,11 +310,7 @@ def create_app( response.status = 400 # Bad request return {"reason": "values attribute must be an array of numbers"} - try: - storage.set_trial_state_values(trial_id, state, values) - except Exception as e: - response.status = 500 - return {"reason": f"Internal server error: {e}"} + storage.set_trial_state_values(trial_id, state, values) response.status = 204 return {} @@ -301,12 +323,8 @@ def create_app( response.status = 400 # Bad request return {"reason": "user_attrs must be specified."} - try: - for key, val in user_attrs.items(): - storage.set_trial_user_attr(trial_id, key, val) - except Exception as e: - response.status = 500 - return {"reason": f"Internal server error: {e}"} + for key, val in user_attrs.items(): + storage.set_trial_user_attr(trial_id, key, val) response.status = 204 return {} diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 53052427..a037290e 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -15,6 +15,7 @@ from . import _note as note from ._form_widget import get_form_widgets_json from ._named_objectives import get_objective_names from .artifact._backend import list_trial_artifacts +from .preferential._study import _SYSTEM_ATTR_PREFERENTIAL_STUDY if TYPE_CHECKING: @@ -107,7 +108,9 @@ def serialize_study_summary(summary: StudySummary) -> dict[str, Any]: "study_name": summary.study_name, "directions": [d.name.lower() for d in summary.directions], "user_attrs": serialize_attrs(summary.user_attrs), - "system_attrs": serialize_attrs(getattr(summary, "system_attrs", {})), + "is_preferential": getattr(summary, "_system_attrs", {}).get( + _SYSTEM_ATTR_PREFERENTIAL_STUDY, False + ), } if summary.datetime_start is not None: @@ -144,6 +147,7 @@ def serialize_study_detail( serialized["union_user_attrs"] = [{"key": a[0], "sortable": a[1]} for a in union_user_attrs] serialized["has_intermediate_values"] = has_intermediate_values serialized["note"] = note.get_note_from_system_attrs(system_attrs, None) + serialized["is_preferential"] = system_attrs.get(_SYSTEM_ATTR_PREFERENTIAL_STUDY, False) objective_names = get_objective_names(system_attrs) if objective_names: serialized["objective_names"] = objective_names @@ -183,9 +187,6 @@ def serialize_frozen_trial( for param_name in fixed_params ], "user_attrs": serialize_attrs(trial.user_attrs), - "system_attrs": serialize_attrs( - {k: trial_system_attrs[k] for k in trial_system_attrs if not k.startswith("dashboard")} - ), "note": note.get_note_from_system_attrs(study_system_attrs, trial._trial_id), "artifacts": list_trial_artifacts(study_system_attrs, trial), "constraints": trial_system_attrs.get(CONSTRAINTS_KEY, []), diff --git a/optuna_dashboard/preferential/_study.py b/optuna_dashboard/preferential/_study.py index 9bbd6d2f..71c00882 100644 --- a/optuna_dashboard/preferential/_study.py +++ b/optuna_dashboard/preferential/_study.py @@ -31,16 +31,7 @@ class PreferentialStudy: @property def best_trials(self) -> list[FrozenTrial]: - ready_trials = [ - t - for t in self._study.get_trials( - deepcopy=False, states=(TrialState.COMPLETE, TrialState.RUNNING) - ) - if t.system_attrs.get(_SYSTEM_ATTR_COMPARISON_READY) is True - ] - preferences = get_preferences(self._study, deepcopy=False) - worse_numbers = {worse.number for _, worse in preferences} - return [copy.deepcopy(t) for t in ready_trials if t.number not in worse_numbers] + return get_best_trials(self._study._study_id, self._study._storage) @property def study_name(self) -> str: @@ -80,10 +71,16 @@ class PreferentialStudy: if not isinstance(worse_trials, list): worse_trials = [worse_trials] - report_preferences(self._study, [(b, w) for b in better_trials for w in worse_trials]) + report_preferences( + self._study._study_id, + self._study._storage, + [(b.number, w.number) for b in better_trials for w in worse_trials], + ) def get_preferences(self, *, deepcopy: bool = True) -> list[tuple[FrozenTrial, FrozenTrial]]: - return get_preferences(self._study, deepcopy=deepcopy) + trials = self._study.get_trials(deepcopy=deepcopy) + preferences = get_preferences(self._study._study_id, self._study._storage) + return [(trials[better], trials[worse]) for (better, worse) in preferences] def set_user_attr(self, key: str, value: Any) -> None: self._study.set_user_attr(key, value) @@ -101,6 +98,21 @@ class PreferentialStudy: storage.set_trial_system_attr(trial_id, _SYSTEM_ATTR_COMPARISON_READY, True) +def get_best_trials(study_id: int, storage: optuna.storages.BaseStorage) -> list[FrozenTrial]: + ready_trials = [ + t + for t in storage.get_all_trials( + study_id, + deepcopy=False, + states=(TrialState.COMPLETE, TrialState.RUNNING), + ) + if t.system_attrs.get(_SYSTEM_ATTR_COMPARISON_READY) is True + ] + preferences = get_preferences(study_id, storage) + worse_numbers = {worse for _, worse in preferences} + return [copy.deepcopy(t) for t in ready_trials if t.number not in worse_numbers] + + def create_study( *, storage: str | optuna.storages.BaseStorage | None = None, diff --git a/optuna_dashboard/preferential/_system_attrs.py b/optuna_dashboard/preferential/_system_attrs.py index 70567655..fdd9db35 100644 --- a/optuna_dashboard/preferential/_system_attrs.py +++ b/optuna_dashboard/preferential/_system_attrs.py @@ -2,45 +2,45 @@ from __future__ import annotations import uuid -import optuna -from optuna.trial import FrozenTrial +from optuna.storages import BaseStorage from optuna.trial import TrialState +from .._storage import get_study_summary + _SYSTEM_ATTR_PREFIX_PREFERENCE = "preference:values" def report_preferences( - study: optuna.Study, - preferences: list[tuple[FrozenTrial, FrozenTrial]], + study_id: int, + storage: BaseStorage, + preferences: list[tuple[int, int]], ) -> None: key = _SYSTEM_ATTR_PREFIX_PREFERENCE + str(uuid.uuid4()) - study._storage.set_study_system_attr( - study_id=study._study_id, + storage.set_study_system_attr( + study_id=study_id, key=key, - value=[(better.number, worse.number) for better, worse in preferences], + value=preferences, ) - - values = [0 for _ in study.directions] - for better, worse in preferences: - for t in (better, worse): - study.tell( - t.number, - values=values, - state=TrialState.COMPLETE, - skip_if_finished=True, - ) + trials = storage.get_all_trials(study_id, deepcopy=False) + directions = storage.get_study_directions(study_id) + values = [0 for _ in directions] + updated_trials = {num for tpl in preferences for num in tpl} + for number in updated_trials: + trial_id = trials[number]._trial_id + if trials[number].state != TrialState.COMPLETE: + storage.set_trial_state_values(trial_id, TrialState.COMPLETE, values) def get_preferences( - study: optuna.Study, - *, - deepcopy: bool = True, -) -> list[tuple[FrozenTrial, FrozenTrial]]: + study_id: int, + storage: BaseStorage, +) -> list[tuple[int, int]]: preferences: list[tuple[int, int]] = [] - for k, v in study.system_attrs.items(): + summary = get_study_summary(storage, study_id) + system_attrs = getattr(summary, "system_attrs", {}) + for k, v in system_attrs.items(): if not k.startswith(_SYSTEM_ATTR_PREFIX_PREFERENCE): continue preferences.extend(v) # type: ignore - trials = study.get_trials(deepcopy=deepcopy) - return [(trials[better], trials[worse]) for (better, worse) in preferences] + return preferences diff --git a/optuna_dashboard/ts/action.ts b/optuna_dashboard/ts/action.ts index 96fde064..c0fed1aa 100644 --- a/optuna_dashboard/ts/action.ts +++ b/optuna_dashboard/ts/action.ts @@ -14,6 +14,7 @@ import { uploadArtifactAPI, getMetaInfoAPI, deleteArtifactAPI, + reportPreferenceAPI, } from "./apiClient" import { graphVisibilityState, @@ -582,6 +583,21 @@ export const actionCreator = () => { console.log(err) }) } + + const updatePreference = ( + study_id: number, + best_trials: number[], + worst_trials: number[] + ) => { + reportPreferenceAPI(study_id, best_trials, worst_trials).catch((err) => { + const reason = err.response?.data.reason + enqueueSnackbar(`Failed to report preference. Reason: ${reason}`, { + variant: "error", + }) + console.log(err) + }) + } + return { updateAPIMeta, updateStudyDetail, @@ -601,6 +617,7 @@ export const actionCreator = () => { makeTrialComplete, makeTrialFail, saveTrialUserAttrs, + updatePreference, } } diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index fc75b942..d9881715 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -27,7 +27,6 @@ interface TrialResponse { param_external_value: string }[] user_attrs: Attribute[] - system_attrs: Attribute[] note: Note artifacts: Artifact[] constraints: number[] @@ -50,7 +49,6 @@ const convertTrialResponse = (res: TrialResponse): Trial => { params: res.params, fixed_params: res.fixed_params, user_attrs: res.user_attrs, - system_attrs: res.system_attrs, note: res.note, artifacts: res.artifacts, constraints: res.constraints, @@ -68,6 +66,7 @@ interface StudyDetailResponse { union_user_attrs: AttributeSpec[] has_intermediate_values: boolean note: Note + is_preferential: boolean objective_names?: string[] form_widgets?: FormWidgets } @@ -103,6 +102,7 @@ export const getStudyDetailAPI = ( note: res.data.note, objective_names: res.data.objective_names, form_widgets: res.data.form_widgets, + is_preferential: res.data.is_preferential, } }) } @@ -113,7 +113,7 @@ interface StudySummariesResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] + is_preferential: boolean datetime_start?: string }[] } @@ -128,7 +128,7 @@ export const getStudySummariesAPI = (): Promise => { study_name: study.study_name, directions: study.directions, user_attrs: study.user_attrs, - system_attrs: study.system_attrs, + is_preferential: study.is_preferential, datetime_start: study.datetime_start ? new Date(study.datetime_start) : undefined, @@ -143,7 +143,7 @@ interface CreateNewStudyResponse { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] + is_preferential: boolean datetime_start?: string } } @@ -165,7 +165,7 @@ export const createNewStudyAPI = ( directions: study_summary.directions, // best_trial: undefined, user_attrs: study_summary.user_attrs, - system_attrs: study_summary.system_attrs, + is_preferential: study_summary.is_preferential, datetime_start: study_summary.datetime_start ? new Date(study_summary.datetime_start) : undefined, @@ -184,7 +184,7 @@ type RenameStudyResponse = { study_name: string directions: StudyDirection[] user_attrs: Attribute[] - system_attrs: Attribute[] + is_prefential: boolean datetime_start?: string } @@ -202,7 +202,7 @@ export const renameStudyAPI = ( study_name: res.data.study_name, directions: res.data.directions, user_attrs: res.data.user_attrs, - system_attrs: res.data.system_attrs, + is_preferential: res.data.is_prefential, datetime_start: res.data.datetime_start ? new Date(res.data.datetime_start) : undefined, @@ -309,3 +309,18 @@ export const getParamImportances = ( return res.data.param_importances }) } + +export const reportPreferenceAPI = ( + studyId: number, + best_trials: number[], + worst_trials: number[] +): Promise => { + return axiosInstance + .post(`/api/studies/${studyId}/preference`, { + best_trials: best_trials, + worst_trials: worst_trials, + }) + .then(() => { + return + }) +} diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 0a861641..78015049 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -69,15 +69,6 @@ export const App: FC = () => { /> } /> - - } - /> { element={ } /> diff --git a/optuna_dashboard/ts/components/AppDrawer.tsx b/optuna_dashboard/ts/components/AppDrawer.tsx index 540aa7cc..79f7d615 100644 --- a/optuna_dashboard/ts/components/AppDrawer.tsx +++ b/optuna_dashboard/ts/components/AppDrawer.tsx @@ -14,7 +14,12 @@ import ListItem from "@mui/material/ListItem" import ListItemButton from "@mui/material/ListItemButton" import ListItemIcon from "@mui/material/ListItemIcon" import ListItemText from "@mui/material/ListItemText" -import { drawerOpenState, reloadIntervalState } from "../state" +import { + drawerOpenState, + reloadIntervalState, + useStudyDetailValue, + useStudySummaryValue, +} from "../state" import { Link } from "react-router-dom" import AutoGraphIcon from "@mui/icons-material/AutoGraph" import ViewListIcon from "@mui/icons-material/ViewList" @@ -28,17 +33,13 @@ import MenuIcon from "@mui/icons-material/Menu" import GitHubIcon from "@mui/icons-material/GitHub" import OpenInNewIcon from "@mui/icons-material/OpenInNew" import QueryStatsIcon from "@mui/icons-material/QueryStats" +import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt" import { Switch } from "@mui/material" import { actionCreator } from "../action" const drawerWidth = 240 -export type PageId = - | "history" - | "analytics" - | "trialTable" - | "trialList" - | "note" +export type PageId = "top" | "analytics" | "trialTable" | "trialList" | "note" const openedMixin = (theme: Theme): CSSObject => ({ width: drawerWidth, @@ -120,6 +121,12 @@ export const AppDrawer: FC<{ const action = actionCreator() const [open, setOpen] = useRecoilState(drawerOpenState) const reloadInterval = useRecoilValue(reloadIntervalState) + const studyDetail = + studyId !== undefined ? useStudyDetailValue(studyId) : null + const studySummary = + studyId !== undefined ? useStudySummaryValue(studyId) : null + const isPreferential = + studyDetail?.is_preferential ?? studySummary?.is_preferential ?? false const styleListItem = { display: "block", @@ -181,32 +188,37 @@ export const AppDrawer: FC<{ {studyId !== undefined && page && ( - + - + {isPreferential ? : } - - - - - - - - - + + {!isPreferential && ( + + + + + + + + + )} void +}> = ({ trial, studyDetail, hideTrial }) => { + const theme = useTheme() + const action = actionCreator() + const trialWidth = 500 + + if (trial == undefined) { + return + } + + return ( + + + Trial {trial.number} (trial_id={trial.trial_id}) + + + + + ) +} + +type DisplayTrials = { + numbers: number[] + last_number: number +} + +export const PreferentialTrials: FC<{ studyDetail: StudyDetail | null }> = ({ + studyDetail, +}) => { + if (studyDetail === null || !studyDetail.is_preferential) { + return null + } + const [displayTrials, setDisplayTrials] = useState({ + numbers: studyDetail.best_trials.map((t) => t.number), + last_number: Math.max(...studyDetail.best_trials.map((t) => t.number), -1), + }) + const new_trails = studyDetail.best_trials.filter( + (t) => + displayTrials.last_number < t.number && + displayTrials.numbers.find((n) => n === t.number) === undefined + ) + if (new_trails.length > 0) { + setDisplayTrials((display) => { + const numbers = [...display.numbers] + new_trails.map((t) => { + const index = numbers.findIndex((n) => n === -1) + if (index === -1) { + numbers.push(t.number) + } else { + numbers[index] = t.number + } + }) + return { + numbers: numbers, + last_number: Math.max(...numbers, -1), + } + }) + } + + const hideTrial = (num: number) => { + setDisplayTrials((display) => { + const index = display.numbers.findIndex((n) => n === num) + if (index === -1) { + return display + } + const numbers = [...displayTrials.numbers] + numbers[index] = -1 + return { + numbers: numbers, + last_number: display.last_number, + } + }) + } + + return ( + + {displayTrials.numbers.map((t, index) => ( + trial.number === t)} + studyDetail={studyDetail} + hideTrial={() => { + hideTrial(t) + }} + /> + ))} + + ) +} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 255be7d2..10619227 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -19,6 +19,7 @@ import { reloadIntervalState, useStudyDetailValue, useStudyName, + useStudySummaryValue, } from "../state" import { TrialTable } from "./TrialTable" import { AppDrawer, PageId } from "./AppDrawer" @@ -28,6 +29,7 @@ import { GraphSlice } from "./GraphSlice" import { GraphEdf } from "./GraphEdf" import { TrialList } from "./TrialList" import { StudyHistory } from "./StudyHistory" +import { PreferentialTrials } from "./PreferentialTrials" interface ParamTypes { studyId: string @@ -47,8 +49,11 @@ export const StudyDetail: FC<{ const action = actionCreator() const studyId = useURLVars() const studyDetail = useStudyDetailValue(studyId) + const studySummary = useStudySummaryValue(studyId) const reloadInterval = useRecoilValue(reloadIntervalState) const studyName = useStudyName(studyId) + const isPreferential = + studySummary?.is_preferential ?? studyDetail?.is_preferential ?? false const title = studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}` @@ -67,11 +72,16 @@ export const StudyDetail: FC<{ let interval = reloadInterval * 1000 // For Human-in-the-loop Optimization, the interval is set to 2 seconds - // when the number of trials is small and the page is "trialList". - if (page === "trialList" && nTrials < 100) { - interval = 2000 - } else if (page === "trialList" && nTrials < 500) { - interval = 5000 + // when the number of trials is small, and the page is "trialList" or top page of preferential. + if ( + (!isPreferential && page === "trialList") || + (isPreferential && page === "top") + ) { + if (nTrials < 100) { + interval = 2000 + } else if (nTrials < 500) { + interval = 5000 + } } const intervalId = setInterval(function () { @@ -81,8 +91,12 @@ export const StudyDetail: FC<{ }, [reloadInterval, studyDetail, page]) let content = null - if (page === "history") { - content = + if (page === "top") { + content = isPreferential ? ( + + ) : ( + + ) } else if (page === "analytics") { content = ( diff --git a/optuna_dashboard/ts/components/StudyList.tsx b/optuna_dashboard/ts/components/StudyList.tsx index fee9de4b..c41c51cc 100644 --- a/optuna_dashboard/ts/components/StudyList.tsx +++ b/optuna_dashboard/ts/components/StudyList.tsx @@ -215,10 +215,12 @@ export const StudyList: FC<{ color="text.secondary" component="div" > - {"Direction: " + - study.directions - .map((d) => d.toString().toUpperCase()) - .join(", ")} + {study.is_preferential + ? "Preferential Optimization" + : "Direction: " + + study.directions + .map((d) => d.toString().toUpperCase()) + .join(", ")} diff --git a/optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx b/optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx new file mode 100644 index 00000000..536f1d91 --- /dev/null +++ b/optuna_dashboard/ts/components/ThreejsArtifactViewer.tsx @@ -0,0 +1,111 @@ +import * as THREE from "three" +import React, { useEffect, useState } from "react" +import { Canvas } from "@react-three/fiber" +import { GizmoHelper, GizmoViewport, OrbitControls } from "@react-three/drei" +import { STLLoader } from "three/examples/jsm/loaders/STLLoader" +import { Rhino3dmLoader } from "three/examples/jsm/loaders/3DMLoader" +import { PerspectiveCamera } from "three" + +interface ThreejsArtifactViewerProps { + src: string + width: string + height: string + hasGizmo: boolean + filetype: string | undefined +} + +const CustomGizmoHelper: React.FC = () => { + return ( + + + + ) +} + +const calculateBoundingBox = (geometries: THREE.BufferGeometry[]) => { + const boundingBox = new THREE.Box3() + geometries.forEach((geometry) => { + const mesh = new THREE.Mesh(geometry) + boundingBox.expandByObject(mesh) + }) + return boundingBox +} + +export const ThreejsArtifactViewer: React.FC = ( + props +) => { + const [geometry, setGeometry] = useState([]) + const [modelSize, setModelSize] = useState( + new THREE.Vector3(10, 10, 10) + ) + const [cameraSettings, setCameraSettings] = useState( + new THREE.PerspectiveCamera() + ) + + const handleLoadedGeometries = (geometries: THREE.BufferGeometry[]) => { + setGeometry(geometries) + const boundingBox = calculateBoundingBox(geometries) + if (boundingBox !== null) { + const size = boundingBox.getSize(new THREE.Vector3()) + setModelSize(size) + } + } + + useEffect(() => { + if ("stl" === props.filetype) { + const stlLoader = new STLLoader() + stlLoader.load(props.src, (stlGeometries: THREE.BufferGeometry) => { + if (stlGeometries) { + handleLoadedGeometries([stlGeometries]) + } + }) + } else if ("3dm" === props.filetype) { + const loader = new Rhino3dmLoader() + loader.setLibraryPath("https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/") + loader.load(props.src, (object: THREE.Object3D) => { + const meshes = object.children as THREE.Mesh[] + const rhinoGeometries = meshes.map((mesh) => mesh.geometry) + if (rhinoGeometries.length > 0) { + rhinoGeometries.forEach((rhinoGeometry) => { + rhinoGeometry.rotateX(-Math.PI / 4) + }) + handleLoadedGeometries(rhinoGeometries) + } + }) + } + const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z) + const cameraSet = new THREE.PerspectiveCamera( + modelSize + ? Math.min( + 45, + Math.atan(modelSize.y / modelSize.z) * (180 / Math.PI) * 2 + ) + : 45, + window.innerWidth / window.innerHeight + ) + cameraSet.position.set(maxModelSize * 2, maxModelSize * 2, maxModelSize * 2) + setCameraSettings(cameraSet) + }, []) + + return ( + + + + + {props.hasGizmo && } + + {geometry.length > 0 && + geometry.map((geo, index) => ( + + + + ))} + + ) +} diff --git a/optuna_dashboard/ts/components/TrialList.tsx b/optuna_dashboard/ts/components/TrialList.tsx index c2c75dd3..eb700c13 100644 --- a/optuna_dashboard/ts/components/TrialList.tsx +++ b/optuna_dashboard/ts/components/TrialList.tsx @@ -20,6 +20,7 @@ import { CardContent, CardMedia, CardActionArea, + Modal, } from "@mui/material" import Chip from "@mui/material/Chip" import Divider from "@mui/material/Divider" @@ -34,6 +35,7 @@ import CheckBoxIcon from "@mui/icons-material/CheckBox" import UploadFileIcon from "@mui/icons-material/UploadFile" import DownloadIcon from "@mui/icons-material/Download" import DeleteIcon from "@mui/icons-material/Delete" +import FullscreenIcon from "@mui/icons-material/Fullscreen" import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" import StopCircleIcon from "@mui/icons-material/StopCircle" @@ -45,6 +47,7 @@ import { artifactIsAvailable } from "../state" import { actionCreator } from "../action" import { useDeleteArtifactDialog } from "./DeleteArtifactDialog" import { TrialFormWidgets } from "./TrialFormWidgets" +import { ThreejsArtifactViewer } from "./ThreejsArtifactViewer" const states: TrialState[] = [ "Complete", @@ -327,6 +330,9 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { const [openDeleteArtifactDialog, renderDeleteArtifactDialog] = useDeleteArtifactDialog() const [dragOver, setDragOver] = useState(false) + const [open3dModelViewer, setOpen3dModelViewer] = useState<{ + [key: string]: boolean + }>({}) const width = "200px" const height = "150px" @@ -366,6 +372,7 @@ const TrialArtifact: FC<{ trial: Trial }> = ({ trial }) => { e.dataTransfer.dropEffect = "copy" setDragOver(false) } + return ( <> = ({ trial }) => { ) + } else if ( + a.filename.endsWith(".stl") || + a.filename.endsWith(".3dm") + ) { + return ( + + + + + + + {a.filename} + + { + setOpen3dModelViewer(() => { + const obj = { ...open3dModelViewer } + obj[a.artifact_id] = true + return obj + }) + }} + > + + + { + setOpen3dModelViewer(() => { + const obj = { ...open3dModelViewer } + obj[a.artifact_id] = false + return obj + }) + }} + > + + + + + { + openDeleteArtifactDialog( + trial.study_id, + trial.trial_id, + a + ) + }} + > + + + + + + + + ) } else if (a.mimetype.startsWith("audio")) { return ( =6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==" + }, + "node_modules/@react-three/drei": { + "version": "9.80.0", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.80.0.tgz", + "integrity": "sha512-quBD0Ap7ygf9Yg94keIQpQNnveNSKoZUge9ybHd6UgE9QTdpfkPF3YgzTJLbDEoZKepCTE9ngpwUgFy+cpL9Pg==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.2", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", + "detect-gpu": "^5.0.28", + "glsl-noise": "^0.0.0", + "lodash.clamp": "^4.0.3", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "maath": "^0.6.0", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats-gl": "^1.0.4", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.6.0", + "three-stdlib": "^2.23.9", + "troika-three-text": "^0.47.2", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + }, + "peerDependencies": { + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.137" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "8.13.6", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.6.tgz", + "integrity": "sha512-V49lldHcbsC7PMnnf4aYrMpHPQe8R7hJYL0AEFjqwioY0nkwga9A+Jx6lCLVG02DF03xwCfJZv5cjZCChffsWg==", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-gl": ">=11.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@remix-run/router": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz", @@ -3399,6 +3593,11 @@ "node": ">= 10" } }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "node_modules/@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -3454,6 +3653,11 @@ "@types/ms": "*" } }, + "node_modules/@types/draco3d": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.2.tgz", + "integrity": "sha512-goh23EGr6CLV6aKPwN1p8kBD/7tT5V/bLpToSbarKrwVejqNrspVrv8DhliteYkkhZYrlq/fwKZRRUzH4XN88w==" + }, "node_modules/@types/eslint": { "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", @@ -3497,12 +3701,6 @@ "@types/unist": "*" } }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3621,6 +3819,11 @@ "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", "dev": true }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3685,27 +3888,14 @@ "csstype": "^3.0.2" } }, - "node_modules/@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", "dependencies": { - "@types/history": "^4.7.11", "@types/react": "*" } }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.5", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz", @@ -3734,6 +3924,24 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==" + }, + "node_modules/@types/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.154.0.tgz", + "integrity": "sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==", + "dependencies": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -3750,6 +3958,11 @@ "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.46.tgz", "integrity": "sha512-ki0OmbjSdAEfvmy5AYWFpMkRsPW+6h4ibQ4tzk8SJsS9dkrrD3B/U1eVvdNNWxAzntjq6o2sjSia6UBCoPH+Yg==" }, + "node_modules/@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==" + }, "node_modules/@types/yargs": { "version": "17.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", @@ -3952,6 +4165,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@use-gesture/core": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.27.tgz", + "integrity": "sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==" + }, + "node_modules/@use-gesture/react": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.27.tgz", + "integrity": "sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==", + "dependencies": { + "@use-gesture/core": "10.2.27" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -4490,6 +4719,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bidi-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz", + "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -4611,6 +4848,14 @@ "node": ">=6" } }, + "node_modules/camera-controls": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.7.0.tgz", + "integrity": "sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w==", + "peerDependencies": { + "three": ">=0.126.1" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001439", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", @@ -4685,6 +4930,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -4924,6 +5182,11 @@ "node": ">=12" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5035,6 +5298,14 @@ "node": ">=6" } }, + "node_modules/detect-gpu": { + "version": "5.0.34", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.34.tgz", + "integrity": "sha512-iHYDY2iy6OVvJVmTXvMrp5+OROP0Q62qTlrsC7wl3kZmm6yVAoOhQx5cIIxTVEEIZe1M1aPVI3RgsbG/Z6/7PQ==", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5111,6 +5382,11 @@ "node": ">=12" } }, + "node_modules/draco3d": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", + "integrity": "sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==" + }, "node_modules/electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -6195,6 +6471,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6465,6 +6746,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==" + }, "node_modules/goober": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", @@ -7419,6 +7705,25 @@ "node": ">=8" } }, + "node_modules/its-fine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz", + "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", + "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", @@ -10019,6 +10324,11 @@ "node": ">=6" } }, + "node_modules/ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10041,6 +10351,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10081,6 +10396,16 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10099,6 +10424,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -10159,6 +10494,15 @@ "lz-string": "bin/bin.js" } }, + "node_modules/maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -10435,6 +10779,19 @@ "node": ">= 8" } }, + "node_modules/meshline": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz", + "integrity": "sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" + }, "node_modules/mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", @@ -11076,6 +11433,11 @@ "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, + "node_modules/mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==" + }, "node_modules/moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -11259,6 +11621,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -11455,6 +11832,11 @@ "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.22.0.tgz", "integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw==" }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11628,6 +12010,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -11675,6 +12068,38 @@ "react": ">=16" } }, + "node_modules/react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-router": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz", @@ -11735,6 +12160,18 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "dependencies": { + "debounce": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + } + }, "node_modules/rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -11820,6 +12257,11 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -12078,7 +12520,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12516,6 +12957,16 @@ "node": ">=8" } }, + "node_modules/stats-gl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-1.0.4.tgz", + "integrity": "sha512-oxo13HHonoMWIYcrIu4xCk8IcFEFaqAOkMOMIyfvZFxNZzGy+jnW8sy0W3VfEjKQd5JX0Kp2KhePAKhtI6/TSw==" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -12543,6 +12994,11 @@ "node": ">=8" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12620,6 +13076,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -12774,6 +13238,45 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/three": { + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.155.0.tgz", + "integrity": "sha512-sNgCYmDijnIqkD/bMfk+1pHg3YzsxW7V2ChpuP6HCQ8NiZr3RufsXQr8M3SSUMjW4hG+sUk7YbyuY0DncaDTJQ==" + }, + "node_modules/three-mesh-bvh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.6.3.tgz", + "integrity": "sha512-xjuGLSI9nBATIsWcT/DnnNma5xXYyvBiXfUbhGLAFqItOlOKYF5JWsUOX+cuSAnSWovEoHzd5Emx23qKiByrlw==", + "peerDependencies": { + "three": ">= 0.151.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.23.13.tgz", + "integrity": "sha512-WU4XHs4E6szAyoREzTs5bUAc1WFadiz5jRjNlA7aIIqf+raT4jfA320P0XmlyZz/wJwmpZnOuki4kAFsmadkTQ==", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12834,6 +13337,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/troika-three-text": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz", + "integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.47.2", + "troika-worker-utils": "^0.47.2", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz", + "integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz", + "integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA==" + }, "node_modules/trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -13276,6 +13806,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -13414,6 +13952,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -13882,6 +14430,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -15036,6 +15605,35 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -15980,6 +16578,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@mediapipe/tasks-vision": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.2.tgz", + "integrity": "sha512-d8Q9uRK89ZRWmED2JLI9/blpJcfdbh0iEUuMo8TgkMzNfQBY1/GC0FEJWrairTwHkxIf6Oud1vFBP+aHicWqJA==" + }, "@mui/base": { "version": "5.0.0-alpha.127", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz", @@ -16127,6 +16730,110 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "requires": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "requires": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==" + }, + "@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "requires": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/three": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "requires": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + } + }, + "@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==" + }, + "@react-three/drei": { + "version": "9.80.0", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.80.0.tgz", + "integrity": "sha512-quBD0Ap7ygf9Yg94keIQpQNnveNSKoZUge9ybHd6UgE9QTdpfkPF3YgzTJLbDEoZKepCTE9ngpwUgFy+cpL9Pg==", + "requires": { + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.2", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", + "detect-gpu": "^5.0.28", + "glsl-noise": "^0.0.0", + "lodash.clamp": "^4.0.3", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "maath": "^0.6.0", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats-gl": "^1.0.4", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.6.0", + "three-stdlib": "^2.23.9", + "troika-three-text": "^0.47.2", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + } + }, + "@react-three/fiber": { + "version": "8.13.6", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.6.tgz", + "integrity": "sha512-V49lldHcbsC7PMnnf4aYrMpHPQe8R7hJYL0AEFjqwioY0nkwga9A+Jx6lCLVG02DF03xwCfJZv5cjZCChffsWg==", + "requires": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "dependencies": { + "scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, "@remix-run/router": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz", @@ -16239,6 +16946,11 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" }, + "@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -16294,6 +17006,11 @@ "@types/ms": "*" } }, + "@types/draco3d": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.2.tgz", + "integrity": "sha512-goh23EGr6CLV6aKPwN1p8kBD/7tT5V/bLpToSbarKrwVejqNrspVrv8DhliteYkkhZYrlq/fwKZRRUzH4XN88w==" + }, "@types/eslint": { "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", @@ -16337,12 +17054,6 @@ "@types/unist": "*" } }, - "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -16453,6 +17164,11 @@ "integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg==", "dev": true }, + "@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -16519,27 +17235,14 @@ } } }, - "@types/react-router": { - "version": "5.1.19", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.19.tgz", - "integrity": "sha512-Fv/5kb2STAEMT3wHzdKQK2z8xKq38EDIGVrutYLmQVVLe+4orDFquU52hQrULnEHinMKv9FSA6lf9+uNT1ITtA==", - "dev": true, + "@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", "requires": { - "@types/history": "^4.7.11", "@types/react": "*" } }, - "@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dev": true, - "requires": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "@types/react-syntax-highlighter": { "version": "15.5.5", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.5.tgz", @@ -16568,6 +17271,24 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==" + }, + "@types/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.154.0.tgz", + "integrity": "sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==", + "requires": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0", + "meshoptimizer": "~0.18.1" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -16584,6 +17305,11 @@ "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.46.tgz", "integrity": "sha512-ki0OmbjSdAEfvmy5AYWFpMkRsPW+6h4ibQ4tzk8SJsS9dkrrD3B/U1eVvdNNWxAzntjq6o2sjSia6UBCoPH+Yg==" }, + "@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==" + }, "@types/yargs": { "version": "17.0.19", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.19.tgz", @@ -16704,6 +17430,19 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@use-gesture/core": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.27.tgz", + "integrity": "sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==" + }, + "@use-gesture/react": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.27.tgz", + "integrity": "sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==", + "requires": { + "@use-gesture/core": "10.2.27" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -17146,6 +17885,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bidi-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz", + "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==", + "requires": { + "require-from-string": "^2.0.2" + } + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -17233,6 +17980,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camera-controls": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.7.0.tgz", + "integrity": "sha512-HONMoMYHieOCQOoweS639bdWHP/P/fvVGR08imnECGVUp04mqGfsX/zp1ZufLeiAA5hA6i1JhP6SrnOwh01C0w==", + "requires": {} + }, "caniuse-lite": { "version": "1.0.30001439", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz", @@ -17275,6 +18028,19 @@ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, + "chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -17464,6 +18230,11 @@ "whatwg-url": "^11.0.0" } }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -17545,6 +18316,14 @@ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, + "detect-gpu": { + "version": "5.0.34", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.34.tgz", + "integrity": "sha512-iHYDY2iy6OVvJVmTXvMrp5+OROP0Q62qTlrsC7wl3kZmm6yVAoOhQx5cIIxTVEEIZe1M1aPVI3RgsbG/Z6/7PQ==", + "requires": { + "webgl-constants": "^1.1.1" + } + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -17603,6 +18382,11 @@ "webidl-conversions": "^7.0.0" } }, + "draco3d": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", + "integrity": "sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==" + }, "electron-to-chromium": { "version": "1.4.284", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", @@ -18306,6 +19090,11 @@ "bser": "2.1.1" } }, + "fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -18498,6 +19287,11 @@ "slash": "^3.0.0" } }, + "glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==" + }, "goober": { "version": "2.1.13", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", @@ -19164,6 +19958,24 @@ "istanbul-lib-report": "^3.0.0" } }, + "its-fine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.1.1.tgz", + "integrity": "sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw==", + "requires": { + "@types/react-reconciler": "^0.28.0" + }, + "dependencies": { + "@types/react-reconciler": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", + "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", + "requires": { + "@types/react": "*" + } + } + } + }, "jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest/-/jest-29.3.1.tgz", @@ -21113,6 +21925,11 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -21129,6 +21946,11 @@ "type-check": "~0.4.0" } }, + "lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -21160,6 +21982,16 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==" + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -21178,6 +22010,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -21221,6 +22063,12 @@ "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==", "dev": true }, + "maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "requires": {} + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -21428,6 +22276,17 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "meshline": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz", + "integrity": "sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==", + "requires": {} + }, + "meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==" + }, "mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", @@ -21804,6 +22663,11 @@ "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, + "mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==" + }, "moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -21941,6 +22805,15 @@ "mimic-fn": "^2.1.0" } }, + "opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "requires": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + } + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -22083,6 +22956,11 @@ "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.22.0.tgz", "integrity": "sha512-2b7w4CQI06px8HVpKpgZtfuoDjuCLA26VlgdnG71UDBrJvtCYvXb39H4ElNv+CA1bbD4S98KanpWPRqTqlxBZw==" }, + "potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -22206,6 +23084,14 @@ "loose-envify": "^1.1.0" } }, + "react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "requires": { + "prop-types": "^15.6.0" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -22242,6 +23128,30 @@ "vfile": "^5.0.0" } }, + "react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==" + }, + "react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "dependencies": { + "scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, "react-router": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz", @@ -22282,6 +23192,14 @@ "prop-types": "^15.6.2" } }, + "react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "requires": { + "debounce": "^1.2.1" + } + }, "rechoir": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", @@ -22345,6 +23263,11 @@ "@babel/runtime": "^7.8.4" } }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -22535,8 +23458,7 @@ "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, "requires-port": { "version": "1.0.0", @@ -22853,6 +23775,16 @@ } } }, + "stats-gl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-1.0.4.tgz", + "integrity": "sha512-oxo13HHonoMWIYcrIu4xCk8IcFEFaqAOkMOMIyfvZFxNZzGy+jnW8sy0W3VfEjKQd5JX0Kp2KhePAKhtI6/TSw==" + }, + "stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -22874,6 +23806,11 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -22927,6 +23864,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "requires": {} + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -23034,6 +23977,40 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "three": { + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.155.0.tgz", + "integrity": "sha512-sNgCYmDijnIqkD/bMfk+1pHg3YzsxW7V2ChpuP6HCQ8NiZr3RufsXQr8M3SSUMjW4hG+sUk7YbyuY0DncaDTJQ==" + }, + "three-mesh-bvh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.6.3.tgz", + "integrity": "sha512-xjuGLSI9nBATIsWcT/DnnNma5xXYyvBiXfUbhGLAFqItOlOKYF5JWsUOX+cuSAnSWovEoHzd5Emx23qKiByrlw==", + "requires": {} + }, + "three-stdlib": { + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.23.13.tgz", + "integrity": "sha512-WU4XHs4E6szAyoREzTs5bUAc1WFadiz5jRjNlA7aIIqf+raT4jfA320P0XmlyZz/wJwmpZnOuki4kAFsmadkTQ==", + "requires": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + } + }, + "tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -23078,6 +24055,28 @@ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" }, + "troika-three-text": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz", + "integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==", + "requires": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.47.2", + "troika-worker-utils": "^0.47.2", + "webgl-sdf-generator": "1.1.1" + } + }, + "troika-three-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz", + "integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==", + "requires": {} + }, + "troika-worker-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz", + "integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA==" + }, "trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -23361,6 +24360,11 @@ "requires-port": "^1.0.0" } }, + "utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==" + }, "uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", @@ -23466,6 +24470,16 @@ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==" }, + "webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -23782,6 +24796,17 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" + }, + "zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "requires": {} + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index a319c4d7..82d587fe 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,9 @@ "@mui/icons-material": "^5.11.6", "@mui/lab": "^5.0.0-alpha.128", "@mui/material": "^5.12.1", + "@react-three/drei": "^9.80.0", + "@react-three/fiber": "^8.13.6", + "@types/three": "^0.154.0", "axios": "^1.2.1", "notistack": "^3.0.1", "plotly.js-dist-min": "^2.22.0", @@ -35,7 +38,8 @@ "rehype-mathjax": "^4.0.2", "rehype-raw": "^6.1.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1" + "remark-math": "^5.1.1", + "three": "^0.155.0" }, "devDependencies": { "@babel/core": "^7.14.3", @@ -45,7 +49,6 @@ "@types/plotly.js": "^2.12.11", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", - "@types/react-router-dom": "^5.3.3", "@types/react-syntax-highlighter": "^15.5.5", "@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/parser": "^4.26.1", diff --git a/pyproject.toml b/pyproject.toml index be4a18e0..d9c0082c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,14 @@ dependencies = [ ] dynamic = ["version"] +[project.optional-dependencies] +docs = [ + "boto3", + "streamlit", + "sphinx", + "sphinx_rtd_theme", +] + [project.scripts] optuna-dashboard = "optuna_dashboard._cli:main" diff --git a/python_tests/preferential/test_system_attrs.py b/python_tests/preferential/test_system_attrs.py index b9e22e19..10448d48 100644 --- a/python_tests/preferential/test_system_attrs.py +++ b/python_tests/preferential/test_system_attrs.py @@ -17,12 +17,13 @@ def test_report_and_get_preferences(storage_supplier: Callable[[], StorageSuppli study.ask() study.ask() - assert len(get_preferences(study)) == 0 + study_id = study._study_id + assert len(get_preferences(study_id, storage)) == 0 better, worse = study.trials[0], study.trials[1] - report_preferences(study, [(better, worse)]) - assert len(get_preferences(study)) == 1 + report_preferences(study_id, storage, [(better.number, worse.number)]) + assert len(get_preferences(study_id, storage)) == 1 - actual_better, actual_worse = get_preferences(study)[0] - assert actual_better.number == better.number - assert actual_worse.number == worse.number + actual_better, actual_worse = get_preferences(study_id, storage)[0] + assert actual_better == better.number + assert actual_worse == worse.number diff --git a/python_tests/test_api.py b/python_tests/test_api.py index 4dd2b3d2..c995c0e2 100644 --- a/python_tests/test_api.py +++ b/python_tests/test_api.py @@ -8,6 +8,7 @@ from optuna import get_all_study_summaries from optuna.study import StudyDirection from optuna_dashboard._app import create_app from optuna_dashboard._app import create_new_study +from optuna_dashboard.preferential import create_study from .wsgi_client import send_request @@ -99,6 +100,57 @@ class APITestCase(TestCase): ) self.assertEqual(status, 400) + def test_get_best_trials_of_preferential_study(self) -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + for _ in range(3): + trial = study.ask() + study.mark_comparison_ready(trial) + study.report_preference(study.trials[0], study.trials[1]) + + app = create_app(storage) + study_id = study._study._study_id + status, _, body = send_request( + app, + f"/api/studies/{study_id}", + "GET", + content_type="application/json", + ) + self.assertEqual(status, 200) + + best_trials = json.loads(body)["best_trials"] + assert len(best_trials) == 2 + assert best_trials[0]["number"] == 0 + assert best_trials[1]["number"] == 2 + + def test_report_preference(self) -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + for _ in range(3): + trial = study.ask() + study.mark_comparison_ready(trial) + + app = create_app(storage) + study_id = study._study._study_id + status, _, _ = send_request( + app, + f"/api/studies/{study_id}/preference", + "POST", + body=json.dumps({"best_trials": [0, 2], "worst_trials": [1]}), + content_type="application/json", + ) + self.assertEqual(status, 204) + + preferences = study.get_preferences() + preferences.sort(key=lambda x: (x[0].number, x[1].number)) + assert len(preferences) == 2 + better, worse = preferences[0] + assert better.number == 0 + assert worse.number == 1 + better, worse = preferences[1] + assert better.number == 2 + assert worse.number == 1 + def test_create_study(self) -> None: for name, directions, expected_status in [ ("single-objective success", ["minimize"], 201), diff --git a/python_tests/test_serializers.py b/python_tests/test_serializers.py index 4ea8c9bd..a75db32d 100644 --- a/python_tests/test_serializers.py +++ b/python_tests/test_serializers.py @@ -1,19 +1,63 @@ from __future__ import annotations -from unittest import TestCase - +import optuna from optuna_dashboard._serializer import serialize_attrs +from optuna_dashboard._serializer import serialize_study_detail +from optuna_dashboard._serializer import serialize_study_summary +from optuna_dashboard._storage import get_study_summaries +from optuna_dashboard.preferential import create_study -class SerializeAttrsTestCase(TestCase): - def test_serialize_bytes(self) -> None: - serialized = serialize_attrs({"bytes": b"This is a bytes object."}) - self.assertEqual(serialized[0]["value"], "") +def test_serialize_bytes() -> None: + serialized = serialize_attrs({"bytes": b"This is a bytes object."}) + assert serialized[0]["value"] == "" - def test_serialize_dict(self) -> None: - serialized = serialize_attrs( - { - "key": {"foo": "bar"}, - } - ) - self.assertLessEqual(len(serialized), 1) + +def test_serialize_dict() -> None: + serialized = serialize_attrs( + { + "key": {"foo": "bar"}, + } + ) + assert len(serialized) <= 1 + + +def test_get_study_detail_is_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + study = create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = study_summaries[0] + study_detail = serialize_study_detail(study_summary, [], study.trials, [], [], [], False) + assert study_detail["is_preferential"] + + +def test_get_study_detail_is_not_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + study = optuna.create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = study_summaries[0] + study_detail = serialize_study_detail(study_summary, [], study.trials, [], [], [], False) + assert not study_detail["is_preferential"] + + +def test_get_study_summary_is_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + + study_summary = serialize_study_summary(study_summaries[0]) + assert study_summary["is_preferential"] + + +def test_get_study_summary_is_not_preferential() -> None: + storage = optuna.storages.InMemoryStorage() + optuna.create_study(storage=storage) + study_summaries = get_study_summaries(storage) + assert len(study_summaries) == 1 + study_summary = serialize_study_summary(study_summaries[0]) + assert not study_summary["is_preferential"] diff --git a/python_tests/wsgi_client.py b/python_tests/wsgi_client.py index 3a6b078e..84fe8aac 100644 --- a/python_tests/wsgi_client.py +++ b/python_tests/wsgi_client.py @@ -6,12 +6,21 @@ from typing import Optional from typing import Union from bottle import Bottle +from optuna_dashboard._storage import trials_cache +from optuna_dashboard._storage import trials_cache_lock +from optuna_dashboard._storage import trials_last_fetched_at if typing.TYPE_CHECKING: from _typeshed.wsgi import WSGIEnvironment +def clear_inmemory_cache() -> None: + with trials_cache_lock: + trials_cache.clear() + trials_last_fetched_at.clear() + + def create_wsgi_env( path: str, method: str, @@ -66,6 +75,8 @@ def send_request( headers = headers or {} queries = queries or {} env = create_wsgi_env(path, method, content_type, bytes_body, queries, headers) + + clear_inmemory_cache() response_body = b"" iterable_body = app(env, start_response) for b in iterable_body: diff --git a/requirements.txt b/requirements.txt index 492d883e..59a9c586 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,8 +18,4 @@ boto3 moto[s3] # visual regression tests -pytest-playwright - -# docs -sphinx -sphinx_rtd_theme \ No newline at end of file +pytest-playwright \ No newline at end of file