mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-10 12:23:22 +08:00
Merge pull request #601 from moririn2528/preferential-graph
Add Preference Graph
This commit is contained in:
@@ -18,6 +18,7 @@ from ._named_objectives import get_objective_names
|
||||
from ._preferential_history import _SYSTEM_ATTR_PREFIX_HISTORY
|
||||
from .artifact._backend import list_trial_artifacts
|
||||
from .preferential._study import _SYSTEM_ATTR_PREFERENTIAL_STUDY
|
||||
from .preferential._system_attrs import get_preferences
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -163,6 +164,7 @@ def serialize_study_detail(
|
||||
serialized["form_widgets"] = form_widgets
|
||||
if serialized["is_preferential"]:
|
||||
serialized["preference_history"] = serialize_preference_history(system_attrs)
|
||||
serialized["preferences"] = get_preferences(system_attrs)
|
||||
serialized["plotly_graph_objects"] = [
|
||||
{"id": id_, "graph_object": graph_object}
|
||||
for id_, graph_object in plotly_graph_objects.items()
|
||||
|
||||
@@ -92,6 +92,7 @@ interface StudyDetailResponse {
|
||||
is_preferential: boolean
|
||||
objective_names?: string[]
|
||||
form_widgets?: FormWidgets
|
||||
preferences?: [number, number][]
|
||||
preference_history?: PreferenceHistoryResponce[]
|
||||
plotly_graph_objects: PlotlyGraphObject[]
|
||||
}
|
||||
@@ -129,6 +130,7 @@ export const getStudyDetailAPI = (
|
||||
objective_names: res.data.objective_names,
|
||||
form_widgets: res.data.form_widgets,
|
||||
is_preferential: res.data.is_preferential,
|
||||
preferences: res.data.preferences,
|
||||
preference_history: res.data.preference_history?.map(
|
||||
convertPreferenceHistory
|
||||
),
|
||||
|
||||
@@ -87,6 +87,15 @@ export const App: FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId/graph"}
|
||||
element={
|
||||
<StudyDetail
|
||||
toggleColorMode={toggleColorMode}
|
||||
page={"graph"}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={URL_PREFIX + "/studies/:studyId"}
|
||||
element={
|
||||
|
||||
@@ -17,8 +17,7 @@ import ListItemText from "@mui/material/ListItemText"
|
||||
import {
|
||||
drawerOpenState,
|
||||
reloadIntervalState,
|
||||
useStudyDetailValue,
|
||||
useStudySummaryValue,
|
||||
useStudyIsPreferencial,
|
||||
} from "../state"
|
||||
import { Link } from "react-router-dom"
|
||||
import AutoGraphIcon from "@mui/icons-material/AutoGraph"
|
||||
@@ -35,6 +34,7 @@ import OpenInNewIcon from "@mui/icons-material/OpenInNew"
|
||||
import QueryStatsIcon from "@mui/icons-material/QueryStats"
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt"
|
||||
import HistoryIcon from "@mui/icons-material/History"
|
||||
import LanIcon from "@mui/icons-material/Lan"
|
||||
import { Switch } from "@mui/material"
|
||||
import { actionCreator } from "../action"
|
||||
|
||||
@@ -47,6 +47,7 @@ export type PageId =
|
||||
| "trialList"
|
||||
| "note"
|
||||
| "preferenceHistory"
|
||||
| "graph"
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: drawerWidth,
|
||||
@@ -128,12 +129,8 @@ export const AppDrawer: FC<{
|
||||
const action = actionCreator()
|
||||
const [open, setOpen] = useRecoilState<boolean>(drawerOpenState)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyDetail =
|
||||
studyId !== undefined ? useStudyDetailValue(studyId) : null
|
||||
const studySummary =
|
||||
studyId !== undefined ? useStudySummaryValue(studyId) : null
|
||||
const isPreferential =
|
||||
studyDetail?.is_preferential ?? studySummary?.is_preferential ?? false
|
||||
studyId !== undefined ? useStudyIsPreferencial(studyId) : null
|
||||
|
||||
const styleListItem = {
|
||||
display: "block",
|
||||
@@ -206,7 +203,7 @@ export const AppDrawer: FC<{
|
||||
{isPreferential ? <ThumbUpAltIcon /> : <AutoGraphIcon />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={isPreferential ? "HumanInTheLoop" : "History"}
|
||||
primary={isPreferential ? "Feedback Preference" : "History"}
|
||||
sx={styleListItemText}
|
||||
/>
|
||||
</ListItemButton>
|
||||
@@ -227,7 +224,7 @@ export const AppDrawer: FC<{
|
||||
<HistoryIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="PreferenceHistory"
|
||||
primary="Preferences (History)"
|
||||
sx={styleListItemText}
|
||||
/>
|
||||
</ListItemButton>
|
||||
@@ -246,6 +243,24 @@ export const AppDrawer: FC<{
|
||||
<ListItemText primary="Analytics" sx={styleListItemText} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{isPreferential && (
|
||||
<ListItem key="PreferenceGraph" disablePadding sx={styleListItem}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={`${URL_PREFIX}/studies/${studyId}/graph`}
|
||||
sx={styleListItemButton}
|
||||
selected={page === "graph"}
|
||||
>
|
||||
<ListItemIcon sx={styleListItemIcon}>
|
||||
<LanIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary="Preferences (Graph)"
|
||||
sx={styleListItemText}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem key="TableList" disablePadding sx={styleListItem}>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
import React, { FC, useState, useCallback, useMemo, useEffect } from "react"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
useTheme,
|
||||
Typography,
|
||||
Box,
|
||||
Chip,
|
||||
} from "@mui/material"
|
||||
import { MarkdownRenderer } from "./Note"
|
||||
import ReactFlow, {
|
||||
Node,
|
||||
NodeProps,
|
||||
NodeTypes,
|
||||
Edge,
|
||||
DefaultEdgeOptions,
|
||||
applyNodeChanges,
|
||||
OnNodesChange,
|
||||
MiniMap,
|
||||
Position,
|
||||
Handle,
|
||||
} from "reactflow"
|
||||
import "reactflow/dist/style.css"
|
||||
import ELK from "elkjs/lib/elk.bundled.js"
|
||||
import { ElkNode } from "elkjs/lib/elk-api.js"
|
||||
|
||||
const elk = new ELK()
|
||||
const nodeWidth = 400
|
||||
const nodeHeight = 300
|
||||
const nodeMargin = 60
|
||||
|
||||
type NodeData = {
|
||||
trial?: Trial
|
||||
isBest: boolean
|
||||
}
|
||||
const GraphNode: FC<NodeProps<NodeData>> = ({ data, isConnectable }) => {
|
||||
const theme = useTheme()
|
||||
const trial = data.trial
|
||||
if (trial === undefined) {
|
||||
return null
|
||||
}
|
||||
const noteBody = trial.note.body
|
||||
const noteFC = useMemo(() => {
|
||||
return <MarkdownRenderer body={noteBody} />
|
||||
}, [noteBody])
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
width: nodeWidth,
|
||||
height: nodeHeight,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
displayDirection: "row",
|
||||
margin: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5">Trial {trial.number}</Typography>
|
||||
{data.isBest && (
|
||||
<Chip
|
||||
label={"Best Trial"}
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
style={{ background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
<CardContent>{noteFC}</CardContent>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
style={{ background: "#555" }}
|
||||
isConnectable={isConnectable}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
note: GraphNode,
|
||||
}
|
||||
const defaultEdgeOptions: DefaultEdgeOptions = {
|
||||
animated: true,
|
||||
}
|
||||
|
||||
const reductionPreference = (
|
||||
input_preferences: [number, number][]
|
||||
): [number, number][] => {
|
||||
const preferences: [number, number][] = []
|
||||
let n = 0
|
||||
for (const [source, target] of input_preferences) {
|
||||
if (
|
||||
preferences.find((p) => p[0] === source && p[1] === target) !==
|
||||
undefined ||
|
||||
input_preferences.find((p) => p[0] === target && p[1] === source) !==
|
||||
undefined
|
||||
) {
|
||||
continue
|
||||
}
|
||||
n = Math.max(n - 1, source, target) + 1
|
||||
preferences.push([source, target])
|
||||
}
|
||||
if (n === 0) {
|
||||
return []
|
||||
}
|
||||
const graph: number[][] = Array.from({ length: n }, () => [])
|
||||
const reverseGraph: number[][] = Array.from({ length: n }, () => [])
|
||||
const degree: number[] = Array.from({ length: n }, () => 0)
|
||||
for (const [source, target] of preferences) {
|
||||
graph[source].push(target)
|
||||
reverseGraph[target].push(source)
|
||||
degree[target]++
|
||||
}
|
||||
const topologicalOrder: number[] = []
|
||||
const q: number[] = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (degree[i] === 0) {
|
||||
q.push(i)
|
||||
}
|
||||
}
|
||||
while (q.length > 0) {
|
||||
const v = q.pop()
|
||||
if (v === undefined) break
|
||||
topologicalOrder.push(v)
|
||||
graph[v].forEach((u) => {
|
||||
degree[u]--
|
||||
if (degree[u] === 0) {
|
||||
q.push(u)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (topologicalOrder.length !== n) {
|
||||
console.error("cycle detected")
|
||||
return preferences
|
||||
}
|
||||
|
||||
const response: [number, number][] = []
|
||||
const descendants: Set<number>[] = Array.from(
|
||||
{ length: n },
|
||||
() => new Set<number>()
|
||||
)
|
||||
topologicalOrder.reverse().forEach((v) => {
|
||||
const descendant = new Set<number>([v])
|
||||
graph[v].forEach((u) => {
|
||||
descendants[u].forEach((d) => descendant.add(d))
|
||||
})
|
||||
graph[v].forEach((u) => {
|
||||
if (reverseGraph[u].filter((d) => descendant.has(d)).length === 1) {
|
||||
response.push([v, u])
|
||||
}
|
||||
})
|
||||
descendants[v] = descendant
|
||||
})
|
||||
return response
|
||||
}
|
||||
|
||||
export const PreferentialGraph: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
}> = ({ studyDetail }) => {
|
||||
const theme = useTheme()
|
||||
const [nodes, setNodes] = useState<Node[]>([])
|
||||
const [edges, setEdges] = useState<Edge[]>([])
|
||||
const onNodesChange: OnNodesChange = useCallback(
|
||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||
[setNodes]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (studyDetail === null) return
|
||||
if (!studyDetail.is_preferential || studyDetail.preferences === undefined)
|
||||
return
|
||||
const preferences = reductionPreference(studyDetail.preferences)
|
||||
const graph: ElkNode = {
|
||||
id: "root",
|
||||
layoutOptions: {
|
||||
"elk.algorithm": "layered",
|
||||
"elk.direction": "DOWN",
|
||||
"elk.layered.spacing.nodeNodeBetweenLayers": nodeMargin.toString(),
|
||||
"elk.spacing.nodeNode": nodeMargin.toString(),
|
||||
},
|
||||
children: studyDetail.trials.map((trial) => ({
|
||||
id: `${trial.number}`,
|
||||
targetPosition: "top",
|
||||
sourcePosition: "bottom",
|
||||
width: nodeWidth,
|
||||
height: nodeHeight,
|
||||
})),
|
||||
edges: preferences.map(([source, target]) => ({
|
||||
id: `e${source}-${target}`,
|
||||
sources: [`${source}`],
|
||||
targets: [`${target}`],
|
||||
})),
|
||||
}
|
||||
elk
|
||||
.layout(graph)
|
||||
.then((layoutedGraph) => {
|
||||
setNodes(
|
||||
layoutedGraph.children?.map((node, index) => {
|
||||
const trial = studyDetail.trials[index]
|
||||
return {
|
||||
id: `${trial.number}`,
|
||||
type: "note",
|
||||
data: {
|
||||
label: `Trial ${trial.number}`,
|
||||
trial: trial,
|
||||
isBest:
|
||||
studyDetail.best_trials.find(
|
||||
(t) => t.number === trial.number
|
||||
) !== undefined,
|
||||
},
|
||||
position: {
|
||||
x: node.x ?? 0,
|
||||
y: node.y ?? 0,
|
||||
},
|
||||
style: {
|
||||
width: nodeWidth,
|
||||
height: nodeHeight,
|
||||
padding: 0,
|
||||
},
|
||||
deletable: false,
|
||||
connectable: false,
|
||||
draggable: false,
|
||||
}
|
||||
}) ?? []
|
||||
)
|
||||
})
|
||||
.catch(console.error)
|
||||
setEdges(
|
||||
preferences.map((p) => {
|
||||
return {
|
||||
id: `e${p[0]}-${p[1]}`,
|
||||
source: `${p[0]}`,
|
||||
target: `${p[1]}`,
|
||||
style: { stroke: theme.palette.text.primary },
|
||||
} as Edge
|
||||
}) ?? []
|
||||
)
|
||||
}, [studyDetail, theme.palette.text.primary])
|
||||
|
||||
if (studyDetail === null || !studyDetail.is_preferential) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
defaultEdgeOptions={defaultEdgeOptions}
|
||||
nodeTypes={nodeTypes}
|
||||
zoomOnScroll={false}
|
||||
panOnScroll={true}
|
||||
minZoom={0.1}
|
||||
defaultViewport={{
|
||||
x: 0,
|
||||
y: 0,
|
||||
zoom: 0.5,
|
||||
}}
|
||||
>
|
||||
<MiniMap nodeStrokeWidth={1} zoomable pannable />
|
||||
</ReactFlow>
|
||||
)
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import { actionCreator } from "../action"
|
||||
import {
|
||||
reloadIntervalState,
|
||||
useStudyDetailValue,
|
||||
useStudyIsPreferencial,
|
||||
useStudyName,
|
||||
useStudySummaryValue,
|
||||
} from "../state"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
import { AppDrawer, PageId } from "./AppDrawer"
|
||||
@@ -32,6 +32,7 @@ import { StudyHistory } from "./StudyHistory"
|
||||
import { PreferentialTrials } from "./PreferentialTrials"
|
||||
import { PreferenceHistory } from "./PreferenceHistory"
|
||||
import { PreferentialAnalytics } from "./PreferentialAnalytics"
|
||||
import { PreferentialGraph } from "./PreferentialGraph"
|
||||
|
||||
interface ParamTypes {
|
||||
studyId: string
|
||||
@@ -51,11 +52,9 @@ export const StudyDetail: FC<{
|
||||
const action = actionCreator()
|
||||
const studyId = useURLVars()
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const studySummary = useStudySummaryValue(studyId)
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyName = useStudyName(studyId)
|
||||
const isPreferential =
|
||||
studySummary?.is_preferential ?? studyDetail?.is_preferential ?? false
|
||||
const isPreferential = useStudyIsPreferencial(studyId)
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
@@ -176,6 +175,17 @@ export const StudyDetail: FC<{
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
} else if (page === "graph") {
|
||||
content = (
|
||||
<Box
|
||||
sx={{
|
||||
height: `calc(100vh - ${theme.spacing(8)})`,
|
||||
padding: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<PreferentialGraph studyDetail={studyDetail} />
|
||||
</Box>
|
||||
)
|
||||
} else if (page == "preferenceHistory") {
|
||||
content = <PreferenceHistory studyDetail={studyDetail} />
|
||||
}
|
||||
|
||||
@@ -87,6 +87,12 @@ export const useStudyDirections = (
|
||||
return studyDetail?.directions || studySummary?.directions || null
|
||||
}
|
||||
|
||||
export const useStudyIsPreferencial = (studyId: number): boolean | null => {
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const studySummary = useStudySummaryValue(studyId)
|
||||
return studyDetail?.is_preferential || studySummary?.is_preferential || null
|
||||
}
|
||||
|
||||
export const useStudyName = (studyId: number): string | null => {
|
||||
const studyDetail = useStudyDetailValue(studyId)
|
||||
const studySummary = useStudySummaryValue(studyId)
|
||||
|
||||
Vendored
+1
-1
@@ -203,6 +203,7 @@ type StudyDetail = {
|
||||
is_preferential: boolean
|
||||
objective_names?: string[]
|
||||
form_widgets?: FormWidgets
|
||||
preferences?: [number, number][]
|
||||
preference_history?: PreferenceHistory[]
|
||||
plotly_graph_objects: PlotlyGraphObject[]
|
||||
}
|
||||
@@ -214,7 +215,6 @@ type StudyDetails = {
|
||||
type StudyParamImportance = {
|
||||
[study_id: string]: ParamImportance[][]
|
||||
}
|
||||
|
||||
type PreferenceHistory = {
|
||||
id: string
|
||||
preference_id: string
|
||||
|
||||
Generated
+1422
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@
|
||||
"@react-three/fiber": "^8.13.6",
|
||||
"@types/three": "^0.154.0",
|
||||
"axios": "^1.2.1",
|
||||
"elkjs": "^0.8.2",
|
||||
"notistack": "^3.0.1",
|
||||
"plotly.js-dist-min": "^2.22.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -34,6 +35,7 @@
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-router-dom": "^6.11.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"reactflow": "^11.8.3",
|
||||
"recoil": "^0.7.7",
|
||||
"rehype-mathjax": "^4.0.2",
|
||||
"rehype-raw": "^6.1.1",
|
||||
@@ -53,12 +55,14 @@
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/parser": "^4.26.1",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"esbuild-loader": "^2.18.0",
|
||||
"eslint": "^7.28.0",
|
||||
"jest": "^29.2.1",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"prettier": "^2.5.1",
|
||||
"style-loader": "^3.3.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-loader": "^9.2.7",
|
||||
"typescript": "^4.6.2",
|
||||
|
||||
+75
-59
@@ -1,65 +1,81 @@
|
||||
const webpack = require('webpack');
|
||||
const webpack = require("webpack")
|
||||
|
||||
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
|
||||
const isDev = mode === 'development';
|
||||
const mode =
|
||||
process.env.NODE_ENV === "production" ? "production" : "development"
|
||||
const isDev = mode === "development"
|
||||
|
||||
const typeScriptLoader = process.env.TYPESCRIPT_LOADER === "esbuild-loader" ? {
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: 'esbuild-loader',
|
||||
options: {
|
||||
loader: 'tsx',
|
||||
tsconfigRaw: require('./tsconfig.json')
|
||||
}
|
||||
} : {
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: __dirname + '/tsconfig.json',
|
||||
transpileOnly: isDev,
|
||||
happyPackMode: true
|
||||
}
|
||||
}
|
||||
const typeScriptLoader =
|
||||
process.env.TYPESCRIPT_LOADER === "esbuild-loader"
|
||||
? {
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: "esbuild-loader",
|
||||
options: {
|
||||
loader: "tsx",
|
||||
tsconfigRaw: require("./tsconfig.json"),
|
||||
},
|
||||
}
|
||||
: {
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
configFile: __dirname + "/tsconfig.json",
|
||||
transpileOnly: isDev,
|
||||
happyPackMode: true,
|
||||
},
|
||||
}
|
||||
|
||||
var config = {
|
||||
mode,
|
||||
entry: [__dirname + '/optuna_dashboard/ts/index.tsx'],
|
||||
output: {
|
||||
path: __dirname + '/optuna_dashboard/public/',
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/public/'
|
||||
},
|
||||
module: {
|
||||
rules: [{oneOf: [typeScriptLoader]}]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js']
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'APP_BAR_TITLE': JSON.stringify(process.env.APP_BAR_TITLE || "Optuna Dashboard"),
|
||||
'API_ENDPOINT': JSON.stringify(process.env.API_ENDPOINT),
|
||||
'URL_PREFIX': JSON.stringify(process.env.URL_PREFIX || "/dashboard")
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
if (isDev) {
|
||||
config.devtool = 'source-map';
|
||||
config.cache = {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
}
|
||||
}
|
||||
console.log('= = = = = = = = = = = = = = = = = = =');
|
||||
console.log('DEVELOPMENT BUILD');
|
||||
console.log(process.env.TYPESCRIPT_LOADER === 'esbuild-loader' ? 'esbuild-loader' : 'ts-loader');
|
||||
console.log('= = = = = = = = = = = = = = = = = = =');
|
||||
} else {
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
config.plugins.push(new CompressionPlugin())
|
||||
mode,
|
||||
entry: [__dirname + "/optuna_dashboard/ts/index.tsx"],
|
||||
output: {
|
||||
path: __dirname + "/optuna_dashboard/public/",
|
||||
filename: "bundle.js",
|
||||
publicPath: "/public/",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{ oneOf: [typeScriptLoader] },
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
APP_BAR_TITLE: JSON.stringify(
|
||||
process.env.APP_BAR_TITLE || "Optuna Dashboard"
|
||||
),
|
||||
API_ENDPOINT: JSON.stringify(process.env.API_ENDPOINT),
|
||||
URL_PREFIX: JSON.stringify(process.env.URL_PREFIX || "/dashboard"),
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
if (isDev) {
|
||||
config.devtool = "source-map"
|
||||
config.cache = {
|
||||
type: "filesystem",
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
}
|
||||
console.log("= = = = = = = = = = = = = = = = = = =")
|
||||
console.log("DEVELOPMENT BUILD")
|
||||
console.log(
|
||||
process.env.TYPESCRIPT_LOADER === "esbuild-loader"
|
||||
? "esbuild-loader"
|
||||
: "ts-loader"
|
||||
)
|
||||
console.log("= = = = = = = = = = = = = = = = = = =")
|
||||
} else {
|
||||
const CompressionPlugin = require("compression-webpack-plugin")
|
||||
config.plugins.push(new CompressionPlugin())
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
||||
Reference in New Issue
Block a user