more-compute 0.1.3__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.
Files changed (55) hide show
  1. frontend/app/globals.css +322 -77
  2. frontend/app/layout.tsx +98 -82
  3. frontend/components/Cell.tsx +234 -95
  4. frontend/components/Notebook.tsx +430 -199
  5. frontend/components/{AddCellButton.tsx → cell/AddCellButton.tsx} +0 -2
  6. frontend/components/cell/MonacoCell.tsx +726 -0
  7. frontend/components/layout/ConnectionBanner.tsx +41 -0
  8. frontend/components/{Sidebar.tsx → layout/Sidebar.tsx} +16 -11
  9. frontend/components/modals/ConfirmModal.tsx +154 -0
  10. frontend/components/modals/SuccessModal.tsx +140 -0
  11. frontend/components/output/MarkdownRenderer.tsx +116 -0
  12. frontend/components/popups/ComputePopup.tsx +674 -365
  13. frontend/components/popups/MetricsPopup.tsx +11 -7
  14. frontend/components/popups/SettingsPopup.tsx +11 -13
  15. frontend/contexts/PodWebSocketContext.tsx +247 -0
  16. frontend/eslint.config.mjs +11 -0
  17. frontend/lib/monaco-themes.ts +160 -0
  18. frontend/lib/settings.ts +128 -26
  19. frontend/lib/themes.json +9973 -0
  20. frontend/lib/websocket-native.ts +19 -8
  21. frontend/lib/websocket.ts +59 -11
  22. frontend/next.config.ts +8 -0
  23. frontend/package-lock.json +1705 -3
  24. frontend/package.json +8 -1
  25. frontend/styling_README.md +18 -0
  26. kernel_run.py +161 -43
  27. more_compute-0.2.0.dist-info/METADATA +126 -0
  28. more_compute-0.2.0.dist-info/RECORD +100 -0
  29. morecompute/__version__.py +1 -0
  30. morecompute/execution/executor.py +31 -20
  31. morecompute/execution/worker.py +68 -7
  32. morecompute/models/__init__.py +31 -0
  33. morecompute/models/api_models.py +197 -0
  34. morecompute/notebook.py +50 -7
  35. morecompute/server.py +574 -94
  36. morecompute/services/data_manager.py +379 -0
  37. morecompute/services/lsp_service.py +335 -0
  38. morecompute/services/pod_manager.py +122 -20
  39. morecompute/services/pod_monitor.py +138 -0
  40. morecompute/services/prime_intellect.py +87 -63
  41. morecompute/utils/config_util.py +59 -0
  42. morecompute/utils/special_commands.py +11 -5
  43. morecompute/utils/zmq_util.py +51 -0
  44. frontend/components/MarkdownRenderer.tsx +0 -84
  45. frontend/components/popups/PythonPopup.tsx +0 -292
  46. more_compute-0.1.3.dist-info/METADATA +0 -173
  47. more_compute-0.1.3.dist-info/RECORD +0 -85
  48. /frontend/components/{CellButton.tsx → cell/CellButton.tsx} +0 -0
  49. /frontend/components/{ErrorModal.tsx → modals/ErrorModal.tsx} +0 -0
  50. /frontend/components/{CellOutput.tsx → output/CellOutput.tsx} +0 -0
  51. /frontend/components/{ErrorDisplay.tsx → output/ErrorDisplay.tsx} +0 -0
  52. {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/WHEEL +0 -0
  53. {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/entry_points.txt +0 -0
  54. {more_compute-0.1.3.dist-info → more_compute-0.2.0.dist-info}/licenses/LICENSE +0 -0
  55. {more_compute-0.1.3.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 title="GPU Utilization" icon={<Gauge size={14} />}>
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(metrics!.gpu![0].util_percent)}
76
- subtitle={`Temp ${metrics!.gpu![0].temperature_c ?? "-"}°C`}
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[0]?.util_percent) || 0)}
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="#3b82f6" strokeWidth="2" />
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<{ onClose?: () => void; onSettingsChange?: (settings: NotebookSettings) => void }> = ({ onClose, onSettingsChange }) => {
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: any) {
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
- <div style={{ marginBottom: '16px', padding: '12px', background: '#f8f9fa', borderRadius: '6px', color: '#6b7280', fontSize: '13px', lineHeight: 1.5 }}>
46
- <strong>MoreCompute Settings</strong><br />
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
+ };
@@ -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
+ }