neural-loom 0.2.47 → 0.2.49

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 (126) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +4 -0
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/cache/.previewinfo +1 -1
  5. package/.next/cache/.rscinfo +1 -1
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/diagnostics/route-bundle-stats.json +1 -1
  8. package/.next/fallback-build-manifest.json +3 -3
  9. package/.next/prerender-manifest.json +3 -3
  10. package/.next/routes-manifest.json +24 -0
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
  27. package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
  28. package/.next/server/app/api/context/route.js.nft.json +1 -1
  29. package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
  30. package/.next/server/app/api/recents/route.js.nft.json +1 -1
  31. package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
  32. package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
  33. package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
  34. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  35. package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
  36. package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
  37. package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
  38. package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
  39. package/.next/server/app/api/sql/connections/route/app-paths-manifest.json +3 -0
  40. package/.next/server/app/api/sql/connections/route/build-manifest.json +9 -0
  41. package/.next/server/app/api/sql/connections/route/server-reference-manifest.json +4 -0
  42. package/.next/server/app/api/sql/connections/route.js +6 -0
  43. package/.next/server/app/api/sql/connections/route.js.map +5 -0
  44. package/.next/server/app/api/sql/connections/route.js.nft.json +1 -0
  45. package/.next/server/app/api/sql/connections/route_client-reference-manifest.js +3 -0
  46. package/.next/server/app/api/sql/mcp/route/app-paths-manifest.json +3 -0
  47. package/.next/server/app/api/sql/mcp/route/build-manifest.json +9 -0
  48. package/.next/server/app/api/sql/mcp/route/server-reference-manifest.json +4 -0
  49. package/.next/server/app/api/sql/mcp/route.js +6 -0
  50. package/.next/server/app/api/sql/mcp/route.js.map +5 -0
  51. package/.next/server/app/api/sql/mcp/route.js.nft.json +1 -0
  52. package/.next/server/app/api/sql/mcp/route_client-reference-manifest.js +3 -0
  53. package/.next/server/app/api/sql/query/route/app-paths-manifest.json +3 -0
  54. package/.next/server/app/api/sql/query/route/build-manifest.json +9 -0
  55. package/.next/server/app/api/sql/query/route/server-reference-manifest.json +4 -0
  56. package/.next/server/app/api/sql/query/route.js +6 -0
  57. package/.next/server/app/api/sql/query/route.js.map +5 -0
  58. package/.next/server/app/api/sql/query/route.js.nft.json +1 -0
  59. package/.next/server/app/api/sql/query/route_client-reference-manifest.js +3 -0
  60. package/.next/server/app/api/sql/schema/route/app-paths-manifest.json +3 -0
  61. package/.next/server/app/api/sql/schema/route/build-manifest.json +9 -0
  62. package/.next/server/app/api/sql/schema/route/server-reference-manifest.json +4 -0
  63. package/.next/server/app/api/sql/schema/route.js +6 -0
  64. package/.next/server/app/api/sql/schema/route.js.map +5 -0
  65. package/.next/server/app/api/sql/schema/route.js.nft.json +1 -0
  66. package/.next/server/app/api/sql/schema/route_client-reference-manifest.js +3 -0
  67. package/.next/server/app/index.html +1 -1
  68. package/.next/server/app/index.rsc +3 -3
  69. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  70. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  71. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  72. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  73. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/page/react-loadable-manifest.json +1 -1
  75. package/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/.next/server/app-paths-manifest.json +4 -0
  77. package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js +86 -0
  78. package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js.map +1 -0
  79. package/.next/server/chunks/[root-of-the-server]__00l5btc._.js +74 -0
  80. package/.next/server/chunks/[root-of-the-server]__00l5btc._.js.map +1 -0
  81. package/.next/server/chunks/[root-of-the-server]__06men8g._.js +74 -0
  82. package/.next/server/chunks/[root-of-the-server]__06men8g._.js.map +1 -0
  83. package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +1 -1
  84. package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js.map +1 -1
  85. package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js +74 -0
  86. package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js.map +1 -0
  87. package/.next/server/chunks/_next-internal_server_app_api_sql_connections_route_actions_11cib8~.js +3 -0
  88. package/.next/server/chunks/_next-internal_server_app_api_sql_connections_route_actions_11cib8~.js.map +1 -0
  89. package/.next/server/chunks/_next-internal_server_app_api_sql_mcp_route_actions_0wihfps.js +3 -0
  90. package/.next/server/chunks/_next-internal_server_app_api_sql_mcp_route_actions_0wihfps.js.map +1 -0
  91. package/.next/server/chunks/_next-internal_server_app_api_sql_query_route_actions_11x3q3c.js +3 -0
  92. package/.next/server/chunks/_next-internal_server_app_api_sql_query_route_actions_11x3q3c.js.map +1 -0
  93. package/.next/server/chunks/_next-internal_server_app_api_sql_schema_route_actions_015okwb.js +3 -0
  94. package/.next/server/chunks/_next-internal_server_app_api_sql_schema_route_actions_015okwb.js.map +1 -0
  95. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js +1 -1
  96. package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js.map +1 -1
  97. package/.next/server/middleware-build-manifest.js +3 -3
  98. package/.next/server/pages/404.html +1 -1
  99. package/.next/server/pages/500.html +1 -1
  100. package/.next/server/server-reference-manifest.js +1 -1
  101. package/.next/server/server-reference-manifest.json +1 -1
  102. package/.next/static/chunks/03f4bqk_d65hs.js +6 -0
  103. package/.next/static/chunks/{15zf~gqr_i8s1.js → 0~6gm~u8rgtpd.js} +1 -1
  104. package/.next/trace +1 -1
  105. package/.next/trace-build +1 -1
  106. package/.next/types/routes.d.ts +5 -1
  107. package/.next/types/validator.ts +36 -0
  108. package/bin/cli.js +76 -0
  109. package/package.json +2 -2
  110. package/src/app/api/sql/connections/route.ts +60 -0
  111. package/src/app/api/sql/mcp/route.ts +27 -0
  112. package/src/app/api/sql/query/route.ts +56 -0
  113. package/src/app/api/sql/schema/route.ts +42 -0
  114. package/src/app/components/DbExplorer.tsx +1279 -0
  115. package/src/app/components/IdeLayout.tsx +27 -1
  116. package/src/app/layout.tsx +1 -0
  117. package/src/lib/sql/ConnectionManager.ts +207 -0
  118. package/src/lib/sql/DbAdapter.ts +62 -0
  119. package/src/lib/sql/McpServer.ts +278 -0
  120. package/src/lib/sql/adapters/MysqlAdapter.ts +120 -0
  121. package/src/lib/sql/adapters/PostgresAdapter.ts +139 -0
  122. package/src/lib/sql/adapters/SqliteAdapter.ts +111 -0
  123. package/.next/static/chunks/0bclk7hf35ac4.js +0 -1
  124. /package/.next/static/{kPeXDg1G9iPLf0aCjKTXh → BoncS4SNz6CDpSdR8oY-K}/_buildManifest.js +0 -0
  125. /package/.next/static/{kPeXDg1G9iPLf0aCjKTXh → BoncS4SNz6CDpSdR8oY-K}/_clientMiddlewareManifest.js +0 -0
  126. /package/.next/static/{kPeXDg1G9iPLf0aCjKTXh → BoncS4SNz6CDpSdR8oY-K}/_ssgManifest.js +0 -0
@@ -0,0 +1,1279 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+
5
+ interface DbConnectionProfile {
6
+ id: string;
7
+ name: string;
8
+ type: "sqlite" | "postgres" | "mysql";
9
+ filepath?: string; // sqlite
10
+ host?: string; // pg, mysql
11
+ port?: number; // pg, mysql
12
+ database?: string; // pg, mysql
13
+ username?: string; // pg, mysql
14
+ password?: string; // pg, mysql
15
+ ssl?: boolean; // pg
16
+ readOnly?: boolean;
17
+ }
18
+
19
+ interface TableSchema {
20
+ name: string;
21
+ type: string;
22
+ }
23
+
24
+ interface ColumnSchema {
25
+ name: string;
26
+ type: string;
27
+ nullable: boolean;
28
+ defaultValue?: string | null;
29
+ isPrimaryKey: boolean;
30
+ isForeignKey: boolean;
31
+ referencedTable?: string | null;
32
+ referencedColumn?: string | null;
33
+ }
34
+
35
+ interface DbExplorerProps {
36
+ workspaceRoot: string;
37
+ }
38
+
39
+ export default function DbExplorer({ workspaceRoot }: DbExplorerProps) {
40
+ // Connection state
41
+ const [profiles, setProfiles] = useState<{ global: DbConnectionProfile[]; workspace: DbConnectionProfile[] }>({
42
+ global: [],
43
+ workspace: [],
44
+ });
45
+ const [activeId, setActiveId] = useState<string | null>(null);
46
+ const [isActiveGlobal, setIsActiveGlobal] = useState<boolean>(true);
47
+
48
+ // Schema state
49
+ const [tables, setTables] = useState<TableSchema[]>([]);
50
+ const [expandedTable, setExpandedTable] = useState<string | null>(null);
51
+ const [tableColumns, setTableColumns] = useState<{ [tableName: string]: ColumnSchema[] }>({});
52
+ const [schemaLoading, setSchemaLoading] = useState(false);
53
+
54
+ // Query state
55
+ const [sql, setSql] = useState<string>("SELECT * FROM sqlite_master;");
56
+ const [queryLoading, setQueryLoading] = useState(false);
57
+ const [queryError, setQueryError] = useState<string | null>(null);
58
+ const [queryResult, setQueryResult] = useState<{
59
+ rows: any[];
60
+ fields: string[];
61
+ durationMs: number;
62
+ affectedRows?: number;
63
+ } | null>(null);
64
+
65
+ // Connection management modal state
66
+ const [showAddModal, setShowAddModal] = useState(false);
67
+ const [isNewGlobal, setIsNewGlobal] = useState(false);
68
+ const [newProfile, setNewProfile] = useState<Partial<DbConnectionProfile>>({
69
+ id: "",
70
+ name: "",
71
+ type: "sqlite",
72
+ filepath: "./dev.db",
73
+ host: "localhost",
74
+ port: 5432,
75
+ database: "postgres",
76
+ username: "postgres",
77
+ password: "",
78
+ ssl: false,
79
+ readOnly: true,
80
+ });
81
+ const [profileSaving, setProfileSaving] = useState(false);
82
+ const [testStatus, setTestStatus] = useState<{ success?: boolean; message?: string } | null>(null);
83
+
84
+ // Fetch connection profiles
85
+ const fetchProfiles = async () => {
86
+ try {
87
+ const res = await fetch(`/api/sql/connections?root=${encodeURIComponent(workspaceRoot)}`);
88
+ if (res.ok) {
89
+ const data = await res.json();
90
+ if (data.success) {
91
+ setProfiles({
92
+ global: data.global || [],
93
+ workspace: data.workspace || [],
94
+ });
95
+ setActiveId(data.activeId);
96
+ setIsActiveGlobal(data.isActiveGlobal);
97
+ }
98
+ }
99
+ } catch (err) {
100
+ console.error("Failed to load connection profiles:", err);
101
+ }
102
+ };
103
+
104
+ // Fetch schema tables
105
+ const fetchSchema = async () => {
106
+ if (!activeId) {
107
+ setTables([]);
108
+ return;
109
+ }
110
+ setSchemaLoading(true);
111
+ try {
112
+ const res = await fetch(`/api/sql/schema?action=list_tables&root=${encodeURIComponent(workspaceRoot)}`);
113
+ if (res.ok) {
114
+ const data = await res.json();
115
+ if (data.success) {
116
+ setTables(data.tables || []);
117
+ setQueryError(null);
118
+ } else {
119
+ setQueryError(data.error || "Failed to load database schema.");
120
+ }
121
+ } else {
122
+ const data = await res.json();
123
+ setQueryError(data.error || "Failed to load database schema.");
124
+ }
125
+ } catch (err) {
126
+ console.error("Failed to load schema:", err);
127
+ } finally {
128
+ setSchemaLoading(false);
129
+ }
130
+ };
131
+
132
+ // Fetch table columns
133
+ const fetchTableColumns = async (tableName: string) => {
134
+ if (tableColumns[tableName]) {
135
+ setExpandedTable(expandedTable === tableName ? null : tableName);
136
+ return;
137
+ }
138
+ try {
139
+ const res = await fetch(
140
+ `/api/sql/schema?action=describe_table&table=${encodeURIComponent(tableName)}&root=${encodeURIComponent(
141
+ workspaceRoot
142
+ )}`
143
+ );
144
+ if (res.ok) {
145
+ const data = await res.json();
146
+ if (data.success) {
147
+ setTableColumns((prev) => ({ ...prev, [tableName]: data.columns }));
148
+ setExpandedTable(tableName);
149
+ }
150
+ }
151
+ } catch (err) {
152
+ console.error(`Failed to load columns for table ${tableName}:`, err);
153
+ }
154
+ };
155
+
156
+ useEffect(() => {
157
+ fetchProfiles();
158
+ }, [workspaceRoot]);
159
+
160
+ useEffect(() => {
161
+ fetchSchema();
162
+ setQueryResult(null);
163
+ setExpandedTable(null);
164
+ setTableColumns({});
165
+
166
+ // Set query template based on active database type
167
+ const activeProfile = isActiveGlobal
168
+ ? profiles.global.find((p) => p.id === activeId)
169
+ : profiles.workspace.find((p) => p.id === activeId);
170
+
171
+ if (activeProfile) {
172
+ if (activeProfile.type === "sqlite") {
173
+ setSql("SELECT name, type FROM sqlite_master WHERE type='table';");
174
+ } else {
175
+ setSql("SELECT * FROM information_schema.tables LIMIT 10;");
176
+ }
177
+ }
178
+ }, [activeId, isActiveGlobal, profiles.global.length, profiles.workspace.length]);
179
+
180
+ // Handle setting active profile
181
+ const handleSelectActive = async (id: string, isGlobal: boolean) => {
182
+ try {
183
+ const res = await fetch("/api/sql/connections", {
184
+ method: "POST",
185
+ headers: { "Content-Type": "application/json" },
186
+ body: JSON.stringify({
187
+ action: "set_active",
188
+ profileId: id,
189
+ isGlobal,
190
+ root: workspaceRoot,
191
+ }),
192
+ });
193
+ if (res.ok) {
194
+ setActiveId(id);
195
+ setIsActiveGlobal(isGlobal);
196
+ }
197
+ } catch (err) {
198
+ console.error("Failed to set active profile:", err);
199
+ }
200
+ };
201
+
202
+ // Test connection profile
203
+ const handleTestConnection = async () => {
204
+ setTestStatus(null);
205
+ try {
206
+ const res = await fetch("/api/sql/connections", {
207
+ method: "POST",
208
+ headers: { "Content-Type": "application/json" },
209
+ body: JSON.stringify({
210
+ action: "test",
211
+ profile: newProfile,
212
+ root: workspaceRoot,
213
+ }),
214
+ });
215
+ const data = await res.json();
216
+ if (data.success) {
217
+ setTestStatus({ success: true, message: "Connection test successful!" });
218
+ } else {
219
+ setTestStatus({ success: false, message: data.error || "Connection test failed." });
220
+ }
221
+ } catch (err: any) {
222
+ setTestStatus({ success: false, message: err.message || "Failed to make test query." });
223
+ }
224
+ };
225
+
226
+ // Save connection profile
227
+ const handleSaveConnection = async (e: React.FormEvent) => {
228
+ e.preventDefault();
229
+ if (!newProfile.name || !newProfile.type) return;
230
+
231
+ setProfileSaving(true);
232
+ const profileId = newProfile.id || `profile_${Math.random().toString(36).substring(7)}`;
233
+ const finalProfile: DbConnectionProfile = {
234
+ ...newProfile,
235
+ id: profileId,
236
+ name: newProfile.name,
237
+ type: newProfile.type as any,
238
+ } as DbConnectionProfile;
239
+
240
+ try {
241
+ const res = await fetch("/api/sql/connections", {
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" },
244
+ body: JSON.stringify({
245
+ action: "save",
246
+ profile: finalProfile,
247
+ isGlobal: isNewGlobal,
248
+ root: workspaceRoot,
249
+ }),
250
+ });
251
+ if (res.ok) {
252
+ await fetchProfiles();
253
+ setShowAddModal(false);
254
+ // Automatically make it active if it is the only one
255
+ if (!activeId) {
256
+ handleSelectActive(profileId, isNewGlobal);
257
+ }
258
+ }
259
+ } catch (err) {
260
+ console.error("Failed to save profile:", err);
261
+ } finally {
262
+ setProfileSaving(false);
263
+ }
264
+ };
265
+
266
+ // Delete connection profile
267
+ const handleDeleteConnection = async (id: string, isGlobal: boolean) => {
268
+ if (!confirm("Are you sure you want to delete this connection profile?")) return;
269
+ try {
270
+ const res = await fetch("/api/sql/connections", {
271
+ method: "POST",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify({
274
+ action: "delete",
275
+ profileId: id,
276
+ isGlobal,
277
+ root: workspaceRoot,
278
+ }),
279
+ });
280
+ if (res.ok) {
281
+ await fetchProfiles();
282
+ if (activeId === id) {
283
+ setActiveId(null);
284
+ }
285
+ }
286
+ } catch (err) {
287
+ console.error("Failed to delete connection profile:", err);
288
+ }
289
+ };
290
+
291
+ // Run SQL Query
292
+ const handleRunQuery = async () => {
293
+ if (!sql.trim() || !activeId) return;
294
+
295
+ setQueryLoading(true);
296
+ setQueryError(null);
297
+ setQueryResult(null);
298
+
299
+ try {
300
+ const res = await fetch("/api/sql/query", {
301
+ method: "POST",
302
+ headers: { "Content-Type": "application/json" },
303
+ body: JSON.stringify({
304
+ sql: sql.trim(),
305
+ root: workspaceRoot,
306
+ }),
307
+ });
308
+
309
+ const data = await res.json();
310
+ if (res.ok && data.success) {
311
+ setQueryResult({
312
+ rows: data.rows || [],
313
+ fields: data.fields || [],
314
+ durationMs: data.durationMs || 0,
315
+ affectedRows: data.affectedRows,
316
+ });
317
+ // Proactively refresh schema if a table creation or DDL was run
318
+ const isDDL =
319
+ sql.toLowerCase().includes("create table") ||
320
+ sql.toLowerCase().includes("drop table") ||
321
+ sql.toLowerCase().includes("alter table");
322
+ if (isDDL) {
323
+ fetchSchema();
324
+ }
325
+ } else {
326
+ setQueryError(data.error || "Failed to execute query.");
327
+ }
328
+ } catch (err: any) {
329
+ setQueryError(err.message || "An unexpected error occurred during execution.");
330
+ } finally {
331
+ setQueryLoading(false);
332
+ }
333
+ };
334
+
335
+ const activeProfile = isActiveGlobal
336
+ ? profiles.global.find((p) => p.id === activeId)
337
+ : profiles.workspace.find((p) => p.id === activeId);
338
+
339
+ return (
340
+ <div style={styles.container}>
341
+ {/* Sidebar: Connections & Schemas */}
342
+ <div style={styles.sidebar}>
343
+ {/* Connection Profiles section */}
344
+ <div style={styles.section}>
345
+ <div style={styles.sectionHeader}>
346
+ <span style={styles.sectionTitle}>Connections</span>
347
+ <button
348
+ style={styles.addBtn}
349
+ onClick={() => {
350
+ setTestStatus(null);
351
+ setNewProfile({
352
+ id: "",
353
+ name: "",
354
+ type: "sqlite",
355
+ filepath: "./dev.db",
356
+ host: "localhost",
357
+ port: 5432,
358
+ database: "postgres",
359
+ username: "postgres",
360
+ password: "",
361
+ ssl: false,
362
+ readOnly: true,
363
+ });
364
+ setIsNewGlobal(false);
365
+ setShowAddModal(true);
366
+ }}
367
+ title="Add Database Connection Profile"
368
+ >
369
+ ➕ Add
370
+ </button>
371
+ </div>
372
+
373
+ <div style={styles.profileList}>
374
+ {profiles.global.length === 0 && profiles.workspace.length === 0 && (
375
+ <div style={styles.emptyText}>No connections configured. Add one to start.</div>
376
+ )}
377
+
378
+ {profiles.workspace.length > 0 && (
379
+ <>
380
+ <div style={styles.groupHeader}>WORKSPACE CONNECTIONS</div>
381
+ {profiles.workspace.map((p) => (
382
+ <div
383
+ key={p.id}
384
+ style={{
385
+ ...styles.profileItem,
386
+ ...(activeId === p.id && !isActiveGlobal ? styles.profileItemActive : {}),
387
+ }}
388
+ onClick={() => handleSelectActive(p.id, false)}
389
+ >
390
+ <div style={styles.profileMeta}>
391
+ <span style={styles.profileName}>📁 {p.name}</span>
392
+ <span style={styles.profileType}>{p.type}</span>
393
+ </div>
394
+ <button
395
+ style={styles.deleteProfileBtn}
396
+ onClick={(e) => {
397
+ e.stopPropagation();
398
+ handleDeleteConnection(p.id, false);
399
+ }}
400
+ >
401
+
402
+ </button>
403
+ </div>
404
+ ))}
405
+ </>
406
+ )}
407
+
408
+ {profiles.global.length > 0 && (
409
+ <>
410
+ <div style={styles.groupHeader}>GLOBAL CONNECTIONS</div>
411
+ {profiles.global.map((p) => (
412
+ <div
413
+ key={p.id}
414
+ style={{
415
+ ...styles.profileItem,
416
+ ...(activeId === p.id && isActiveGlobal ? styles.profileItemActive : {}),
417
+ }}
418
+ onClick={() => handleSelectActive(p.id, true)}
419
+ >
420
+ <div style={styles.profileMeta}>
421
+ <span style={styles.profileName}>🌐 {p.name}</span>
422
+ <span style={styles.profileType}>{p.type}</span>
423
+ </div>
424
+ <button
425
+ style={styles.deleteProfileBtn}
426
+ onClick={(e) => {
427
+ e.stopPropagation();
428
+ handleDeleteConnection(p.id, true);
429
+ }}
430
+ >
431
+
432
+ </button>
433
+ </div>
434
+ ))}
435
+ </>
436
+ )}
437
+ </div>
438
+ </div>
439
+
440
+ {/* Database Tables tree view */}
441
+ <div style={{ ...styles.section, flex: 1, borderTop: "1px solid var(--border)" }}>
442
+ <div style={styles.sectionHeader}>
443
+ <span style={styles.sectionTitle}>Schema Tables</span>
444
+ {schemaLoading && <span style={styles.spinnerMini}></span>}
445
+ </div>
446
+
447
+ <div style={styles.tableList}>
448
+ {!activeId && <div style={styles.emptyText}>Select an active connection to view tables.</div>}
449
+ {activeId && tables.length === 0 && !schemaLoading && (
450
+ <div style={styles.emptyText}>No tables found in database schema.</div>
451
+ )}
452
+
453
+ {tables.map((t) => (
454
+ <div key={t.name} style={styles.tableTreeItem}>
455
+ <div style={styles.tableRow} onClick={() => fetchTableColumns(t.name)}>
456
+ <span style={styles.tableIcon}>{t.type === "view" ? "👁️" : "📊"}</span>
457
+ <span style={styles.tableName}>{t.name}</span>
458
+ <span style={styles.tableChevron}>{expandedTable === t.name ? "▼" : "▶"}</span>
459
+ </div>
460
+
461
+ {expandedTable === t.name && tableColumns[t.name] && (
462
+ <div style={styles.columnList}>
463
+ {tableColumns[t.name].map((col) => (
464
+ <div key={col.name} style={styles.columnItem}>
465
+ <span style={styles.columnIcon}>
466
+ {col.isPrimaryKey ? "🔑" : col.isForeignKey ? "🔗" : "▫"}
467
+ </span>
468
+ <span style={styles.columnName} title={col.type}>
469
+ {col.name}
470
+ </span>
471
+ <span style={styles.columnType}>
472
+ {col.type.toLowerCase().substring(0, 12)}
473
+ </span>
474
+ </div>
475
+ ))}
476
+ </div>
477
+ )}
478
+ </div>
479
+ ))}
480
+ </div>
481
+ </div>
482
+ </div>
483
+
484
+ {/* Main Panel: Query Editor & Data Results */}
485
+ <div style={styles.mainContent}>
486
+ {/* Connection status header */}
487
+ <div style={styles.statusBar}>
488
+ <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
489
+ <span style={styles.statusLabel}>Active Connection:</span>
490
+ {activeProfile ? (
491
+ <span style={styles.statusValue}>
492
+ {isActiveGlobal ? "🌐" : "📁"} {activeProfile.name} ({activeProfile.type})
493
+ {activeProfile.readOnly && <span style={styles.readOnlyBadge}>Read-Only</span>}
494
+ </span>
495
+ ) : (
496
+ <span style={{ ...styles.statusValue, color: "var(--danger)" }}>No Connection Selected</span>
497
+ )}
498
+ </div>
499
+
500
+ <button
501
+ style={{
502
+ ...styles.runBtn,
503
+ opacity: !activeId || queryLoading ? 0.6 : 1,
504
+ cursor: !activeId || queryLoading ? "not-allowed" : "pointer",
505
+ }}
506
+ onClick={handleRunQuery}
507
+ disabled={!activeId || queryLoading}
508
+ >
509
+ {queryLoading ? "⌛ Running..." : "⚡ Execute Query (F5)"}
510
+ </button>
511
+ </div>
512
+
513
+ {/* SQL Input Area */}
514
+ <div style={styles.editorArea}>
515
+ <textarea
516
+ style={styles.textarea}
517
+ value={sql}
518
+ onChange={(e) => setSql(e.target.value)}
519
+ placeholder="Write your SQL query script here..."
520
+ onKeyDown={(e) => {
521
+ if (e.key === "F5" || (e.ctrlKey && e.key === "Enter")) {
522
+ e.preventDefault();
523
+ handleRunQuery();
524
+ }
525
+ }}
526
+ />
527
+ </div>
528
+
529
+ {/* Results Viewer */}
530
+ <div style={styles.resultsArea}>
531
+ {queryError && (
532
+ <div style={styles.errorContainer}>
533
+ <h4 style={{ margin: "0 0 0.5rem 0", color: "var(--danger)" }}>⚠️ Execution Error</h4>
534
+ <pre style={styles.errorText}>{queryError}</pre>
535
+ {queryError.includes("not installed") && (
536
+ <div style={styles.driverPrompt}>
537
+ <p style={{ margin: "0 0 0.5rem 0" }}>To resolve this, run this installer command in your workspace terminal:</p>
538
+ <code style={styles.driverCode}>
539
+ {activeProfile?.type === "sqlite"
540
+ ? "npm install better-sqlite3"
541
+ : activeProfile?.type === "postgres"
542
+ ? "npm install pg"
543
+ : "npm install mysql2"}
544
+ </code>
545
+ </div>
546
+ )}
547
+ </div>
548
+ )}
549
+
550
+ {queryResult && (
551
+ <div style={styles.resultsWrapper}>
552
+ <div style={styles.resultsMeta}>
553
+ <span>
554
+ Query completed.{" "}
555
+ {queryResult.affectedRows !== undefined
556
+ ? `Affected rows: ${queryResult.affectedRows}`
557
+ : `Returned ${queryResult.rows.length} rows`}
558
+ </span>
559
+ <span>Duration: {queryResult.durationMs}ms</span>
560
+ </div>
561
+
562
+ {queryResult.rows.length > 0 ? (
563
+ <div style={styles.tableContainer}>
564
+ <table style={styles.table}>
565
+ <thead>
566
+ <tr>
567
+ {queryResult.fields.map((f) => (
568
+ <th key={f} style={styles.th}>
569
+ {f}
570
+ </th>
571
+ ))}
572
+ </tr>
573
+ </thead>
574
+ <tbody>
575
+ {queryResult.rows.map((row, idx) => (
576
+ <tr key={idx} style={idx % 2 === 0 ? styles.trEven : styles.trOdd}>
577
+ {queryResult.fields.map((f) => (
578
+ <td key={f} style={styles.td}>
579
+ {row[f] === null
580
+ ? "NULL"
581
+ : typeof row[f] === "object"
582
+ ? JSON.stringify(row[f])
583
+ : String(row[f])}
584
+ </td>
585
+ ))}
586
+ </tr>
587
+ ))}
588
+ </tbody>
589
+ </table>
590
+ </div>
591
+ ) : (
592
+ queryResult.affectedRows === undefined && (
593
+ <div style={styles.emptyResults}>Query executed successfully, but returned 0 results.</div>
594
+ )
595
+ )}
596
+ </div>
597
+ )}
598
+
599
+ {!queryError && !queryResult && !queryLoading && (
600
+ <div style={styles.emptyResults}>Write a query script and click Execute to view table records.</div>
601
+ )}
602
+
603
+ {queryLoading && (
604
+ <div style={styles.loadingWrapper}>
605
+ <span style={styles.spinnerLarge}></span>
606
+ <span style={{ color: "var(--muted)" }}>Running database statement...</span>
607
+ </div>
608
+ )}
609
+ </div>
610
+ </div>
611
+
612
+ {/* Add Connection Profile Modal */}
613
+ {showAddModal && (
614
+ <div style={styles.modalOverlay}>
615
+ <div style={styles.modalContent}>
616
+ <h3 style={{ margin: "0 0 1rem 0", display: "flex", justifyContent: "space-between" }}>
617
+ <span>🔌 Add Database Profile</span>
618
+ <span style={{ cursor: "pointer" }} onClick={() => setShowAddModal(false)}>
619
+
620
+ </span>
621
+ </h3>
622
+
623
+ <form onSubmit={handleSaveConnection}>
624
+ <div style={styles.formRow}>
625
+ <label style={styles.label}>Profile Name</label>
626
+ <input
627
+ style={styles.input}
628
+ type="text"
629
+ required
630
+ value={newProfile.name}
631
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, name: e.target.value }))}
632
+ placeholder="e.g. Local Postgres, Dev SQLite"
633
+ />
634
+ </div>
635
+
636
+ <div style={styles.formRow}>
637
+ <label style={styles.label}>Scope Level</label>
638
+ <div style={{ display: "flex", gap: "1rem" }}>
639
+ <label style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "0.25rem" }}>
640
+ <input
641
+ type="radio"
642
+ checked={!isNewGlobal}
643
+ onChange={() => setIsNewGlobal(false)}
644
+ />
645
+ Workspace Local
646
+ </label>
647
+ <label style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "0.25rem" }}>
648
+ <input
649
+ type="radio"
650
+ checked={isNewGlobal}
651
+ onChange={() => setIsNewGlobal(true)}
652
+ />
653
+ Global User Profile
654
+ </label>
655
+ </div>
656
+ </div>
657
+
658
+ <div style={styles.formRow}>
659
+ <label style={styles.label}>Database Type</label>
660
+ <select
661
+ style={styles.select}
662
+ value={newProfile.type}
663
+ onChange={(e) =>
664
+ setNewProfile((prev) => ({
665
+ ...prev,
666
+ type: e.target.value as any,
667
+ filepath: e.target.value === "sqlite" ? "./dev.db" : undefined,
668
+ }))
669
+ }
670
+ >
671
+ <option value="sqlite">SQLite (Local File)</option>
672
+ <option value="postgres">PostgreSQL</option>
673
+ <option value="mysql">MySQL</option>
674
+ </select>
675
+ </div>
676
+
677
+ {newProfile.type === "sqlite" ? (
678
+ <div style={styles.formRow}>
679
+ <label style={styles.label}>Database File Path</label>
680
+ <input
681
+ style={styles.input}
682
+ type="text"
683
+ required
684
+ value={newProfile.filepath || ""}
685
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, filepath: e.target.value }))}
686
+ placeholder="Relative (e.g. ./data/dev.db) or Absolute path"
687
+ />
688
+ </div>
689
+ ) : (
690
+ <>
691
+ <div style={{ display: "flex", gap: "1rem" }}>
692
+ <div style={{ ...styles.formRow, flex: 3 }}>
693
+ <label style={styles.label}>Host IP Address</label>
694
+ <input
695
+ style={styles.input}
696
+ type="text"
697
+ required
698
+ value={newProfile.host || ""}
699
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, host: e.target.value }))}
700
+ placeholder="localhost or IP"
701
+ />
702
+ </div>
703
+ <div style={{ ...styles.formRow, flex: 1 }}>
704
+ <label style={styles.label}>Port</label>
705
+ <input
706
+ style={styles.input}
707
+ type="number"
708
+ required
709
+ value={newProfile.port || 0}
710
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, port: parseInt(e.target.value) }))}
711
+ />
712
+ </div>
713
+ </div>
714
+
715
+ <div style={styles.formRow}>
716
+ <label style={styles.label}>Database Name</label>
717
+ <input
718
+ style={styles.input}
719
+ type="text"
720
+ required
721
+ value={newProfile.database || ""}
722
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, database: e.target.value }))}
723
+ />
724
+ </div>
725
+
726
+ <div style={{ display: "flex", gap: "1rem" }}>
727
+ <div style={{ ...styles.formRow, flex: 1 }}>
728
+ <label style={styles.label}>Username</label>
729
+ <input
730
+ style={styles.input}
731
+ type="text"
732
+ required
733
+ value={newProfile.username || ""}
734
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, username: e.target.value }))}
735
+ />
736
+ </div>
737
+ <div style={{ ...styles.formRow, flex: 1 }}>
738
+ <label style={styles.label}>Password</label>
739
+ <input
740
+ style={styles.input}
741
+ type="password"
742
+ value={newProfile.password || ""}
743
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, password: e.target.value }))}
744
+ />
745
+ </div>
746
+ </div>
747
+
748
+ {newProfile.type === "postgres" && (
749
+ <div style={styles.formRow}>
750
+ <label style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "0.25rem" }}>
751
+ <input
752
+ type="checkbox"
753
+ checked={!!newProfile.ssl}
754
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, ssl: e.target.checked }))}
755
+ />
756
+ Require SSL Secure Connection
757
+ </label>
758
+ </div>
759
+ )}
760
+ </>
761
+ )}
762
+
763
+ <div style={styles.formRow}>
764
+ <label style={{ cursor: "pointer", display: "flex", alignItems: "center", gap: "0.25rem" }}>
765
+ <input
766
+ type="checkbox"
767
+ checked={!!newProfile.readOnly}
768
+ onChange={(e) => setNewProfile((prev) => ({ ...prev, readOnly: e.target.checked }))}
769
+ />
770
+ Enforce Read-Only Connection (Disable mutations)
771
+ </label>
772
+ </div>
773
+
774
+ {testStatus && (
775
+ <div
776
+ style={{
777
+ ...styles.testResult,
778
+ backgroundColor: testStatus.success ? "rgba(16, 185, 129, 0.12)" : "rgba(239, 68, 68, 0.12)",
779
+ border: testStatus.success ? "1px solid var(--success)" : "1px solid var(--danger)",
780
+ color: testStatus.success ? "var(--success)" : "var(--danger)",
781
+ }}
782
+ >
783
+ {testStatus.message}
784
+ </div>
785
+ )}
786
+
787
+ <div style={styles.modalFooter}>
788
+ <button type="button" style={styles.testBtn} onClick={handleTestConnection}>
789
+ 🔍 Test Connection
790
+ </button>
791
+ <div style={{ display: "flex", gap: "0.5rem" }}>
792
+ <button type="button" style={styles.cancelBtn} onClick={() => setShowAddModal(false)}>
793
+ Cancel
794
+ </button>
795
+ <button type="submit" style={styles.saveBtn} disabled={profileSaving}>
796
+ {profileSaving ? "Saving..." : "Save Profile"}
797
+ </button>
798
+ </div>
799
+ </div>
800
+ </form>
801
+ </div>
802
+ </div>
803
+ )}
804
+ </div>
805
+ );
806
+ }
807
+
808
+ // Styling Object incorporating harmonized CSS dashboard rules
809
+ const styles: { [key: string]: React.CSSProperties } = {
810
+ container: {
811
+ display: "flex",
812
+ height: "100%",
813
+ width: "100%",
814
+ backgroundColor: "#05070a",
815
+ color: "#cbd5e1",
816
+ fontSize: "0.875rem",
817
+ minHeight: 0,
818
+ },
819
+ sidebar: {
820
+ width: "280px",
821
+ borderRight: "1px solid var(--border)",
822
+ display: "flex",
823
+ flexDirection: "column",
824
+ minHeight: 0,
825
+ backgroundColor: "rgba(10, 15, 24, 0.4)",
826
+ flexShrink: 0,
827
+ },
828
+ section: {
829
+ display: "flex",
830
+ flexDirection: "column",
831
+ padding: "1rem",
832
+ minHeight: 0,
833
+ },
834
+ sectionHeader: {
835
+ display: "flex",
836
+ justifyContent: "space-between",
837
+ alignItems: "center",
838
+ marginBottom: "0.75rem",
839
+ },
840
+ sectionTitle: {
841
+ fontWeight: 700,
842
+ letterSpacing: "0.05em",
843
+ textTransform: "uppercase",
844
+ fontSize: "0.75rem",
845
+ color: "var(--muted)",
846
+ },
847
+ addBtn: {
848
+ padding: "0.2rem 0.5rem",
849
+ borderRadius: "4px",
850
+ border: "1px solid var(--primary)",
851
+ backgroundColor: "transparent",
852
+ color: "var(--primary)",
853
+ fontSize: "0.7rem",
854
+ fontWeight: 600,
855
+ cursor: "pointer",
856
+ },
857
+ profileList: {
858
+ display: "flex",
859
+ flexDirection: "column",
860
+ gap: "0.4rem",
861
+ overflowY: "auto",
862
+ maxHeight: "220px",
863
+ },
864
+ groupHeader: {
865
+ fontSize: "0.65rem",
866
+ fontWeight: 600,
867
+ color: "var(--muted)",
868
+ marginTop: "0.5rem",
869
+ marginBottom: "0.2rem",
870
+ letterSpacing: "0.05em",
871
+ },
872
+ profileItem: {
873
+ display: "flex",
874
+ justifyContent: "space-between",
875
+ alignItems: "center",
876
+ padding: "0.5rem 0.6rem",
877
+ borderRadius: "6px",
878
+ backgroundColor: "rgba(255, 255, 255, 0.02)",
879
+ border: "1px solid var(--border)",
880
+ cursor: "pointer",
881
+ transition: "all 0.2s ease",
882
+ },
883
+ profileItemActive: {
884
+ backgroundColor: "rgba(109, 40, 217, 0.08)",
885
+ border: "1px solid var(--border-focus)",
886
+ boxShadow: "0 0 10px rgba(109, 40, 217, 0.1)",
887
+ },
888
+ profileMeta: {
889
+ display: "flex",
890
+ flexDirection: "column",
891
+ gap: "0.15rem",
892
+ flex: 1,
893
+ minWidth: 0,
894
+ },
895
+ profileName: {
896
+ fontWeight: 600,
897
+ fontSize: "0.8rem",
898
+ color: "#fff",
899
+ whiteSpace: "nowrap",
900
+ overflow: "hidden",
901
+ textOverflow: "ellipsis",
902
+ },
903
+ profileType: {
904
+ fontSize: "0.7rem",
905
+ color: "var(--muted)",
906
+ textTransform: "uppercase",
907
+ },
908
+ deleteProfileBtn: {
909
+ background: "none",
910
+ border: "none",
911
+ color: "rgba(239, 68, 68, 0.4)",
912
+ cursor: "pointer",
913
+ fontSize: "0.75rem",
914
+ padding: "0.25rem",
915
+ },
916
+ tableList: {
917
+ display: "flex",
918
+ flexDirection: "column",
919
+ gap: "0.25rem",
920
+ overflowY: "auto",
921
+ flex: 1,
922
+ minHeight: 0,
923
+ },
924
+ emptyText: {
925
+ fontSize: "0.75rem",
926
+ color: "var(--muted)",
927
+ textAlign: "center",
928
+ padding: "1rem 0",
929
+ },
930
+ tableTreeItem: {
931
+ display: "flex",
932
+ flexDirection: "column",
933
+ },
934
+ tableRow: {
935
+ display: "flex",
936
+ alignItems: "center",
937
+ padding: "0.35rem 0.5rem",
938
+ borderRadius: "4px",
939
+ cursor: "pointer",
940
+ userSelect: "none",
941
+ transition: "background 0.2s ease",
942
+ },
943
+ tableIcon: {
944
+ marginRight: "0.4rem",
945
+ fontSize: "0.9rem",
946
+ },
947
+ tableName: {
948
+ flex: 1,
949
+ fontSize: "0.8rem",
950
+ color: "#e2e8f0",
951
+ whiteSpace: "nowrap",
952
+ overflow: "hidden",
953
+ textOverflow: "ellipsis",
954
+ },
955
+ tableChevron: {
956
+ fontSize: "0.6rem",
957
+ color: "var(--muted)",
958
+ marginLeft: "0.25rem",
959
+ },
960
+ columnList: {
961
+ display: "flex",
962
+ flexDirection: "column",
963
+ gap: "0.2rem",
964
+ paddingLeft: "1.5rem",
965
+ marginTop: "0.15rem",
966
+ borderLeft: "1px dashed rgba(255,255,255,0.06)",
967
+ marginLeft: "0.8rem",
968
+ marginBottom: "0.25rem",
969
+ },
970
+ columnItem: {
971
+ display: "flex",
972
+ alignItems: "center",
973
+ padding: "0.15rem 0.25rem",
974
+ fontSize: "0.75rem",
975
+ },
976
+ columnIcon: {
977
+ marginRight: "0.3rem",
978
+ fontSize: "0.7rem",
979
+ },
980
+ columnName: {
981
+ flex: 1,
982
+ color: "var(--muted)",
983
+ overflow: "hidden",
984
+ textOverflow: "ellipsis",
985
+ },
986
+ columnType: {
987
+ fontSize: "0.65rem",
988
+ color: "rgba(109, 40, 217, 0.75)",
989
+ fontFamily: "monospace",
990
+ paddingLeft: "0.5rem",
991
+ },
992
+ mainContent: {
993
+ flex: 1,
994
+ display: "flex",
995
+ flexDirection: "column",
996
+ minHeight: 0,
997
+ backgroundColor: "#05070a",
998
+ },
999
+ statusBar: {
1000
+ display: "flex",
1001
+ justifyContent: "space-between",
1002
+ alignItems: "center",
1003
+ padding: "0.75rem 1rem",
1004
+ borderBottom: "1px solid var(--border)",
1005
+ backgroundColor: "rgba(15, 23, 42, 0.3)",
1006
+ flexShrink: 0,
1007
+ },
1008
+ statusLabel: {
1009
+ fontSize: "0.75rem",
1010
+ color: "var(--muted)",
1011
+ },
1012
+ statusValue: {
1013
+ fontSize: "0.8rem",
1014
+ fontWeight: 600,
1015
+ color: "#fff",
1016
+ display: "flex",
1017
+ alignItems: "center",
1018
+ gap: "0.4rem",
1019
+ },
1020
+ readOnlyBadge: {
1021
+ padding: "0.1rem 0.4rem",
1022
+ borderRadius: "4px",
1023
+ backgroundColor: "rgba(234, 179, 8, 0.12)",
1024
+ color: "#eab308",
1025
+ fontSize: "0.65rem",
1026
+ fontWeight: 700,
1027
+ border: "1px solid rgba(234, 179, 8, 0.2)",
1028
+ },
1029
+ runBtn: {
1030
+ padding: "0.4rem 0.9rem",
1031
+ borderRadius: "6px",
1032
+ border: "none",
1033
+ backgroundColor: "var(--primary)",
1034
+ color: "#fff",
1035
+ fontSize: "0.8rem",
1036
+ fontWeight: 600,
1037
+ transition: "all 0.2s ease",
1038
+ },
1039
+ editorArea: {
1040
+ height: "180px",
1041
+ borderBottom: "1px solid var(--border)",
1042
+ position: "relative",
1043
+ flexShrink: 0,
1044
+ },
1045
+ textarea: {
1046
+ width: "100%",
1047
+ height: "100%",
1048
+ backgroundColor: "#080c14",
1049
+ border: "none",
1050
+ resize: "none",
1051
+ color: "#e2e8f0",
1052
+ fontFamily: "monospace",
1053
+ fontSize: "0.85rem",
1054
+ padding: "1rem",
1055
+ outline: "none",
1056
+ lineHeight: "1.4",
1057
+ },
1058
+ resultsArea: {
1059
+ flex: 1,
1060
+ display: "flex",
1061
+ flexDirection: "column",
1062
+ minHeight: 0,
1063
+ padding: "1rem",
1064
+ },
1065
+ errorContainer: {
1066
+ padding: "1rem",
1067
+ borderRadius: "8px",
1068
+ backgroundColor: "rgba(239, 68, 68, 0.08)",
1069
+ border: "1px solid rgba(239, 68, 68, 0.2)",
1070
+ overflowY: "auto",
1071
+ },
1072
+ errorText: {
1073
+ margin: 0,
1074
+ fontFamily: "monospace",
1075
+ fontSize: "0.8rem",
1076
+ color: "#f87171",
1077
+ whiteSpace: "pre-wrap",
1078
+ },
1079
+ driverPrompt: {
1080
+ marginTop: "1rem",
1081
+ padding: "0.75rem",
1082
+ borderRadius: "6px",
1083
+ backgroundColor: "rgba(0, 0, 0, 0.3)",
1084
+ border: "1px solid var(--border)",
1085
+ },
1086
+ driverCode: {
1087
+ display: "block",
1088
+ padding: "0.5rem",
1089
+ backgroundColor: "#090d16",
1090
+ borderRadius: "4px",
1091
+ fontFamily: "monospace",
1092
+ fontSize: "0.8rem",
1093
+ color: "var(--primary)",
1094
+ border: "1px solid var(--border)",
1095
+ },
1096
+ resultsWrapper: {
1097
+ display: "flex",
1098
+ flexDirection: "column",
1099
+ height: "100%",
1100
+ minHeight: 0,
1101
+ },
1102
+ resultsMeta: {
1103
+ display: "flex",
1104
+ justifyContent: "space-between",
1105
+ fontSize: "0.75rem",
1106
+ color: "var(--muted)",
1107
+ marginBottom: "0.5rem",
1108
+ },
1109
+ tableContainer: {
1110
+ flex: 1,
1111
+ overflow: "auto",
1112
+ border: "1px solid var(--border)",
1113
+ borderRadius: "6px",
1114
+ },
1115
+ table: {
1116
+ width: "100%",
1117
+ borderCollapse: "collapse",
1118
+ fontFamily: "monospace",
1119
+ fontSize: "0.8rem",
1120
+ },
1121
+ th: {
1122
+ padding: "0.5rem 0.75rem",
1123
+ textAlign: "left",
1124
+ backgroundColor: "#0f172a",
1125
+ borderBottom: "1px solid var(--border)",
1126
+ color: "#94a3b8",
1127
+ fontWeight: 600,
1128
+ position: "sticky",
1129
+ top: 0,
1130
+ zIndex: 1,
1131
+ },
1132
+ td: {
1133
+ padding: "0.4rem 0.75rem",
1134
+ borderBottom: "1px solid rgba(255, 255, 255, 0.03)",
1135
+ color: "#cbd5e1",
1136
+ whiteSpace: "nowrap",
1137
+ },
1138
+ trEven: {
1139
+ backgroundColor: "transparent",
1140
+ },
1141
+ trOdd: {
1142
+ backgroundColor: "rgba(255, 255, 255, 0.01)",
1143
+ },
1144
+ emptyResults: {
1145
+ flex: 1,
1146
+ display: "flex",
1147
+ alignItems: "center",
1148
+ justifyContent: "center",
1149
+ color: "var(--muted)",
1150
+ fontSize: "0.8rem",
1151
+ textAlign: "center",
1152
+ },
1153
+ loadingWrapper: {
1154
+ flex: 1,
1155
+ display: "flex",
1156
+ flexDirection: "column",
1157
+ alignItems: "center",
1158
+ justifyContent: "center",
1159
+ gap: "1rem",
1160
+ },
1161
+ spinnerLarge: {
1162
+ width: "24px",
1163
+ height: "24px",
1164
+ border: "3px solid rgba(109, 40, 217, 0.15)",
1165
+ borderTop: "3px solid var(--primary)",
1166
+ borderRadius: "50%",
1167
+ animation: "spin 1s linear infinite",
1168
+ },
1169
+ spinnerMini: {
1170
+ width: "12px",
1171
+ height: "12px",
1172
+ border: "2px solid rgba(255, 255, 255, 0.1)",
1173
+ borderTop: "2px solid var(--muted)",
1174
+ borderRadius: "50%",
1175
+ animation: "spin 1s linear infinite",
1176
+ },
1177
+ modalOverlay: {
1178
+ position: "fixed",
1179
+ top: 0,
1180
+ left: 0,
1181
+ right: 0,
1182
+ bottom: 0,
1183
+ backgroundColor: "rgba(0, 0, 0, 0.7)",
1184
+ display: "flex",
1185
+ alignItems: "center",
1186
+ justifyContent: "center",
1187
+ zIndex: 999,
1188
+ },
1189
+ modalContent: {
1190
+ width: "480px",
1191
+ backgroundColor: "#0d131f",
1192
+ borderRadius: "12px",
1193
+ border: "1px solid var(--border-focus)",
1194
+ padding: "1.5rem",
1195
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.5)",
1196
+ },
1197
+ formRow: {
1198
+ display: "flex",
1199
+ flexDirection: "column",
1200
+ gap: "0.35rem",
1201
+ marginBottom: "1rem",
1202
+ },
1203
+ label: {
1204
+ fontSize: "0.75rem",
1205
+ fontWeight: 600,
1206
+ color: "var(--muted)",
1207
+ },
1208
+ input: {
1209
+ padding: "0.5rem",
1210
+ borderRadius: "6px",
1211
+ border: "1px solid var(--border)",
1212
+ backgroundColor: "#070a12",
1213
+ color: "#fff",
1214
+ outline: "none",
1215
+ fontSize: "0.8rem",
1216
+ },
1217
+ select: {
1218
+ padding: "0.5rem",
1219
+ borderRadius: "6px",
1220
+ border: "1px solid var(--border)",
1221
+ backgroundColor: "#070a12",
1222
+ color: "#fff",
1223
+ outline: "none",
1224
+ fontSize: "0.8rem",
1225
+ },
1226
+ testResult: {
1227
+ padding: "0.5rem",
1228
+ borderRadius: "6px",
1229
+ fontSize: "0.75rem",
1230
+ marginBottom: "1rem",
1231
+ },
1232
+ modalFooter: {
1233
+ display: "flex",
1234
+ justifyContent: "space-between",
1235
+ marginTop: "1.5rem",
1236
+ },
1237
+ testBtn: {
1238
+ padding: "0.4rem 0.75rem",
1239
+ borderRadius: "6px",
1240
+ border: "1px solid var(--border)",
1241
+ backgroundColor: "transparent",
1242
+ color: "var(--muted)",
1243
+ fontSize: "0.75rem",
1244
+ fontWeight: 600,
1245
+ cursor: "pointer",
1246
+ },
1247
+ cancelBtn: {
1248
+ padding: "0.4rem 0.75rem",
1249
+ borderRadius: "6px",
1250
+ border: "none",
1251
+ backgroundColor: "transparent",
1252
+ color: "var(--muted)",
1253
+ fontSize: "0.75rem",
1254
+ fontWeight: 600,
1255
+ cursor: "pointer",
1256
+ },
1257
+ saveBtn: {
1258
+ padding: "0.4rem 0.75rem",
1259
+ borderRadius: "6px",
1260
+ border: "none",
1261
+ backgroundColor: "var(--primary)",
1262
+ color: "#fff",
1263
+ fontSize: "0.75rem",
1264
+ fontWeight: 600,
1265
+ cursor: "pointer",
1266
+ },
1267
+ };
1268
+
1269
+ // Inject keyframe spinner directly for robustness
1270
+ if (typeof document !== "undefined") {
1271
+ const style = document.createElement("style");
1272
+ style.innerHTML = `
1273
+ @keyframes spin {
1274
+ 0% { transform: rotate(0deg); }
1275
+ 100% { transform: rotate(360deg); }
1276
+ }
1277
+ `;
1278
+ document.head.appendChild(style);
1279
+ }