nitrostack 1.0.70 → 1.0.72

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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/studio/app/api/chat/route.ts +33 -15
  3. package/src/studio/app/auth/callback/page.tsx +6 -6
  4. package/src/studio/app/chat/page.tsx +1124 -415
  5. package/src/studio/app/chat/page.tsx.backup +1046 -187
  6. package/src/studio/app/globals.css +361 -191
  7. package/src/studio/app/health/page.tsx +72 -76
  8. package/src/studio/app/layout.tsx +9 -11
  9. package/src/studio/app/logs/page.tsx +29 -30
  10. package/src/studio/app/page.tsx +134 -230
  11. package/src/studio/app/prompts/page.tsx +115 -97
  12. package/src/studio/app/resources/page.tsx +115 -124
  13. package/src/studio/app/settings/page.tsx +1080 -125
  14. package/src/studio/app/tools/page.tsx +343 -0
  15. package/src/studio/components/EnlargeModal.tsx +76 -65
  16. package/src/studio/components/LogMessage.tsx +5 -5
  17. package/src/studio/components/MarkdownRenderer.tsx +4 -4
  18. package/src/studio/components/Sidebar.tsx +150 -210
  19. package/src/studio/components/SplashScreen.tsx +109 -0
  20. package/src/studio/components/ToolCard.tsx +50 -41
  21. package/src/studio/components/VoiceOrbOverlay.tsx +469 -0
  22. package/src/studio/components/WidgetRenderer.tsx +8 -3
  23. package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
  24. package/src/studio/lib/llm-service.ts +104 -1
  25. package/src/studio/lib/store.ts +36 -21
  26. package/src/studio/lib/types.ts +1 -1
  27. package/src/studio/package-lock.json +3303 -0
  28. package/src/studio/package.json +3 -1
  29. package/src/studio/public/NitroStudio Isotype Color.png +0 -0
  30. package/src/studio/tailwind.config.ts +63 -17
  31. package/templates/typescript-starter/package-lock.json +4112 -0
  32. package/templates/typescript-starter/package.json +2 -3
  33. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +100 -5
  34. package/src/studio/app/auth/page.tsx +0 -560
  35. package/src/studio/app/ping/page.tsx +0 -209
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from 'react';
4
4
  import { api } from '@/lib/api';
5
- import { Activity, RefreshCw, CheckCircle2, AlertTriangle, XCircle, HelpCircle } from 'lucide-react';
5
+ import { HeartIcon, ArrowPathIcon, CheckCircleIcon, ExclamationTriangleIcon, XCircleIcon, QuestionMarkCircleIcon } from '@heroicons/react/24/outline';
6
6
 
7
7
  interface HealthCheck {
8
8
  name: string;
@@ -38,47 +38,40 @@ export default function HealthPage() {
38
38
  healthChecks.length === 0
39
39
  ? 'unknown'
40
40
  : healthChecks.every((c) => c.status === 'up')
41
- ? 'healthy'
42
- : healthChecks.some((c) => c.status === 'down')
43
- ? 'unhealthy'
44
- : 'degraded';
41
+ ? 'healthy'
42
+ : healthChecks.some((c) => c.status === 'down')
43
+ ? 'unhealthy'
44
+ : 'degraded';
45
45
 
46
46
  const getStatusIcon = (status: string) => {
47
47
  switch (status) {
48
48
  case 'up':
49
49
  case 'healthy':
50
- return <CheckCircle2 className="w-12 h-12 text-emerald-500" />;
50
+ return <CheckCircleIcon className="w-12 h-12 text-emerald-500" />;
51
51
  case 'degraded':
52
- return <AlertTriangle className="w-12 h-12 text-amber-500" />;
52
+ return <ExclamationTriangleIcon className="w-12 h-12 text-status-warning" />;
53
53
  case 'down':
54
54
  case 'unhealthy':
55
- return <XCircle className="w-12 h-12 text-rose-500" />;
55
+ return <XCircleIcon className="w-12 h-12 text-rose-500" />;
56
56
  default:
57
- return <HelpCircle className="w-12 h-12 text-muted-foreground" />;
57
+ return <QuestionMarkCircleIcon className="w-12 h-12 text-muted-foreground" />;
58
58
  }
59
59
  };
60
60
 
61
61
  return (
62
62
  <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
63
- {/* Sticky Header */}
64
- <div className="sticky top-0 z-10 border-b border-border/50 px-6 py-3 flex items-center justify-between bg-card/80 backdrop-blur-md shadow-sm">
65
- <div className="flex items-center gap-3">
66
- <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-md">
67
- <Activity className="w-5 h-5 text-white" strokeWidth={2.5} />
68
- </div>
69
- <div>
70
- <h1 className="text-lg font-bold text-foreground">Health</h1>
71
- </div>
72
- </div>
73
- <button onClick={loadHealth} className="btn btn-primary text-sm px-4 py-2 gap-2" disabled={loading}>
74
- <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
75
- {loading ? 'Checking...' : 'Refresh'}
63
+ {/* Minimal Professional Header */}
64
+ <div className="sticky top-0 z-10 border-b border-border/50 px-6 py-4 flex items-center justify-between bg-card/50 backdrop-blur-sm">
65
+ <h1 className="text-lg font-semibold text-foreground">Health</h1>
66
+ <button onClick={loadHealth} className="btn btn-primary text-sm px-4 py-2 gap-2">
67
+ <ArrowPathIcon className="h-4 w-4" />
68
+ Refresh
76
69
  </button>
77
70
  </div>
78
71
 
79
- {/* Content - ONLY this scrolls */}
72
+ {/* Content */}
80
73
  <div className="flex-1 overflow-y-auto overflow-x-hidden">
81
- <div className="max-w-4xl mx-auto px-6 py-6">
74
+ <div className="max-w-5xl mx-auto px-6 py-6">
82
75
  {/* Overall Status */}
83
76
  <div className="card p-8 bg-gradient-to-br from-card to-muted/20 mb-6">
84
77
  <div className="flex items-center gap-6">
@@ -101,79 +94,82 @@ export default function HealthPage() {
101
94
  </div>
102
95
  ) : healthChecks.length === 0 ? (
103
96
  <div className="empty-state">
104
- <Activity className="empty-state-icon" />
97
+ <HeartIcon className="empty-state-icon" />
105
98
  <p className="empty-state-title">No health checks configured</p>
106
99
  <p className="empty-state-description">
107
100
  Add health checks using the @HealthCheck decorator
108
101
  </p>
109
102
  </div>
110
103
  ) : (
111
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
104
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
112
105
  {healthChecks.map((check) => (
113
- <div key={check.name} className="card card-hover p-6 animate-fade-in">
114
- <div className="flex items-start justify-between mb-4">
115
- <div className="flex items-center gap-3">
116
- <div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
117
- check.status === 'up'
106
+ <div key={check.name} className="group relative bg-card/50 border border-border/50 rounded-xl p-5 transition-all duration-200 hover:bg-card hover:border-border hover:shadow-lg animate-fade-in">
107
+ {/* Gradient hover effect */}
108
+ <div className="absolute inset-0 rounded-xl bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
109
+
110
+ <div className="relative">
111
+ <div className="flex items-start justify-between mb-3">
112
+ <div className="flex items-center gap-3">
113
+ <div className={`w-8 h-8 rounded-lg flex items-center justify-center ${check.status === 'up'
118
114
  ? 'bg-emerald-500/10'
119
115
  : check.status === 'degraded'
120
- ? 'bg-amber-500/10'
121
- : 'bg-rose-500/10'
122
- }`}>
123
- {check.status === 'up' ? (
124
- <CheckCircle2 className="w-6 h-6 text-emerald-500" />
125
- ) : check.status === 'degraded' ? (
126
- <AlertTriangle className="w-6 h-6 text-amber-500" />
127
- ) : (
128
- <XCircle className="w-6 h-6 text-rose-500" />
129
- )}
130
- </div>
131
- <div>
132
- <h3 className="font-semibold capitalize text-foreground">{check.name}</h3>
133
- <span
134
- className={`badge text-xs mt-1 ${
135
- check.status === 'up'
136
- ? 'badge-success'
116
+ ? 'bg-yellow-500/10'
117
+ : 'bg-rose-500/10'
118
+ }`}>
119
+ {check.status === 'up' ? (
120
+ <CheckCircleIcon className="w-4 h-4 text-emerald-500" />
121
+ ) : check.status === 'degraded' ? (
122
+ <ExclamationTriangleIcon className="w-4 h-4 text-yellow-500" />
123
+ ) : (
124
+ <XCircleIcon className="w-4 h-4 text-rose-500" />
125
+ )}
126
+ </div>
127
+ <div>
128
+ <h3 className="text-sm font-medium capitalize text-foreground">{check.name}</h3>
129
+ <span
130
+ className={`text-[10px] font-medium px-2 py-0.5 rounded-full ${check.status === 'up'
131
+ ? 'bg-emerald-500/10 text-emerald-500'
137
132
  : check.status === 'degraded'
138
- ? 'badge-warning'
139
- : 'badge-error'
140
- }`}
141
- >
142
- {check.status}
143
- </span>
133
+ ? 'bg-yellow-500/10 text-yellow-500'
134
+ : 'bg-rose-500/10 text-rose-500'
135
+ }`}
136
+ >
137
+ {check.status}
138
+ </span>
139
+ </div>
144
140
  </div>
145
141
  </div>
146
- </div>
147
142
 
148
- {check.message && (
149
- <p className="text-sm text-muted-foreground mb-3">{check.message}</p>
150
- )}
143
+ {check.message && (
144
+ <p className="text-xs text-muted-foreground/80 mb-3 leading-relaxed">{check.message}</p>
145
+ )}
151
146
 
152
- {check.details && (
153
- <div className="mt-3 p-4 bg-muted/30 rounded-lg border border-border">
154
- <p className="text-xs font-semibold text-muted-foreground mb-3 uppercase tracking-wide">Details</p>
155
- <div className="space-y-2">
156
- {Object.entries(check.details).map(([key, value]) => (
157
- <div key={key} className="flex justify-between text-sm">
158
- <span className="text-muted-foreground capitalize">{key}:</span>
159
- <span className="text-foreground font-mono font-medium">{String(value)}</span>
160
- </div>
161
- ))}
147
+ {check.details && (
148
+ <div className="mt-3 p-3 bg-muted/20 rounded-lg border border-border/50">
149
+ <p className="text-[10px] font-medium text-muted-foreground mb-2 uppercase tracking-wider">Details</p>
150
+ <div className="space-y-1.5">
151
+ {Object.entries(check.details).map(([key, value]) => (
152
+ <div key={key} className="flex justify-between text-xs">
153
+ <span className="text-muted-foreground/70 capitalize">{key}:</span>
154
+ <span className="text-foreground font-mono">{String(value)}</span>
155
+ </div>
156
+ ))}
157
+ </div>
162
158
  </div>
163
- </div>
164
- )}
159
+ )}
165
160
 
166
- {check.timestamp && (
167
- <p className="text-xs text-muted-foreground mt-3">
168
- Last check: {new Date(check.timestamp).toLocaleString()}
169
- </p>
170
- )}
161
+ {check.timestamp && (
162
+ <p className="text-[10px] text-muted-foreground/60 mt-3">
163
+ Last check: {new Date(check.timestamp).toLocaleString()}
164
+ </p>
165
+ )}
166
+ </div>
171
167
  </div>
172
168
  ))}
173
169
  </div>
174
170
  )}
175
171
  </div>
176
172
  </div>
177
- </div>
173
+ </div>
178
174
  );
179
175
  }
@@ -1,13 +1,10 @@
1
1
  import type { Metadata } from 'next';
2
2
  import './globals.css';
3
- import '@fontsource/inter/400.css';
4
- import '@fontsource/inter/500.css';
5
- import '@fontsource/inter/600.css';
6
- import '@fontsource/inter/700.css';
7
3
  import '@fontsource/jetbrains-mono/400.css';
8
4
  import '@fontsource/jetbrains-mono/500.css';
9
5
  import { Sidebar } from '@/components/Sidebar';
10
6
  import { EnlargeModal } from '@/components/EnlargeModal';
7
+ import { SplashScreen } from '@/components/SplashScreen';
11
8
 
12
9
  export const metadata: Metadata = {
13
10
  title: 'NitroStudio - MCP Development Suite',
@@ -26,17 +23,18 @@ export default function RootLayout({
26
23
  }) {
27
24
  return (
28
25
  <html lang="en" suppressHydrationWarning className="antialiased">
29
- <head>
30
- <script
31
- dangerouslySetInnerHTML={{
32
- __html: `
26
+ <head>
27
+ <script
28
+ dangerouslySetInnerHTML={{
29
+ __html: `
33
30
  // Force dark mode
34
31
  document.documentElement.className = 'dark antialiased';
35
32
  `,
36
- }}
37
- />
38
- </head>
33
+ }}
34
+ />
35
+ </head>
39
36
  <body className="min-h-screen font-sans">
37
+ <SplashScreen />
40
38
  <div className="flex min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
41
39
  <Sidebar />
42
40
  <main className="flex-1 transition-all duration-300 ease-in-out" style={{ marginLeft: 'var(--sidebar-width, 15rem)' }}>
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useState, useRef } from 'react';
4
- import { Terminal, Download, Copy, Trash2, Play, Pause, Filter } from 'lucide-react';
4
+ import { CommandLineIcon, ArrowDownTrayIcon, ClipboardDocumentIcon, TrashIcon, PlayIcon, PauseIcon, FunnelIcon } from '@heroicons/react/24/outline';
5
5
 
6
6
  interface LogEntry {
7
7
  timestamp: string;
@@ -17,12 +17,13 @@ export default function LogsPage() {
17
17
  const [filter, setFilter] = useState<string>('all');
18
18
  const [error, setError] = useState<string | null>(null);
19
19
  const logsEndRef = useRef<HTMLDivElement>(null);
20
+ const logsContainerRef = useRef<HTMLDivElement>(null);
20
21
  const eventSourceRef = useRef<EventSource | null>(null);
21
22
 
22
- // Auto-scroll to bottom
23
+ // Auto-scroll to bottom of logs container only
23
24
  useEffect(() => {
24
- if (autoScroll && logsEndRef.current) {
25
- logsEndRef.current.scrollIntoView({ behavior: 'smooth' });
25
+ if (autoScroll && logsContainerRef.current) {
26
+ logsContainerRef.current.scrollTop = logsContainerRef.current.scrollHeight;
26
27
  }
27
28
  }, [logs, autoScroll]);
28
29
 
@@ -63,10 +64,10 @@ export default function LogsPage() {
63
64
  };
64
65
 
65
66
  const downloadLogs = () => {
66
- const logsText = logs.map(log =>
67
+ const logsText = logs.map(log =>
67
68
  `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}${log.data ? '\n' + JSON.stringify(log.data, null, 2) : ''}`
68
69
  ).join('\n\n');
69
-
70
+
70
71
  const blob = new Blob([logsText], { type: 'text/plain' });
71
72
  const url = URL.createObjectURL(blob);
72
73
  const a = document.createElement('a');
@@ -77,10 +78,10 @@ export default function LogsPage() {
77
78
  };
78
79
 
79
80
  const copyLogs = async () => {
80
- const logsText = logs.map(log =>
81
+ const logsText = logs.map(log =>
81
82
  `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}${log.data ? '\n' + JSON.stringify(log.data, null, 2) : ''}`
82
83
  ).join('\n\n');
83
-
84
+
84
85
  await navigator.clipboard.writeText(logsText);
85
86
  // Could add a toast notification here
86
87
  };
@@ -89,8 +90,8 @@ export default function LogsPage() {
89
90
  setIsStreaming(!isStreaming);
90
91
  };
91
92
 
92
- const filteredLogs = filter === 'all'
93
- ? logs
93
+ const filteredLogs = filter === 'all'
94
+ ? logs
94
95
  : logs.filter(log => log.level === filter);
95
96
 
96
97
  const getLevelColor = (level: string) => {
@@ -120,12 +121,12 @@ export default function LogsPage() {
120
121
  };
121
122
 
122
123
  return (
123
- <div className="flex flex-col h-screen bg-black">
124
+ <div className="fixed inset-0 flex flex-col bg-black" style={{ left: 'var(--sidebar-width, 15rem)' }}>
124
125
  {/* Header */}
125
126
  <div className="flex items-center justify-between px-6 py-4 bg-gradient-to-r from-slate-900 via-slate-800 to-slate-900 border-b border-slate-700">
126
127
  <div className="flex items-center gap-3">
127
128
  <div className="p-2 rounded-lg bg-emerald-500/10 border border-emerald-500/20">
128
- <Terminal className="w-5 h-5 text-emerald-400" />
129
+ <CommandLineIcon className="w-5 h-5 text-emerald-400" />
129
130
  </div>
130
131
  <div>
131
132
  <h1 className="text-xl font-bold text-white">Server Logs</h1>
@@ -150,11 +151,10 @@ export default function LogsPage() {
150
151
  {/* Auto-scroll toggle */}
151
152
  <button
152
153
  onClick={() => setAutoScroll(!autoScroll)}
153
- className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
154
- autoScroll
155
- ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
156
- : 'bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700'
157
- }`}
154
+ className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${autoScroll
155
+ ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
156
+ : 'bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700'
157
+ }`}
158
158
  >
159
159
  Auto-scroll
160
160
  </button>
@@ -162,14 +162,13 @@ export default function LogsPage() {
162
162
  {/* Streaming toggle */}
163
163
  <button
164
164
  onClick={toggleStreaming}
165
- className={`p-2 rounded-lg transition-colors ${
166
- isStreaming
167
- ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
168
- : 'bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700'
169
- }`}
165
+ className={`p-2 rounded-lg transition-colors ${isStreaming
166
+ ? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
167
+ : 'bg-slate-800 text-slate-400 border border-slate-700 hover:bg-slate-700'
168
+ }`}
170
169
  title={isStreaming ? 'Pause streaming' : 'Resume streaming'}
171
170
  >
172
- {isStreaming ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
171
+ {isStreaming ? <PauseIcon className="w-4 h-4" /> : <PlayIcon className="w-4 h-4" />}
173
172
  </button>
174
173
 
175
174
  {/* Copy */}
@@ -178,7 +177,7 @@ export default function LogsPage() {
178
177
  className="p-2 bg-slate-800 border border-slate-700 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-white transition-colors"
179
178
  title="Copy logs"
180
179
  >
181
- <Copy className="w-4 h-4" />
180
+ <ClipboardDocumentIcon className="w-4 h-4" />
182
181
  </button>
183
182
 
184
183
  {/* Download */}
@@ -187,7 +186,7 @@ export default function LogsPage() {
187
186
  className="p-2 bg-slate-800 border border-slate-700 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-white transition-colors"
188
187
  title="Download logs"
189
188
  >
190
- <Download className="w-4 h-4" />
189
+ <ArrowDownTrayIcon className="w-4 h-4" />
191
190
  </button>
192
191
 
193
192
  {/* Clear */}
@@ -196,7 +195,7 @@ export default function LogsPage() {
196
195
  className="p-2 bg-slate-800 border border-slate-700 rounded-lg text-slate-400 hover:bg-red-900/50 hover:text-red-400 hover:border-red-500/30 transition-colors"
197
196
  title="Clear logs"
198
197
  >
199
- <Trash2 className="w-4 h-4" />
198
+ <TrashIcon className="w-4 h-4" />
200
199
  </button>
201
200
  </div>
202
201
  </div>
@@ -226,11 +225,11 @@ export default function LogsPage() {
226
225
  </div>
227
226
  </div>
228
227
 
229
- {/* Logs Container */}
230
- <div className="flex-1 overflow-y-auto bg-black font-mono text-sm">
228
+ {/* Logs Container - Only this scrolls */}
229
+ <div ref={logsContainerRef} className="flex-1 overflow-y-auto bg-black font-mono text-sm">
231
230
  {filteredLogs.length === 0 ? (
232
231
  <div className="flex flex-col items-center justify-center h-full text-slate-600">
233
- <Terminal className="w-16 h-16 mb-4 opacity-20" />
232
+ <CommandLineIcon className="w-16 h-16 mb-4 opacity-20" />
234
233
  <p className="text-lg font-medium">No logs yet</p>
235
234
  <p className="text-sm">Logs will appear here as they are generated</p>
236
235
  </div>
@@ -257,7 +256,7 @@ export default function LogsPage() {
257
256
  {/* Message */}
258
257
  <div className="flex-1">
259
258
  <p className="text-white break-words">{log.message}</p>
260
-
259
+
261
260
  {/* Data (if present) */}
262
261
  {log.data && (
263
262
  <pre className="mt-2 p-3 bg-black/50 rounded border border-slate-800 overflow-x-auto">