more-compute 0.4.4__py3-none-any.whl → 0.5.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 (57) hide show
  1. frontend/app/globals.css +734 -27
  2. frontend/app/layout.tsx +13 -3
  3. frontend/components/Notebook.tsx +2 -14
  4. frontend/components/cell/MonacoCell.tsx +99 -5
  5. frontend/components/layout/Sidebar.tsx +39 -4
  6. frontend/components/panels/ClaudePanel.tsx +461 -0
  7. frontend/components/popups/ComputePopup.tsx +738 -447
  8. frontend/components/popups/FilterPopup.tsx +305 -189
  9. frontend/components/popups/MetricsPopup.tsx +20 -1
  10. frontend/components/popups/ProviderConfigModal.tsx +322 -0
  11. frontend/components/popups/ProviderDropdown.tsx +398 -0
  12. frontend/components/popups/SettingsPopup.tsx +1 -1
  13. frontend/contexts/ClaudeContext.tsx +392 -0
  14. frontend/contexts/PodWebSocketContext.tsx +16 -21
  15. frontend/hooks/useInlineDiff.ts +269 -0
  16. frontend/lib/api.ts +323 -12
  17. frontend/lib/settings.ts +5 -0
  18. frontend/lib/websocket-native.ts +4 -8
  19. frontend/lib/websocket.ts +1 -2
  20. frontend/package-lock.json +733 -36
  21. frontend/package.json +2 -0
  22. frontend/public/assets/icons/providers/lambda_labs.svg +22 -0
  23. frontend/public/assets/icons/providers/prime_intellect.svg +18 -0
  24. frontend/public/assets/icons/providers/runpod.svg +9 -0
  25. frontend/public/assets/icons/providers/vastai.svg +1 -0
  26. frontend/settings.md +54 -0
  27. frontend/tsconfig.tsbuildinfo +1 -0
  28. frontend/types/claude.ts +194 -0
  29. kernel_run.py +13 -0
  30. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/METADATA +53 -11
  31. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/RECORD +56 -37
  32. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/WHEEL +1 -1
  33. morecompute/__init__.py +1 -1
  34. morecompute/__version__.py +1 -1
  35. morecompute/execution/executor.py +24 -67
  36. morecompute/execution/worker.py +6 -72
  37. morecompute/models/api_models.py +62 -0
  38. morecompute/notebook.py +11 -0
  39. morecompute/server.py +641 -133
  40. morecompute/services/claude_service.py +392 -0
  41. morecompute/services/pod_manager.py +168 -67
  42. morecompute/services/pod_monitor.py +67 -39
  43. morecompute/services/prime_intellect.py +0 -4
  44. morecompute/services/providers/__init__.py +92 -0
  45. morecompute/services/providers/base_provider.py +336 -0
  46. morecompute/services/providers/lambda_labs_provider.py +394 -0
  47. morecompute/services/providers/provider_factory.py +194 -0
  48. morecompute/services/providers/runpod_provider.py +504 -0
  49. morecompute/services/providers/vastai_provider.py +407 -0
  50. morecompute/utils/cell_magics.py +0 -3
  51. morecompute/utils/config_util.py +93 -3
  52. morecompute/utils/special_commands.py +5 -32
  53. morecompute/utils/version_check.py +117 -0
  54. frontend/styling_README.md +0 -23
  55. {more_compute-0.4.4.dist-info/licenses → more_compute-0.5.0.dist-info}/LICENSE +0 -0
  56. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/entry_points.txt +0 -0
  57. {more_compute-0.4.4.dist-info → more_compute-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,322 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { ProviderInfo, configureGpuProvider } from "../../lib/api";
5
+
6
+ interface ProviderConfigModalProps {
7
+ provider: ProviderInfo;
8
+ onClose: () => void;
9
+ onConfigured: (provider: ProviderInfo) => void;
10
+ }
11
+
12
+ export default function ProviderConfigModal({
13
+ provider,
14
+ onClose,
15
+ onConfigured,
16
+ }: ProviderConfigModalProps) {
17
+ const [apiKey, setApiKey] = useState("");
18
+ const [makeActive, setMakeActive] = useState(true);
19
+ const [loading, setLoading] = useState(false);
20
+ const [error, setError] = useState<string | null>(null);
21
+
22
+ // Reset form when provider changes
23
+ useEffect(() => {
24
+ setApiKey("");
25
+ setError(null);
26
+ setMakeActive(true);
27
+ }, [provider.name]);
28
+
29
+ async function handleSubmit(e: React.FormEvent) {
30
+ e.preventDefault();
31
+
32
+ if (!apiKey.trim()) {
33
+ setError("API key is required");
34
+ return;
35
+ }
36
+
37
+ try {
38
+ setLoading(true);
39
+ setError(null);
40
+
41
+ await configureGpuProvider(provider.name, {
42
+ api_key: apiKey.trim(),
43
+ make_active: makeActive,
44
+ });
45
+
46
+ // Update provider status and notify parent
47
+ const updatedProvider: ProviderInfo = {
48
+ ...provider,
49
+ configured: true,
50
+ is_active: makeActive,
51
+ };
52
+ onConfigured(updatedProvider);
53
+ onClose();
54
+ } catch (err) {
55
+ setError(
56
+ err instanceof Error ? err.message : "Failed to configure provider"
57
+ );
58
+ } finally {
59
+ setLoading(false);
60
+ }
61
+ }
62
+
63
+ return (
64
+ <div className="modal-overlay" onClick={onClose}>
65
+ <div className="modal-content" onClick={(e) => e.stopPropagation()}>
66
+ <div className="modal-header">
67
+ <h3>Configure {provider.display_name}</h3>
68
+ <button className="modal-close" onClick={onClose}>
69
+ &times;
70
+ </button>
71
+ </div>
72
+
73
+ <form onSubmit={handleSubmit}>
74
+ <div className="modal-body">
75
+ <p className="modal-description">
76
+ Enter your {provider.display_name} API key to enable GPU access.
77
+ </p>
78
+
79
+ <div className="form-group">
80
+ <label htmlFor="apiKey">API Key</label>
81
+ <input
82
+ id="apiKey"
83
+ type="password"
84
+ value={apiKey}
85
+ onChange={(e) => setApiKey(e.target.value)}
86
+ placeholder={`Enter your ${provider.display_name} API key`}
87
+ disabled={loading}
88
+ autoFocus
89
+ autoComplete="off"
90
+ data-lpignore="true"
91
+ data-1p-ignore="true"
92
+ />
93
+ </div>
94
+
95
+ <div className="form-group checkbox">
96
+ <label>
97
+ <input
98
+ type="checkbox"
99
+ checked={makeActive}
100
+ onChange={(e) => setMakeActive(e.target.checked)}
101
+ disabled={loading}
102
+ />
103
+ Set as active provider
104
+ </label>
105
+ </div>
106
+
107
+ {error && <div className="error-message">{error}</div>}
108
+
109
+ <div className="help-text">
110
+ <a
111
+ href={provider.dashboard_url}
112
+ target="_blank"
113
+ rel="noopener noreferrer"
114
+ >
115
+ Get your API key from {provider.display_name}
116
+ </a>
117
+ </div>
118
+ </div>
119
+
120
+ <div className="modal-footer">
121
+ <button
122
+ type="button"
123
+ className="btn-secondary"
124
+ onClick={onClose}
125
+ disabled={loading}
126
+ >
127
+ Cancel
128
+ </button>
129
+ <button type="submit" className="btn-primary" disabled={loading}>
130
+ {loading ? "Saving..." : "Save & Connect"}
131
+ </button>
132
+ </div>
133
+ </form>
134
+
135
+ <style jsx>{`
136
+ .modal-overlay {
137
+ position: fixed;
138
+ top: 0;
139
+ left: 0;
140
+ right: 0;
141
+ bottom: 0;
142
+ background: rgba(0, 0, 0, 0.7);
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ z-index: 1000;
147
+ }
148
+
149
+ .modal-content {
150
+ background: var(--mc-cell-background);
151
+ border: 1px solid var(--mc-border);
152
+ border-radius: 12px;
153
+ width: 100%;
154
+ max-width: 420px;
155
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
156
+ }
157
+
158
+ .modal-header {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: space-between;
162
+ padding: 16px 20px;
163
+ border-bottom: 1px solid var(--mc-border);
164
+ }
165
+
166
+ .modal-header h3 {
167
+ margin: 0;
168
+ font-size: 16px;
169
+ font-weight: 600;
170
+ color: var(--mc-text-color);
171
+ }
172
+
173
+ .modal-close {
174
+ background: none;
175
+ border: none;
176
+ color: var(--mc-text-color);
177
+ opacity: 0.6;
178
+ font-size: 24px;
179
+ cursor: pointer;
180
+ padding: 0;
181
+ line-height: 1;
182
+ }
183
+
184
+ .modal-close:hover {
185
+ opacity: 1;
186
+ }
187
+
188
+ .modal-body {
189
+ padding: 20px;
190
+ }
191
+
192
+ .modal-description {
193
+ margin: 0 0 16px;
194
+ color: var(--mc-text-color);
195
+ opacity: 0.7;
196
+ font-size: 13px;
197
+ line-height: 1.5;
198
+ }
199
+
200
+ .warning-text {
201
+ color: #f59e0b;
202
+ }
203
+
204
+ .form-group {
205
+ margin-bottom: 16px;
206
+ }
207
+
208
+ .form-group label {
209
+ display: block;
210
+ margin-bottom: 6px;
211
+ color: var(--mc-text-color);
212
+ font-size: 13px;
213
+ font-weight: 500;
214
+ }
215
+
216
+ .form-group input[type="password"],
217
+ .form-group input[type="text"] {
218
+ width: 100%;
219
+ padding: 10px 12px;
220
+ background: var(--mc-background);
221
+ border: 1px solid var(--mc-border);
222
+ border-radius: 6px;
223
+ color: var(--mc-text-color);
224
+ font-size: 14px;
225
+ transition: border-color 0.2s;
226
+ }
227
+
228
+ .form-group input:focus {
229
+ outline: none;
230
+ border-color: var(--mc-primary);
231
+ }
232
+
233
+ .form-group input:disabled {
234
+ opacity: 0.5;
235
+ }
236
+
237
+ .form-group.checkbox label {
238
+ display: flex;
239
+ align-items: center;
240
+ gap: 8px;
241
+ cursor: pointer;
242
+ }
243
+
244
+ .form-group.checkbox input[type="checkbox"] {
245
+ width: 16px;
246
+ height: 16px;
247
+ cursor: pointer;
248
+ }
249
+
250
+ .error-message {
251
+ padding: 10px 12px;
252
+ background: rgba(239, 68, 68, 0.1);
253
+ border: 1px solid rgba(239, 68, 68, 0.3);
254
+ border-radius: 6px;
255
+ color: #ef4444;
256
+ font-size: 13px;
257
+ margin-bottom: 16px;
258
+ }
259
+
260
+ .help-text {
261
+ font-size: 12px;
262
+ }
263
+
264
+ .help-text a {
265
+ color: var(--mc-primary);
266
+ text-decoration: none;
267
+ }
268
+
269
+ .help-text a:hover {
270
+ text-decoration: underline;
271
+ }
272
+
273
+ .modal-footer {
274
+ display: flex;
275
+ justify-content: flex-end;
276
+ gap: 8px;
277
+ padding: 16px 20px;
278
+ border-top: 1px solid var(--mc-border);
279
+ }
280
+
281
+ .btn-secondary,
282
+ .btn-primary {
283
+ padding: 8px 16px;
284
+ border-radius: 6px;
285
+ font-size: 13px;
286
+ font-weight: 500;
287
+ cursor: pointer;
288
+ transition: all 0.2s;
289
+ }
290
+
291
+ .btn-secondary {
292
+ background: transparent;
293
+ border: 1px solid var(--mc-border);
294
+ color: var(--mc-text-color);
295
+ opacity: 0.8;
296
+ }
297
+
298
+ .btn-secondary:hover:not(:disabled) {
299
+ background: var(--mc-secondary);
300
+ opacity: 1;
301
+ }
302
+
303
+ .btn-primary {
304
+ background: var(--mc-primary);
305
+ border: none;
306
+ color: white;
307
+ }
308
+
309
+ .btn-primary:hover:not(:disabled) {
310
+ background: var(--mc-primary-hover);
311
+ }
312
+
313
+ .btn-secondary:disabled,
314
+ .btn-primary:disabled {
315
+ opacity: 0.5;
316
+ cursor: not-allowed;
317
+ }
318
+ `}</style>
319
+ </div>
320
+ </div>
321
+ );
322
+ }
@@ -0,0 +1,398 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect } from "react";
4
+ import Image from "next/image";
5
+ import {
6
+ ProviderInfo,
7
+ fetchGpuProviders,
8
+ setActiveGpuProvider,
9
+ } from "../../lib/api";
10
+
11
+ // Provider logos stored in public/assets/icons/providers/
12
+ // Only SSH-based providers are supported
13
+ const PROVIDER_LOGOS: Record<string, string> = {
14
+ runpod: "/assets/icons/providers/runpod.svg",
15
+ lambda_labs: "/assets/icons/providers/lambda_labs.svg",
16
+ vastai: "/assets/icons/providers/vastai.svg",
17
+ };
18
+
19
+ const DEFAULT_LOGO = "/assets/icons/providers/runpod.svg";
20
+
21
+ interface ProviderDropdownProps {
22
+ onProviderChange: (provider: ProviderInfo) => void;
23
+ onConfigureProvider: (provider: ProviderInfo) => void;
24
+ selectedProvider: ProviderInfo | null;
25
+ disabled?: boolean;
26
+ }
27
+
28
+ export default function ProviderDropdown({
29
+ onProviderChange,
30
+ onConfigureProvider,
31
+ selectedProvider,
32
+ disabled = false,
33
+ }: ProviderDropdownProps) {
34
+ const [providers, setProviders] = useState<ProviderInfo[]>([]);
35
+ const [isOpen, setIsOpen] = useState(false);
36
+ const [loading, setLoading] = useState(true);
37
+ const [error, setError] = useState<string | null>(null);
38
+ const dropdownRef = useRef<HTMLDivElement>(null);
39
+
40
+ // Load providers on mount
41
+ useEffect(() => {
42
+ loadProviders();
43
+ }, []);
44
+
45
+ // Close dropdown when clicking outside
46
+ useEffect(() => {
47
+ function handleClickOutside(event: MouseEvent) {
48
+ if (
49
+ dropdownRef.current &&
50
+ !dropdownRef.current.contains(event.target as Node)
51
+ ) {
52
+ setIsOpen(false);
53
+ }
54
+ }
55
+
56
+ document.addEventListener("mousedown", handleClickOutside);
57
+ return () => document.removeEventListener("mousedown", handleClickOutside);
58
+ }, []);
59
+
60
+ async function loadProviders() {
61
+ try {
62
+ setLoading(true);
63
+ setError(null);
64
+ const response = await fetchGpuProviders();
65
+ setProviders(response.providers);
66
+
67
+ // If no provider is selected, try to restore from backend or localStorage
68
+ if (!selectedProvider) {
69
+ // First try backend active provider
70
+ let providerToSelect = response.active_provider;
71
+
72
+ // Fallback to localStorage if no backend active provider
73
+ if (!providerToSelect) {
74
+ const savedProvider = localStorage.getItem(
75
+ "morecompute_active_provider",
76
+ );
77
+ if (savedProvider) {
78
+ providerToSelect = savedProvider;
79
+ }
80
+ }
81
+
82
+ if (providerToSelect) {
83
+ const active = response.providers.find(
84
+ (p) => p.name === providerToSelect,
85
+ );
86
+ // Only auto-select if the provider is configured
87
+ if (active && active.configured) {
88
+ onProviderChange(active);
89
+ } else if (active && !active.configured) {
90
+ // Provider saved but not configured - clear localStorage and don't auto-select
91
+ localStorage.removeItem("morecompute_active_provider");
92
+ // Still set the provider so the UI shows it needs configuration
93
+ onProviderChange(active);
94
+ }
95
+ }
96
+ }
97
+ } catch (err) {
98
+ setError(err instanceof Error ? err.message : "Failed to load providers");
99
+ } finally {
100
+ setLoading(false);
101
+ }
102
+ }
103
+
104
+ async function handleSelectProvider(provider: ProviderInfo) {
105
+ if (!provider.configured) {
106
+ // Open configuration modal for unconfigured providers
107
+ onConfigureProvider(provider);
108
+ setIsOpen(false);
109
+ return;
110
+ }
111
+
112
+ try {
113
+ // Set as active provider
114
+ await setActiveGpuProvider(provider.name);
115
+
116
+ // Save to localStorage for persistence
117
+ localStorage.setItem("morecompute_active_provider", provider.name);
118
+
119
+ onProviderChange(provider);
120
+ setIsOpen(false);
121
+
122
+ // Refresh provider list to update active status
123
+ await loadProviders();
124
+ } catch (err) {
125
+ setError(
126
+ err instanceof Error ? err.message : "Failed to switch provider",
127
+ );
128
+ }
129
+ }
130
+
131
+ function getProviderLogo(providerName: string): string {
132
+ return PROVIDER_LOGOS[providerName] || DEFAULT_LOGO;
133
+ }
134
+
135
+ return (
136
+ <div className="provider-dropdown" ref={dropdownRef}>
137
+ <button
138
+ className="provider-dropdown-button"
139
+ onClick={() => !disabled && setIsOpen(!isOpen)}
140
+ disabled={disabled || loading}
141
+ >
142
+ {loading ? (
143
+ <span className="provider-loading">Loading...</span>
144
+ ) : selectedProvider ? (
145
+ <Image
146
+ src={getProviderLogo(selectedProvider.name)}
147
+ alt={selectedProvider.display_name}
148
+ width={120}
149
+ height={28}
150
+ className="provider-logo"
151
+ style={{ objectFit: "contain" }}
152
+ />
153
+ ) : (
154
+ <span className="provider-placeholder">Select Provider</span>
155
+ )}
156
+ </button>
157
+
158
+ {isOpen && !loading && (
159
+ <div className="provider-dropdown-menu">
160
+ {error && <div className="provider-error">{error}</div>}
161
+
162
+ {providers.map((provider) => (
163
+ <div key={provider.name} className="provider-option-row">
164
+ <button
165
+ className={`provider-option ${
166
+ provider.is_active ? "active" : ""
167
+ } ${!provider.configured ? "unconfigured" : ""}`}
168
+ onClick={() => handleSelectProvider(provider)}
169
+ >
170
+ <span className="provider-logo-container">
171
+ <Image
172
+ src={getProviderLogo(provider.name)}
173
+ alt={provider.display_name}
174
+ width={90}
175
+ height={24}
176
+ className="provider-logo"
177
+ style={{ objectFit: "contain" }}
178
+ />
179
+ </span>
180
+ </button>
181
+ <button
182
+ className="provider-config-btn"
183
+ onClick={(e) => {
184
+ e.stopPropagation();
185
+ onConfigureProvider(provider);
186
+ setIsOpen(false);
187
+ }}
188
+ title={
189
+ provider.configured
190
+ ? "Reconfigure API Key"
191
+ : "Configure API Key"
192
+ }
193
+ >
194
+ <svg
195
+ width="14"
196
+ height="14"
197
+ viewBox="0 0 24 24"
198
+ fill="none"
199
+ stroke="currentColor"
200
+ strokeWidth="2"
201
+ >
202
+ <circle cx="12" cy="12" r="3" />
203
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
204
+ </svg>
205
+ </button>
206
+ </div>
207
+ ))}
208
+
209
+ <div className="provider-dropdown-footer">
210
+ <a
211
+ href={selectedProvider?.dashboard_url || "#"}
212
+ target="_blank"
213
+ rel="noopener noreferrer"
214
+ className="provider-link"
215
+ >
216
+ Get API Keys
217
+ </a>
218
+ </div>
219
+ </div>
220
+ )}
221
+
222
+ <style jsx>{`
223
+ .provider-dropdown {
224
+ position: relative;
225
+ width: 100%;
226
+ }
227
+
228
+ .provider-dropdown-button {
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ width: 100%;
233
+ padding: 12px 16px;
234
+ border: 1px solid #2a2a4e;
235
+ border-radius: 8px;
236
+ background: #1a1a2e;
237
+ color: var(--mc-text-color);
238
+ font-size: 14px;
239
+ cursor: pointer;
240
+ transition: all 0.2s;
241
+ min-height: 52px;
242
+ }
243
+
244
+ .provider-dropdown-button:hover:not(:disabled) {
245
+ border-color: var(--mc-primary);
246
+ background: #252545;
247
+ }
248
+
249
+ .provider-dropdown-button:disabled {
250
+ opacity: 0.5;
251
+ cursor: not-allowed;
252
+ }
253
+
254
+ .provider-logo-container {
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ height: 32px;
259
+ min-width: 110px;
260
+ background: #1a1a2e;
261
+ padding: 6px 12px;
262
+ border-radius: 6px;
263
+ }
264
+
265
+ .provider-placeholder {
266
+ opacity: 0.6;
267
+ }
268
+
269
+ .provider-loading {
270
+ opacity: 0.6;
271
+ }
272
+
273
+ .provider-dropdown-menu {
274
+ position: absolute;
275
+ top: calc(100% + 4px);
276
+ left: 0;
277
+ right: 0;
278
+ background: #1a1a2e;
279
+ border: 1px solid #2a2a4e;
280
+ border-radius: 8px;
281
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
282
+ z-index: 100;
283
+ max-height: 300px;
284
+ overflow-y: auto;
285
+ }
286
+
287
+ .provider-error {
288
+ padding: 8px 12px;
289
+ color: #ef4444;
290
+ font-size: 12px;
291
+ }
292
+
293
+ .provider-option {
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: center;
297
+ gap: 12px;
298
+ flex: 1;
299
+ padding: 16px 12px;
300
+ border: none;
301
+ background: transparent;
302
+ color: #ffffff;
303
+ font-size: 14px;
304
+ cursor: pointer;
305
+ transition: background 0.15s;
306
+ text-align: center;
307
+ min-height: 56px;
308
+ }
309
+
310
+ .provider-option:hover {
311
+ background: #2a2a4e;
312
+ }
313
+
314
+ .provider-option.active {
315
+ background: rgba(99, 102, 241, 0.1);
316
+ }
317
+
318
+ .provider-option-row.active-row {
319
+ border-left: 3px solid var(--mc-primary);
320
+ }
321
+
322
+ .provider-option.unconfigured {
323
+ opacity: 0.8;
324
+ }
325
+
326
+ .provider-option-row {
327
+ display: flex;
328
+ align-items: stretch;
329
+ border-bottom: 1px solid #2a2a4e;
330
+ }
331
+
332
+ .provider-option-row:last-of-type {
333
+ border-bottom: none;
334
+ }
335
+
336
+ .provider-config-btn {
337
+ align-self: stretch;
338
+ min-width: 54px;
339
+ padding: 0 14px;
340
+ background: transparent;
341
+ border: none;
342
+ color: #6b7280;
343
+ cursor: pointer;
344
+ transition: all 0.15s;
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ line-height: 0;
349
+ }
350
+
351
+ .provider-config-btn:hover {
352
+ color: #ffffff;
353
+ background: rgba(255, 255, 255, 0.1);
354
+ }
355
+
356
+ .provider-status-dot {
357
+ width: 8px;
358
+ height: 8px;
359
+ border-radius: 50%;
360
+ display: inline-block;
361
+ flex-shrink: 0;
362
+ }
363
+
364
+ .provider-status-active {
365
+ background: #10b981;
366
+ }
367
+
368
+ .provider-status-setup {
369
+ background: #fbbf24;
370
+ }
371
+
372
+ .provider-dropdown-footer {
373
+ padding: 8px 12px;
374
+ border-top: 1px solid #2a2a4e;
375
+ }
376
+
377
+ .provider-link {
378
+ color: #60a5fa;
379
+ font-size: 12px;
380
+ text-decoration: none;
381
+ }
382
+
383
+ .provider-link:hover {
384
+ text-decoration: underline;
385
+ color: #93c5fd;
386
+ }
387
+ `}</style>
388
+
389
+ <style jsx global>{`
390
+ .provider-logo {
391
+ flex-shrink: 0;
392
+ max-width: 100%;
393
+ height: auto;
394
+ }
395
+ `}</style>
396
+ </div>
397
+ );
398
+ }