more-compute 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frontend/app/globals.css +322 -77
- frontend/app/layout.tsx +98 -82
- frontend/components/Cell.tsx +234 -95
- frontend/components/Notebook.tsx +430 -199
- frontend/components/{AddCellButton.tsx → cell/AddCellButton.tsx} +0 -2
- frontend/components/cell/MonacoCell.tsx +726 -0
- frontend/components/layout/ConnectionBanner.tsx +41 -0
- frontend/components/{Sidebar.tsx → layout/Sidebar.tsx} +16 -11
- frontend/components/modals/ConfirmModal.tsx +154 -0
- frontend/components/modals/SuccessModal.tsx +140 -0
- frontend/components/output/MarkdownRenderer.tsx +116 -0
- frontend/components/popups/ComputePopup.tsx +674 -365
- frontend/components/popups/MetricsPopup.tsx +11 -7
- frontend/components/popups/SettingsPopup.tsx +11 -13
- frontend/contexts/PodWebSocketContext.tsx +247 -0
- frontend/eslint.config.mjs +11 -0
- frontend/lib/monaco-themes.ts +160 -0
- frontend/lib/settings.ts +128 -26
- frontend/lib/themes.json +9973 -0
- frontend/lib/websocket-native.ts +19 -8
- frontend/lib/websocket.ts +59 -11
- frontend/next.config.ts +8 -0
- frontend/package-lock.json +1705 -3
- frontend/package.json +8 -1
- frontend/styling_README.md +18 -0
- kernel_run.py +159 -42
- more_compute-0.2.0.dist-info/METADATA +126 -0
- more_compute-0.2.0.dist-info/RECORD +100 -0
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +31 -20
- morecompute/execution/worker.py +68 -7
- morecompute/models/__init__.py +31 -0
- morecompute/models/api_models.py +197 -0
- morecompute/notebook.py +50 -7
- morecompute/server.py +574 -94
- morecompute/services/data_manager.py +379 -0
- morecompute/services/lsp_service.py +335 -0
- morecompute/services/pod_manager.py +122 -20
- morecompute/services/pod_monitor.py +138 -0
- morecompute/services/prime_intellect.py +87 -63
- morecompute/utils/config_util.py +59 -0
- morecompute/utils/special_commands.py +11 -5
- morecompute/utils/zmq_util.py +51 -0
- frontend/components/MarkdownRenderer.tsx +0 -84
- frontend/components/popups/PythonPopup.tsx +0 -292
- more_compute-0.1.4.dist-info/METADATA +0 -173
- more_compute-0.1.4.dist-info/RECORD +0 -86
- /frontend/components/{CellButton.tsx → cell/CellButton.tsx} +0 -0
- /frontend/components/{ErrorModal.tsx → modals/ErrorModal.tsx} +0 -0
- /frontend/components/{CellOutput.tsx → output/CellOutput.tsx} +0 -0
- /frontend/components/{ErrorDisplay.tsx → output/ErrorDisplay.tsx} +0 -0
- {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/WHEEL +0 -0
- {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.1.4.dist-info → more_compute-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -69,17 +69,21 @@ const MetricsPopup: React.FC<{ onClose?: () => void }> = ({ onClose }) => {
|
|
|
69
69
|
</Panel>
|
|
70
70
|
)}
|
|
71
71
|
|
|
72
|
-
{hasGPU && (
|
|
73
|
-
<Panel
|
|
72
|
+
{hasGPU && metrics!.gpu!.map((gpu, index) => (
|
|
73
|
+
<Panel
|
|
74
|
+
key={index}
|
|
75
|
+
title={`GPU ${index} Utilization`}
|
|
76
|
+
icon={<Gauge size={14} />}
|
|
77
|
+
>
|
|
74
78
|
<BigValue
|
|
75
|
-
value={fmtPct(
|
|
76
|
-
subtitle={`Temp ${
|
|
79
|
+
value={fmtPct(gpu.util_percent)}
|
|
80
|
+
subtitle={`Temp ${gpu.temperature_c ?? "-"}°C`}
|
|
77
81
|
/>
|
|
78
82
|
<MiniChart
|
|
79
|
-
data={history.map((h) => (h.gpu && h.gpu[
|
|
83
|
+
data={history.map((h) => (h.gpu && h.gpu[index]?.util_percent) || 0)}
|
|
80
84
|
/>
|
|
81
85
|
</Panel>
|
|
82
|
-
)}
|
|
86
|
+
))}
|
|
83
87
|
|
|
84
88
|
{metrics?.network && (
|
|
85
89
|
<Panel title="Network" icon={<Activity size={14} />}>
|
|
@@ -145,7 +149,7 @@ const MiniChart: React.FC<{ data: number[] }> = ({ data }) => {
|
|
|
145
149
|
.join(" ");
|
|
146
150
|
return (
|
|
147
151
|
<svg width={width} height={height} className="mini-chart">
|
|
148
|
-
<polyline points={points} fill="none" stroke="
|
|
152
|
+
<polyline points={points} fill="none" stroke="var(--mc-primary)" strokeWidth="2" />
|
|
149
153
|
</svg>
|
|
150
154
|
);
|
|
151
155
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { THEMES, DEFAULT_SETTINGS, loadSettings, saveSettings, applyTheme, type NotebookSettings } from '@/lib/settings';
|
|
3
3
|
|
|
4
|
-
const SettingsPopup: React.FC<{
|
|
4
|
+
const SettingsPopup: React.FC<{ onSettingsChange?: (settings: NotebookSettings) => void }> = ({ onSettingsChange }) => {
|
|
5
5
|
const [settings, setSettings] = useState<NotebookSettings>(() => loadSettings());
|
|
6
6
|
const [settingsJson, setSettingsJson] = useState(() => JSON.stringify(loadSettings(), null, 2));
|
|
7
7
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -9,13 +9,17 @@ const SettingsPopup: React.FC<{ onClose?: () => void; onSettingsChange?: (settin
|
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
setSettingsJson(JSON.stringify(settings, null, 2));
|
|
11
11
|
applyTheme(settings.theme);
|
|
12
|
-
}, []);
|
|
12
|
+
}, [settings]);
|
|
13
13
|
|
|
14
14
|
const persistSettings = useCallback((updated: NotebookSettings) => {
|
|
15
15
|
setSettings(updated);
|
|
16
16
|
setSettingsJson(JSON.stringify(updated, null, 2));
|
|
17
17
|
saveSettings(updated);
|
|
18
18
|
applyTheme(updated.theme);
|
|
19
|
+
|
|
20
|
+
// Dispatch custom event to notify Monaco cells of theme change
|
|
21
|
+
window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme: updated.theme } }));
|
|
22
|
+
|
|
19
23
|
onSettingsChange?.(updated);
|
|
20
24
|
}, [onSettingsChange]);
|
|
21
25
|
|
|
@@ -29,7 +33,7 @@ const SettingsPopup: React.FC<{ onClose?: () => void; onSettingsChange?: (settin
|
|
|
29
33
|
};
|
|
30
34
|
setError(null);
|
|
31
35
|
persistSettings(merged);
|
|
32
|
-
} catch (err:
|
|
36
|
+
} catch (err: unknown) {
|
|
33
37
|
console.error('Failed to parse settings JSON', err);
|
|
34
38
|
setError('Invalid JSON. Please fix the syntax and try again.');
|
|
35
39
|
}
|
|
@@ -42,20 +46,14 @@ const SettingsPopup: React.FC<{ onClose?: () => void; onSettingsChange?: (settin
|
|
|
42
46
|
|
|
43
47
|
return (
|
|
44
48
|
<div className="settings-container">
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
Configure your notebook environment. Changes are saved to local storage.
|
|
48
|
-
<br /><br />
|
|
49
|
-
<em>Experiment with themes (e.g. "catppuccin") or customise fonts and autosave.</em>
|
|
50
|
-
</div>
|
|
51
|
-
<textarea
|
|
52
|
-
className="settings-editor"
|
|
49
|
+
<textarea
|
|
50
|
+
className="settings-editor"
|
|
53
51
|
value={settingsJson}
|
|
54
52
|
onChange={(e) => setSettingsJson(e.target.value)}
|
|
55
53
|
spellCheck={false}
|
|
56
54
|
/>
|
|
57
55
|
{error && (
|
|
58
|
-
<div style={{ color: '#dc2626', fontSize: '12px', marginTop: '8px' }}>{error}</div>
|
|
56
|
+
<div style={{ color: 'var(--mc-error-color, #dc2626)', fontSize: '12px', marginTop: '8px' }}>{error}</div>
|
|
59
57
|
)}
|
|
60
58
|
<div className="settings-actions">
|
|
61
59
|
<button className="btn btn-secondary" type="button" onClick={handleReset}>Reset to Defaults</button>
|
|
@@ -65,4 +63,4 @@ const SettingsPopup: React.FC<{ onClose?: () => void; onSettingsChange?: (settin
|
|
|
65
63
|
);
|
|
66
64
|
};
|
|
67
65
|
|
|
68
|
-
export default SettingsPopup;
|
|
66
|
+
export default SettingsPopup;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from 'react';
|
|
4
|
+
import { PodResponse } from '@/lib/api';
|
|
5
|
+
|
|
6
|
+
export interface GPUPod {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
status: "running" | "stopped" | "starting";
|
|
10
|
+
gpuType: string;
|
|
11
|
+
region: string;
|
|
12
|
+
costPerHour: number;
|
|
13
|
+
sshConnection: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ConnectionState = 'provisioning' | 'deploying' | 'connected' | null;
|
|
17
|
+
|
|
18
|
+
interface PodWebSocketContextType {
|
|
19
|
+
gpuPods: GPUPod[];
|
|
20
|
+
isConnected: boolean;
|
|
21
|
+
updatePod: (pod: GPUPod) => void;
|
|
22
|
+
setPods: (pods: GPUPod[]) => void;
|
|
23
|
+
addPod: (pod: GPUPod) => void;
|
|
24
|
+
removePod: (podId: string) => void;
|
|
25
|
+
registerAutoConnect: (podId: string, callback: (podId: string) => void) => void;
|
|
26
|
+
connectionState: ConnectionState;
|
|
27
|
+
connectingPodId: string | null;
|
|
28
|
+
connectedPodId: string | null;
|
|
29
|
+
setConnectionState: (state: ConnectionState) => void;
|
|
30
|
+
setConnectingPodId: (podId: string | null) => void;
|
|
31
|
+
setConnectedPodId: (podId: string | null) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PodWebSocketContext = createContext<PodWebSocketContextType | undefined>(undefined);
|
|
35
|
+
|
|
36
|
+
export const usePodWebSocket = () => {
|
|
37
|
+
const context = useContext(PodWebSocketContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error('usePodWebSocket must be used within a PodWebSocketProvider');
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
interface PodWebSocketProviderProps {
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const PodWebSocketProvider: React.FC<PodWebSocketProviderProps> = ({ children }) => {
|
|
49
|
+
const [gpuPods, setGpuPods] = useState<GPUPod[]>([]);
|
|
50
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
51
|
+
const [connectionState, setConnectionState] = useState<ConnectionState>(null);
|
|
52
|
+
const [connectingPodId, setConnectingPodId] = useState<string | null>(null);
|
|
53
|
+
const [connectedPodId, setConnectedPodId] = useState<string | null>(null);
|
|
54
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
55
|
+
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
56
|
+
const reconnectAttemptsRef = useRef(0);
|
|
57
|
+
const maxReconnectAttempts = 5;
|
|
58
|
+
const autoConnectCallbacksRef = useRef<Map<string, (podId: string) => void>>(new Map());
|
|
59
|
+
|
|
60
|
+
const updatePod = useCallback((updatedPod: GPUPod) => {
|
|
61
|
+
setGpuPods((prevPods) => {
|
|
62
|
+
const existingIndex = prevPods.findIndex((p) => p.id === updatedPod.id);
|
|
63
|
+
if (existingIndex >= 0) {
|
|
64
|
+
const newPods = [...prevPods];
|
|
65
|
+
newPods[existingIndex] = updatedPod;
|
|
66
|
+
return newPods;
|
|
67
|
+
} else {
|
|
68
|
+
return [...prevPods, updatedPod];
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const setPods = useCallback((pods: GPUPod[]) => {
|
|
74
|
+
setGpuPods(pods);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const addPod = useCallback((pod: GPUPod) => {
|
|
78
|
+
setGpuPods((prevPods) => {
|
|
79
|
+
const exists = prevPods.some((p) => p.id === pod.id);
|
|
80
|
+
if (exists) {
|
|
81
|
+
return prevPods.map((p) => (p.id === pod.id ? pod : p));
|
|
82
|
+
}
|
|
83
|
+
return [...prevPods, pod];
|
|
84
|
+
});
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const removePod = useCallback((podId: string) => {
|
|
88
|
+
setGpuPods((prevPods) => prevPods.filter((p) => p.id !== podId));
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const connectWebSocket = useCallback(() => {
|
|
92
|
+
// Clean up existing connection
|
|
93
|
+
if (wsRef.current) {
|
|
94
|
+
wsRef.current.close();
|
|
95
|
+
wsRef.current = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const wsUrl = 'ws://127.0.0.1:8000/ws';
|
|
99
|
+
const ws = new WebSocket(wsUrl);
|
|
100
|
+
|
|
101
|
+
ws.onopen = () => {
|
|
102
|
+
console.log('[PodWebSocket] Connected');
|
|
103
|
+
setIsConnected(true);
|
|
104
|
+
reconnectAttemptsRef.current = 0;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
ws.onmessage = (event) => {
|
|
108
|
+
try {
|
|
109
|
+
const message = JSON.parse(event.data);
|
|
110
|
+
if (message.type === "pod_status_update" && message.data) {
|
|
111
|
+
const podData = message.data;
|
|
112
|
+
|
|
113
|
+
// Map API status to UI status
|
|
114
|
+
let uiStatus: "running" | "stopped" | "starting" = "stopped";
|
|
115
|
+
const isFullyReady = podData.status === "ACTIVE" && podData.ssh_connection;
|
|
116
|
+
|
|
117
|
+
if (isFullyReady) {
|
|
118
|
+
uiStatus = "running";
|
|
119
|
+
} else if (podData.status === "ACTIVE" || podData.status === "PROVISIONING" || podData.status === "PENDING") {
|
|
120
|
+
uiStatus = "starting";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Update pod in the list
|
|
124
|
+
setGpuPods((prevPods) => {
|
|
125
|
+
const existingPodIndex = prevPods.findIndex((p) => p.id === podData.pod_id);
|
|
126
|
+
|
|
127
|
+
if (existingPodIndex >= 0) {
|
|
128
|
+
// Update existing pod
|
|
129
|
+
const updatedPods = [...prevPods];
|
|
130
|
+
const wasStarting = prevPods[existingPodIndex].status === "starting";
|
|
131
|
+
|
|
132
|
+
updatedPods[existingPodIndex] = {
|
|
133
|
+
id: podData.pod_id,
|
|
134
|
+
name: podData.name,
|
|
135
|
+
status: uiStatus,
|
|
136
|
+
gpuType: podData.gpu_name,
|
|
137
|
+
region: prevPods[existingPodIndex].region,
|
|
138
|
+
costPerHour: podData.price_hr,
|
|
139
|
+
sshConnection: podData.ssh_connection || null,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Trigger auto-connect callback if pod just became ready
|
|
143
|
+
// Small delay to ensure SSH is accepting connections
|
|
144
|
+
if (wasStarting && isFullyReady) {
|
|
145
|
+
console.log(`[PodWebSocket] Pod ${podData.pod_id} is ready, scheduling auto-connect in 5s`);
|
|
146
|
+
const callback = autoConnectCallbacksRef.current.get(podData.pod_id);
|
|
147
|
+
if (callback) {
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
console.log(`[PodWebSocket] Triggering auto-connect for pod ${podData.pod_id}`);
|
|
150
|
+
callback(podData.pod_id);
|
|
151
|
+
}, 5000); // Short delay, backend now handles retries
|
|
152
|
+
autoConnectCallbacksRef.current.delete(podData.pod_id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return updatedPods;
|
|
157
|
+
} else {
|
|
158
|
+
// Add new pod if not in list
|
|
159
|
+
return [
|
|
160
|
+
...prevPods,
|
|
161
|
+
{
|
|
162
|
+
id: podData.pod_id,
|
|
163
|
+
name: podData.name,
|
|
164
|
+
status: uiStatus,
|
|
165
|
+
gpuType: podData.gpu_name,
|
|
166
|
+
region: "Unknown",
|
|
167
|
+
costPerHour: podData.price_hr,
|
|
168
|
+
sshConnection: podData.ssh_connection || null,
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.error('[PodWebSocket] Failed to parse message:', err);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
ws.onerror = (error) => {
|
|
180
|
+
console.error('[PodWebSocket] Error:', error);
|
|
181
|
+
setIsConnected(false);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
ws.onclose = () => {
|
|
185
|
+
console.log('[PodWebSocket] Disconnected');
|
|
186
|
+
setIsConnected(false);
|
|
187
|
+
wsRef.current = null;
|
|
188
|
+
|
|
189
|
+
// Attempt to reconnect with exponential backoff
|
|
190
|
+
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
|
|
191
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000);
|
|
192
|
+
console.log(`[PodWebSocket] Reconnecting in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1}/${maxReconnectAttempts})`);
|
|
193
|
+
|
|
194
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
195
|
+
reconnectAttemptsRef.current++;
|
|
196
|
+
connectWebSocket();
|
|
197
|
+
}, delay);
|
|
198
|
+
} else {
|
|
199
|
+
console.log('[PodWebSocket] Max reconnect attempts reached');
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
wsRef.current = ws;
|
|
204
|
+
}, []);
|
|
205
|
+
|
|
206
|
+
// Register auto-connect callback
|
|
207
|
+
const registerAutoConnect = useCallback((podId: string, callback: (podId: string) => void) => {
|
|
208
|
+
autoConnectCallbacksRef.current.set(podId, callback);
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
// Expose registerAutoConnect through context
|
|
212
|
+
const contextValue: PodWebSocketContextType = React.useMemo(() => ({
|
|
213
|
+
gpuPods,
|
|
214
|
+
isConnected,
|
|
215
|
+
updatePod,
|
|
216
|
+
setPods,
|
|
217
|
+
addPod,
|
|
218
|
+
removePod,
|
|
219
|
+
registerAutoConnect,
|
|
220
|
+
connectionState,
|
|
221
|
+
connectingPodId,
|
|
222
|
+
connectedPodId,
|
|
223
|
+
setConnectionState,
|
|
224
|
+
setConnectingPodId,
|
|
225
|
+
setConnectedPodId,
|
|
226
|
+
}), [gpuPods, isConnected, updatePod, setPods, addPod, removePod, registerAutoConnect, connectionState, connectingPodId, connectedPodId]);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
connectWebSocket();
|
|
230
|
+
|
|
231
|
+
// Cleanup on unmount
|
|
232
|
+
return () => {
|
|
233
|
+
if (reconnectTimeoutRef.current) {
|
|
234
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
235
|
+
}
|
|
236
|
+
if (wsRef.current) {
|
|
237
|
+
wsRef.current.close();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}, [connectWebSocket]);
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<PodWebSocketContext.Provider value={contextValue}>
|
|
244
|
+
{children}
|
|
245
|
+
</PodWebSocketContext.Provider>
|
|
246
|
+
);
|
|
247
|
+
};
|
frontend/eslint.config.mjs
CHANGED
|
@@ -20,6 +20,17 @@ const eslintConfig = [
|
|
|
20
20
|
"next-env.d.ts",
|
|
21
21
|
],
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
rules: {
|
|
25
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
26
|
+
"@typescript-eslint/no-unused-vars": "warn",
|
|
27
|
+
"@typescript-eslint/no-unsafe-function-type": "warn",
|
|
28
|
+
"prefer-const": "warn",
|
|
29
|
+
"react/no-unescaped-entities": "warn",
|
|
30
|
+
"react-hooks/exhaustive-deps": "warn",
|
|
31
|
+
"@next/next/no-img-element": "warn",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
23
34
|
];
|
|
24
35
|
|
|
25
36
|
export default eslintConfig;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { editor } from 'monaco-editor';
|
|
2
|
+
import generatedThemes from './themes.json';
|
|
3
|
+
|
|
4
|
+
export type MonacoThemeName = keyof typeof generatedThemes.themes;
|
|
5
|
+
|
|
6
|
+
interface VSCodeTokenColor {
|
|
7
|
+
name?: string;
|
|
8
|
+
scope: string | string[];
|
|
9
|
+
settings?: {
|
|
10
|
+
foreground?: string;
|
|
11
|
+
background?: string;
|
|
12
|
+
fontStyle?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface VSCodeTheme {
|
|
17
|
+
displayName: string;
|
|
18
|
+
name: string;
|
|
19
|
+
type: 'light' | 'dark';
|
|
20
|
+
colors?: Record<string, string>;
|
|
21
|
+
tokenColors?: VSCodeTokenColor[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert VS Code theme to Monaco theme definition
|
|
26
|
+
*/
|
|
27
|
+
function convertToMonacoTheme(vsCodeTheme: VSCodeTheme): editor.IStandaloneThemeData {
|
|
28
|
+
const colors = vsCodeTheme.colors || {};
|
|
29
|
+
const tokenColors = vsCodeTheme.tokenColors || [];
|
|
30
|
+
|
|
31
|
+
// Convert token colors to Monaco rules format
|
|
32
|
+
const rules: editor.ITokenThemeRule[] = [];
|
|
33
|
+
tokenColors.forEach((tokenColor: VSCodeTokenColor) => {
|
|
34
|
+
const scopes = Array.isArray(tokenColor.scope) ? tokenColor.scope : [tokenColor.scope];
|
|
35
|
+
const settings = tokenColor.settings || {};
|
|
36
|
+
|
|
37
|
+
scopes.forEach((scope: string) => {
|
|
38
|
+
if (scope && settings) {
|
|
39
|
+
rules.push({
|
|
40
|
+
token: scope,
|
|
41
|
+
foreground: settings.foreground?.replace('#', ''),
|
|
42
|
+
background: settings.background?.replace('#', ''),
|
|
43
|
+
fontStyle: settings.fontStyle,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const monacoColors: Record<string, string> = {
|
|
50
|
+
'editor.background': colors['editor.background'],
|
|
51
|
+
'editor.foreground': colors['editor.foreground'],
|
|
52
|
+
'editor.lineHighlightBackground': colors['editor.lineHighlightBackground'] || colors['editor.background'],
|
|
53
|
+
'editor.selectionBackground': colors['editor.selectionBackground'],
|
|
54
|
+
'editorCursor.foreground': colors['editorCursor.foreground'],
|
|
55
|
+
'editorLineNumber.foreground': colors['editorLineNumber.foreground'],
|
|
56
|
+
'editorLineNumber.activeForeground': colors['editorLineNumber.activeForeground'],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Remove undefined values
|
|
60
|
+
Object.keys(monacoColors).forEach(key => {
|
|
61
|
+
if (monacoColors[key] === undefined) {
|
|
62
|
+
delete monacoColors[key];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
base: vsCodeTheme.type === 'light' ? 'vs' : 'vs-dark',
|
|
68
|
+
inherit: true,
|
|
69
|
+
rules,
|
|
70
|
+
colors: monacoColors,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load and define all themes in Monaco Editor
|
|
76
|
+
*/
|
|
77
|
+
export function loadMonacoThemes(monaco: typeof import('monaco-editor')) {
|
|
78
|
+
Object.entries(generatedThemes.themes).forEach(([themeName, themeData]) => {
|
|
79
|
+
const monacoTheme = convertToMonacoTheme(themeData as VSCodeTheme);
|
|
80
|
+
monaco.editor.defineTheme(themeName, monacoTheme);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get list of available theme names
|
|
86
|
+
*/
|
|
87
|
+
export function getAvailableThemes(): Array<{ name: string; displayName: string; type: 'light' | 'dark' }> {
|
|
88
|
+
return Object.entries(generatedThemes.themes).map(([name, data]) => ({
|
|
89
|
+
name,
|
|
90
|
+
displayName: data.displayName,
|
|
91
|
+
type: data.type as 'light' | 'dark',
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Lighten or darken a hex color
|
|
97
|
+
*/
|
|
98
|
+
function adjustColor(hex: string, percent: number): string {
|
|
99
|
+
const num = parseInt(hex.replace('#', ''), 16);
|
|
100
|
+
const r = Math.min(255, Math.max(0, (num >> 16) + percent));
|
|
101
|
+
const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + percent));
|
|
102
|
+
const b = Math.min(255, Math.max(0, (num & 0x0000FF) + percent));
|
|
103
|
+
return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get simplified color scheme for UI elements (non-Monaco parts)
|
|
108
|
+
*/
|
|
109
|
+
export function getThemeColors(themeName: string) {
|
|
110
|
+
const theme = generatedThemes.themes[themeName as keyof typeof generatedThemes.themes];
|
|
111
|
+
if (!theme) return null;
|
|
112
|
+
|
|
113
|
+
const colors: Record<string, string | undefined> = theme.colors || {};
|
|
114
|
+
const isLight = theme.type === 'light';
|
|
115
|
+
|
|
116
|
+
// Extract colors from VS Code theme
|
|
117
|
+
const editorBg = colors['editor.background'] || (isLight ? '#ffffff' : '#1e1e1e');
|
|
118
|
+
const editorFg = colors['editor.foreground'] || (isLight ? '#1f2937' : '#d4d4d4');
|
|
119
|
+
const activityBarBg = colors['activityBar.background'] || editorBg;
|
|
120
|
+
const sidebarBg = colors['sideBar.background'] || activityBarBg;
|
|
121
|
+
const buttonBg = colors['button.background'] || (isLight ? '#3b82f6' : '#60a5fa');
|
|
122
|
+
const buttonFg = colors['button.foreground'] || (isLight ? '#ffffff' : '#000000');
|
|
123
|
+
|
|
124
|
+
// Derive border colors
|
|
125
|
+
const borderColor = isLight ? adjustColor(editorBg, -20) : adjustColor(editorBg, 20);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
// Core colors
|
|
129
|
+
background: adjustColor(editorBg, isLight ? -5 : -10),
|
|
130
|
+
cellBackground: editorBg,
|
|
131
|
+
textColor: editorFg,
|
|
132
|
+
markdownColor: editorFg,
|
|
133
|
+
lineNumberColor: colors['editorLineNumber.foreground'] || (isLight ? '#9ca3af' : '#6b7280'),
|
|
134
|
+
|
|
135
|
+
// Primary/Secondary colors
|
|
136
|
+
primary: buttonBg,
|
|
137
|
+
primaryHover: adjustColor(buttonBg, isLight ? -20 : 20),
|
|
138
|
+
secondary: borderColor,
|
|
139
|
+
secondaryHover: adjustColor(borderColor, isLight ? -15 : 15),
|
|
140
|
+
|
|
141
|
+
// UI element colors
|
|
142
|
+
border: borderColor,
|
|
143
|
+
borderHover: adjustColor(borderColor, isLight ? -10 : 10),
|
|
144
|
+
sidebarBackground: sidebarBg,
|
|
145
|
+
sidebarForeground: colors['sideBar.foreground'] || editorFg,
|
|
146
|
+
buttonBackground: buttonBg,
|
|
147
|
+
buttonForeground: buttonFg,
|
|
148
|
+
inputBackground: editorBg,
|
|
149
|
+
inputBorder: borderColor,
|
|
150
|
+
|
|
151
|
+
// Headings and emphasis
|
|
152
|
+
headingColor: adjustColor(editorFg, isLight ? -30 : 30),
|
|
153
|
+
paragraphColor: editorFg,
|
|
154
|
+
|
|
155
|
+
// Output colors
|
|
156
|
+
outputBackground: adjustColor(editorBg, isLight ? -10 : -5),
|
|
157
|
+
outputTextColor: editorFg,
|
|
158
|
+
outputBorder: borderColor,
|
|
159
|
+
};
|
|
160
|
+
}
|