diff --git a/jupyterlab/src/apiClient.ts b/jupyterlab/src/apiClient.ts index 6a365c2e..0234f3d8 100644 --- a/jupyterlab/src/apiClient.ts +++ b/jupyterlab/src/apiClient.ts @@ -1,419 +1,232 @@ -import { requestAPI } from './handler'; - -type APIMeta = { - artifact_is_available: boolean; -}; - -export const getMetaInfoAPI = (): Promise => { - return requestAPI(`/api/meta`).then(res => res); -}; - -interface TrialResponse { - trial_id: number; - study_id: number; - number: number; - state: TrialState; - values?: TrialValueNumber[]; - intermediate_values: TrialIntermediateValue[]; - datetime_start?: string; - datetime_complete?: string; - params: TrialParam[]; - fixed_params: { - name: string; - param_external_value: string; - }[]; - user_attrs: Attribute[]; - note: Note; - artifacts: Artifact[]; - constraints: number[]; -} - -const convertTrialResponse = (res: TrialResponse): Trial => { - return { - trial_id: res.trial_id, - study_id: res.study_id, - number: res.number, - state: res.state, - values: res.values, - intermediate_values: res.intermediate_values, - datetime_start: res.datetime_start - ? new Date(res.datetime_start) - : undefined, - datetime_complete: res.datetime_complete - ? new Date(res.datetime_complete) - : undefined, - params: res.params, - fixed_params: res.fixed_params, - user_attrs: res.user_attrs, - note: res.note, - artifacts: res.artifacts, - constraints: res.constraints - }; -}; - -interface PreferenceHistoryResponse { - history: { - id: string - candidates: number[] - clicked: number - mode: PreferenceFeedbackMode - timestamp: string - preferences: [number, number][] - } - is_removed: boolean -} - -const convertPreferenceHistory = ( - res: PreferenceHistoryResponse -): PreferenceHistory => { - return { - id: res.history.id, - candidates: res.history.candidates, - clicked: res.history.clicked, - feedback_mode: res.history.mode, - timestamp: new Date(res.history.timestamp), - preferences: res.history.preferences, - is_removed: res.is_removed, - } -} - -interface StudyDetailResponse { - name: string; - datetime_start: string; - directions: StudyDirection[]; - user_attrs: Attribute[]; - trials: TrialResponse[]; - best_trials: TrialResponse[]; - intersection_search_space: SearchSpaceItem[]; - union_search_space: SearchSpaceItem[]; - union_user_attrs: AttributeSpec[]; - has_intermediate_values: boolean; - note: Note; - is_preferential: boolean; - objective_names?: string[]; - form_widgets?: FormWidgets; - preferences?: [number, number][]; - preference_history?: PreferenceHistoryResponse[]; - feedback_component_type: FeedbackComponentType; - skipped_trial_numbers?: number[] -} - -export const getStudyDetailAPI = ( - studyId: number, - nLocalTrials: number -): Promise => { - return requestAPI( - `/api/studies/${studyId}/?after=${nLocalTrials}`, - { - method: 'GET' - } - ).then(res => { - const trials = res.trials.map((trial): Trial => { - return convertTrialResponse(trial); - }); - const best_trials = res.best_trials.map((trial): Trial => { - return convertTrialResponse(trial); - }); - return { - id: studyId, - name: res.name, - datetime_start: new Date(res.datetime_start), - directions: res.directions, - user_attrs: res.user_attrs, - trials: trials, - best_trials: best_trials, - union_search_space: res.union_search_space, - intersection_search_space: res.intersection_search_space, - union_user_attrs: res.union_user_attrs, - has_intermediate_values: res.has_intermediate_values, - note: res.note, - objective_names: res.objective_names, - form_widgets: res.form_widgets, - is_preferential: res.is_preferential, - feedback_component_type: res.feedback_component_type, - preferences: res.preferences, - preference_history: res.preference_history?.map( - convertPreferenceHistory - ), - skipped_trial_numbers: res.skipped_trial_numbers ?? [], - }; - }); -}; - -interface StudySummariesResponse { - study_summaries: { - study_id: number; - study_name: string; - directions: StudyDirection[]; - user_attrs: Attribute[]; - is_preferential: boolean; - datetime_start?: string; - }[]; -} - -export const getStudySummariesAPI = (): Promise => { - return requestAPI(`/api/studies`).then(res => { - return res.study_summaries.map((study): StudySummary => { - return { - study_id: study.study_id, - study_name: study.study_name, - directions: study.directions, - user_attrs: study.user_attrs, - is_preferential: study.is_preferential, - datetime_start: study.datetime_start - ? new Date(study.datetime_start) - : undefined - }; - }); - }); -}; - -interface CreateNewStudyResponse { - study_summary: { - study_id: number; - study_name: string; - directions: StudyDirection[]; - user_attrs: Attribute[]; - is_preferential: boolean, - datetime_start?: string; - }; -} - -export const createNewStudyAPI = ( - studyName: string, - directions: StudyDirection[] -): Promise => { - return requestAPI(`/api/studies`, { - body: JSON.stringify({ - study_name: studyName, - directions - }), - method: 'POST' - }).then(res => { - const study_summary = res.study_summary; - return { - study_id: study_summary.study_id, - study_name: study_summary.study_name, - directions: study_summary.directions, - // best_trial: undefined, - user_attrs: study_summary.user_attrs, - is_preferential: study_summary.is_preferential, - datetime_start: study_summary.datetime_start - ? new Date(study_summary.datetime_start) - : undefined - }; - }); -}; - -export const deleteStudyAPI = (studyId: number): Promise => { - return requestAPI(`/api/studies/${studyId}`, { - method: 'DELETE' - }).then(() => { - return; - }); -}; - -type RenameStudyResponse = { - study_id: number; - study_name: string; - directions: StudyDirection[]; - user_attrs: Attribute[]; - is_preferential: boolean, - datetime_start?: string; -}; - -export const renameStudyAPI = ( - studyId: number, - studyName: string -): Promise => { - return requestAPI(`/api/studies/${studyId}/rename`, { - body: JSON.stringify({ study_name: studyName }), - method: 'POST' - }).then(res => { - return { - study_id: res.study_id, - study_name: res.study_name, - directions: res.directions, - user_attrs: res.user_attrs, - is_preferential: res.is_preferential, - datetime_start: res.datetime_start - ? new Date(res.datetime_start) - : undefined - }; - }); -}; - -export const saveStudyNoteAPI = ( - studyId: number, - note: { version: number; body: string } -): Promise => { - return requestAPI(`/api/studies/${studyId}/note`, { - body: JSON.stringify(note), - method: 'PUT' - }).then(() => { - return; - }); -}; - -export const saveTrialNoteAPI = ( - studyId: number, - trialId: number, - note: { version: number; body: string } -): Promise => { - return requestAPI(`/api/studies/${studyId}/${trialId}/note`, { - body: JSON.stringify(note), - method: 'PUT' - }).then(() => { - return; - }); -}; - -type UploadArtifactAPIResponse = { - artifact_id: string; - artifacts: Artifact[]; -}; - -export const uploadArtifactAPI = ( - studyId: number, - trialId: number, - fileName: string, - dataUrl: string -): Promise => { - return requestAPI( - `/api/artifacts/${studyId}/${trialId}`, - { - body: JSON.stringify({ - file: dataUrl, - filename: fileName - }), - method: 'POST' - } - ).then(res => { - return res; - }); -}; - -export const deleteArtifactAPI = ( - studyId: number, - trialId: number, - artifactId: string -): Promise => { - return requestAPI( - `/api/artifacts/${studyId}/${trialId}/${artifactId}`, - { - method: 'DELETE' - } - ).then(() => { - return; - }); -}; - -export const tellTrialAPI = ( - trialId: number, - state: TrialStateFinished, - values?: number[] -): Promise => { - const req: { state: TrialState; values?: number[] } = { - state: state, - values: values - }; - - return requestAPI(`/api/trials/${trialId}/tell`, { - body: JSON.stringify(req), - method: 'POST' - }).then(() => { - return; - }); -}; - -export const saveTrialUserAttrsAPI = ( - trialId: number, - user_attrs: { [key: string]: number | string } -): Promise => { - const req = { user_attrs: user_attrs }; - - return requestAPI(`/api/trials/${trialId}/user-attrs`, { - body: JSON.stringify(req), - method: 'POST' - }).then(() => { - return; - }); -}; - -interface ParamImportancesResponse { - param_importances: ParamImportance[][]; -} - -export const getParamImportances = ( - studyId: number -): Promise => { - return requestAPI( - `/api/studies/${studyId}/param_importances` - ).then(res => { - return res.param_importances; - }); -}; - -export const reportPreferenceAPI = ( - studyId: number, - candidates: number[], - clicked: number -): Promise => { - return requestAPI(`/api/studies/${studyId}/preference`, { - body: JSON.stringify({ - candidates: candidates, - clicked: clicked, - mode: "ChooseWorst", - }), - method: 'POST' - }).then(() => { - return - }); -} - -export const skipPreferentialTrialAPI = ( - studyId: number, - trialId: number -): Promise => { - return requestAPI(`/api/studies/${studyId}/${trialId}/skip`, { - method: 'POST' - }).then(() => { - return - }) -} - -export const removePreferentialHistoryAPI = ( - studyId: number, - historyUuid: string -): Promise => { - return requestAPI(`/api/studies/${studyId}/preference/${historyUuid}`,{ - method: 'DELETE' - }).then(() => { - return - }) -} - -export const restorePreferentialHistoryAPI = ( - studyId: number, - historyUuid: string -): Promise => { - return requestAPI(`/api/studies/${studyId}/preference/${historyUuid}`,{ - method: 'POST' - }).then(() => { - return - }) -} - -export const reportFeedbackComponentAPI = ( - studyId: number, - component_type: FeedbackComponentType -): Promise => { - return requestAPI( - `/api/studies/${studyId}/preference_feedback_component`, { - body: JSON.stringify({ component_type: component_type }), +import {APIClient, APIMeta, CompareStudiesPlotType, CreateNewStudyResponse, FeedbackComponentType, ParamImportance, ParamImportancesResponse, PlotResponse, PlotType, RenameStudyResponse, StudyDetail, StudyDetailResponse, StudySummariesResponse, StudySummary, Trial, UploadArtifactAPIResponse} from "@optuna/optuna-dashboard" +import { requestAPI } from "./handler"; +import * as Optuna from "@optuna/types" + +export class JupyterlabAPIClient extends APIClient { + constructor() { + super() + } + + getMetaInfo = () => requestAPI(`/api/meta`).then(res => res); + getStudyDetail = (studyId: number, nLocalTrials: number): Promise => requestAPI( + `/api/studies/${studyId}/?after=${nLocalTrials}`, + { + method: 'GET' + } + ).then(res => { + const trials = res.trials.map((trial): Trial => { + return this.convertTrialResponse(trial); + }); + const best_trials = res.best_trials.map((trial): Trial => { + return this.convertTrialResponse(trial); + }); + return { + id: studyId, + name: res.name, + datetime_start: new Date(res.datetime_start), + directions: res.directions, + user_attrs: res.user_attrs, + trials: trials, + best_trials: best_trials, + union_search_space: res.union_search_space, + intersection_search_space: res.intersection_search_space, + union_user_attrs: res.union_user_attrs, + has_intermediate_values: res.has_intermediate_values, + note: res.note, + objective_names: res.objective_names, + form_widgets: res.form_widgets, + is_preferential: res.is_preferential, + feedback_component_type: res.feedback_component_type, + preferences: res.preferences, + preference_history: res.preference_history?.map( + this.convertPreferenceHistory + ), + plotly_graph_objects: res.plotly_graph_objects, // TODO: Support this + artifacts: res.artifacts, // TODO: Support this + skipped_trial_numbers: res.skipped_trial_numbers ?? [], + }; + }); + getStudySummaries = (): Promise => requestAPI(`/api/studies`).then(res => { + return res.study_summaries.map((study): StudySummary => { + return { + study_id: study.study_id, + study_name: study.study_name, + directions: study.directions, + user_attrs: study.user_attrs, + is_preferential: study.is_preferential, + datetime_start: study.datetime_start + ? new Date(study.datetime_start) + : undefined + }; + }); + }); + createNewStudy = (studyName: string, directions: Optuna.StudyDirection[]): Promise => requestAPI(`/api/studies`, { + body: JSON.stringify({ + study_name: studyName, + directions + }), method: 'POST' + }).then(res => { + const study_summary = res.study_summary; + return { + study_id: study_summary.study_id, + study_name: study_summary.study_name, + directions: study_summary.directions, + // best_trial: undefined, + user_attrs: study_summary.user_attrs, + is_preferential: study_summary.is_preferential, + datetime_start: study_summary.datetime_start + ? new Date(study_summary.datetime_start) + : undefined + }; + }); + deleteStudy = (studyId: number, removeAssociatedArtifacts: boolean): Promise => requestAPI(`/api/studies/${studyId}`, { + method: 'DELETE' + }).then(() => { + return; + }); + renameStudy = (studyId: number, studyName: string): Promise => requestAPI(`/api/studies/${studyId}/rename`, { + body: JSON.stringify({ study_name: studyName }), + method: 'POST' + }).then(res => { + return { + study_id: res.study_id, + study_name: res.study_name, + directions: res.directions, + user_attrs: res.user_attrs, + is_preferential: res.is_prefential, // TODO: Fix typo + datetime_start: res.datetime_start + ? new Date(res.datetime_start) + : undefined + }; + }); + saveStudyNote = (studyId: number, note: { version: number, body:string }): Promise => requestAPI(`/api/studies/${studyId}/note`, { + body: JSON.stringify(note), + method: 'PUT' + }).then(() => { + return; + }); + saveTrialNote = (studyId: number, trialId: number, note: { version: number, body: string }): Promise => requestAPI(`/api/studies/${studyId}/${trialId}/note`, { + body: JSON.stringify(note), + method: 'PUT' + }).then(() => { + return; + }); + uploadTrialArtifact = ( + studyId: number, + trialId: number, + fileName: string, + dataUrl: string + ): Promise => requestAPI( + `/api/artifacts/${studyId}/${trialId}`, // TODO: Make API + { + body: JSON.stringify({ + file: dataUrl, + filename: fileName + }), + method: 'POST' + } + ).then(res => { + return res; + }); + uploadStudyArtifact = ( + studyId: number, + fileName: string, + dataUrl: string + ): Promise => requestAPI( + `/api/artifacts/${studyId}`, // TODO: Make API + { + body: JSON.stringify({ + file: dataUrl, + filename: fileName + }), + method: 'POST' + } + ).then(res => { + return res; + }); + deleteTrialArtifact = ( + studyId: number, + trialId: number, + artifactId: string + ): Promise => requestAPI( + `/api/artifacts/${studyId}/${trialId}/${artifactId}`, // TODO: Make API + { + method: 'DELETE' + } + ).then(() => { + return; + }); + deleteStudyArtifact = (studyId: number, artifactId: string): Promise => requestAPI( + `/api/artifacts/${studyId}/${artifactId}`, // TODO: Make API + { + method: 'DELETE' + } + ).then(() => { + return; + }); + tellTrial = async (trialId: number, state: Optuna.TrialStateFinished, values?: number[]): Promise => { + const req: { state: Optuna.TrialState; values?: number[] } = { + state: state, + values: values + }; + + await requestAPI(`/api/trials/${trialId}/tell`, { + body: JSON.stringify(req), + method: 'POST' + }); + } + saveTrialUserAttrs = async (trialId: number, user_attrs: { [key: string]: number | string }): Promise => { + const req = { user_attrs: user_attrs }; + + await requestAPI(`/api/trials/${trialId}/user-attrs`, { + body: JSON.stringify(req), + method: 'POST' + }); + return; } - ).then(() => { - return - }) -} + getParamImportances = (studyId: number): Promise => requestAPI( + `/api/studies/${studyId}/param_importances` + ).then(res => { + return res.param_importances; + }); + reportPreference = (studyId: number, candidates: number[], clicked: number): Promise => requestAPI(`/api/studies/${studyId}/preference`, { + body: JSON.stringify({ + candidates: candidates, + clicked: clicked, + mode: "ChooseWorst", + }), + method: 'POST' + }).then(() => { + return + }); + skipPreferentialTrial = (studyId: number, trialId: number): Promise => requestAPI(`/api/studies/${studyId}/${trialId}/skip`, { + method: 'POST' + }).then(() => { + return + }); + removePreferentialHistory = (studyId: number, historyUuid: string): Promise => requestAPI(`/api/studies/${studyId}/preference/${historyUuid}`,{ + method: 'DELETE' + }).then(() => { + return + }) + restorePreferentialHistory = (studyId: number, historyUuid: string): Promise => requestAPI(`/api/studies/${studyId}/preference/${historyUuid}`,{ + method: 'POST' + }).then(() => { + return + }) + reportFeedbackComponent = (studyId: number, component_type: FeedbackComponentType): Promise => requestAPI( + `/api/studies/${studyId}/preference_feedback_component`, { + body: JSON.stringify({ component_type: component_type }), + method: 'POST' + } + ).then(() => { + return + }) + getPlot = (studyId: number, plotType: PlotType): Promise => { + throw new Error("Method not implemented."); // TODO: Implement + } + getCompareStudiesPlot = (studyIds: number[], plotType: CompareStudiesPlotType): Promise => { + throw new Error("Method not implemented."); // TODO: Implement + } +} \ No newline at end of file diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index f1a7ad8c..023dd1ff 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -21,7 +21,7 @@ export type APIMeta = { plotlypy_is_available: boolean } -interface TrialResponse { +export interface TrialResponse { trial_id: number study_id: number number: number @@ -41,7 +41,7 @@ interface TrialResponse { constraints: number[] } -interface PreferenceHistoryResponse { +export interface PreferenceHistoryResponse { history: { id: string candidates: number[] @@ -103,7 +103,7 @@ export type RenameStudyResponse = { study_name: string directions: Optuna.StudyDirection[] user_attrs: Optuna.Attribute[] - is_prefential: boolean + is_prefential: boolean // TODO: Fix typo datetime_start?: string } diff --git a/optuna_dashboard/ts/pkg_index.tsx b/optuna_dashboard/ts/pkg_index.tsx index c8e04aa6..6fa846b6 100644 --- a/optuna_dashboard/ts/pkg_index.tsx +++ b/optuna_dashboard/ts/pkg_index.tsx @@ -1,5 +1,66 @@ -import { APIClientProvider } from "./apiClientProvider" -import { AxiosClient } from "./axiosClient" -import { App } from "./components/App" - -export { AxiosClient, APIClientProvider, App } +import { + APIClient, + APIMeta, + CompareStudiesPlotType, + CreateNewStudyResponse, + ParamImportancesResponse, + PlotResponse, + PlotType, + PreferenceHistoryResponse, + RenameStudyResponse, + StudyDetailResponse, + StudySummariesResponse, + TrialResponse, + UploadArtifactAPIResponse, + } from "./apiClient" + import { APIClientProvider } from "./apiClientProvider" + import { AxiosClient } from "./axiosClient" + import { App } from "./components/App" + import { + Artifact, + FeedbackComponentType, + FormWidgets, + Note, + ParamImportance, + PlotlyGraphObject, + PreferenceFeedbackMode, + PreferenceHistory, + SearchSpaceItem, + StudyDetail, + StudySummary, + Trial, + TrialParam, + } from "./types/optuna" + + export { + AxiosClient, + APIClientProvider, + App, + APIClient, + Artifact, + FeedbackComponentType, + FormWidgets, + Note, + PlotlyGraphObject, + PreferenceFeedbackMode, + PreferenceHistory, + SearchSpaceItem, + TrialResponse, + PreferenceHistoryResponse, + StudyDetailResponse, + StudySummariesResponse, + CreateNewStudyResponse, + RenameStudyResponse, + UploadArtifactAPIResponse, + ParamImportancesResponse, + APIMeta, + StudyDetail, + StudySummary, + Trial, + TrialParam, + ParamImportance, + CompareStudiesPlotType, + PlotType, + PlotResponse, + } + \ No newline at end of file