nitrostack 1.0.65 → 1.0.66

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. package/package.json +2 -1
  2. package/src/studio/README.md +140 -0
  3. package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
  4. package/src/studio/app/api/auth/register-client/route.ts +67 -0
  5. package/src/studio/app/api/chat/route.ts +250 -0
  6. package/src/studio/app/api/health/checks/route.ts +42 -0
  7. package/src/studio/app/api/health/route.ts +13 -0
  8. package/src/studio/app/api/init/route.ts +109 -0
  9. package/src/studio/app/api/ping/route.ts +13 -0
  10. package/src/studio/app/api/prompts/[name]/route.ts +21 -0
  11. package/src/studio/app/api/prompts/route.ts +13 -0
  12. package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
  13. package/src/studio/app/api/resources/route.ts +13 -0
  14. package/src/studio/app/api/roots/route.ts +13 -0
  15. package/src/studio/app/api/sampling/route.ts +14 -0
  16. package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
  17. package/src/studio/app/api/tools/route.ts +23 -0
  18. package/src/studio/app/api/widget-examples/route.ts +44 -0
  19. package/src/studio/app/auth/callback/page.tsx +175 -0
  20. package/src/studio/app/auth/page.tsx +560 -0
  21. package/src/studio/app/chat/page.tsx +1133 -0
  22. package/src/studio/app/chat/page.tsx.backup +390 -0
  23. package/src/studio/app/globals.css +486 -0
  24. package/src/studio/app/health/page.tsx +179 -0
  25. package/src/studio/app/layout.tsx +68 -0
  26. package/src/studio/app/logs/page.tsx +279 -0
  27. package/src/studio/app/page.tsx +351 -0
  28. package/src/studio/app/page.tsx.backup +346 -0
  29. package/src/studio/app/ping/page.tsx +209 -0
  30. package/src/studio/app/prompts/page.tsx +230 -0
  31. package/src/studio/app/resources/page.tsx +315 -0
  32. package/src/studio/app/settings/page.tsx +199 -0
  33. package/src/studio/branding.md +807 -0
  34. package/src/studio/components/EnlargeModal.tsx +138 -0
  35. package/src/studio/components/LogMessage.tsx +153 -0
  36. package/src/studio/components/MarkdownRenderer.tsx +410 -0
  37. package/src/studio/components/Sidebar.tsx +295 -0
  38. package/src/studio/components/ToolCard.tsx +139 -0
  39. package/src/studio/components/WidgetRenderer.tsx +346 -0
  40. package/src/studio/lib/api.ts +207 -0
  41. package/src/studio/lib/http-client-transport.ts +222 -0
  42. package/src/studio/lib/llm-service.ts +480 -0
  43. package/src/studio/lib/log-manager.ts +76 -0
  44. package/src/studio/lib/mcp-client.ts +258 -0
  45. package/src/studio/lib/store.ts +192 -0
  46. package/src/studio/lib/theme-provider.tsx +50 -0
  47. package/src/studio/lib/types.ts +107 -0
  48. package/src/studio/lib/widget-loader.ts +90 -0
  49. package/src/studio/middleware.ts +27 -0
  50. package/src/studio/next.config.js +38 -0
  51. package/src/studio/package.json +35 -0
  52. package/src/studio/postcss.config.mjs +10 -0
  53. package/src/studio/public/nitrocloud.png +0 -0
  54. package/src/studio/tailwind.config.ts +67 -0
  55. package/src/studio/tsconfig.json +42 -0
@@ -0,0 +1,295 @@
1
+ 'use client';
2
+
3
+ import { useStudioStore } from '@/lib/store';
4
+ import type { TabType } from '@/lib/types';
5
+ import { useRouter, usePathname } from 'next/navigation';
6
+ import { useEffect, useState } from 'react';
7
+ import Image from 'next/image';
8
+ import {
9
+ Wrench,
10
+ MessageSquare,
11
+ Package,
12
+ FileText,
13
+ Activity,
14
+ Shield,
15
+ Wifi,
16
+ Zap,
17
+ Settings,
18
+ Sparkles,
19
+ Terminal
20
+ } from 'lucide-react';
21
+
22
+ const navItems: Array<{ id: TabType | 'settings' | 'logs'; label: string; icon: any; path: string }> = [
23
+ { id: 'tools', label: 'Tools', icon: Wrench, path: '/' },
24
+ { id: 'chat', label: 'AI Chat', icon: MessageSquare, path: '/chat' },
25
+ { id: 'resources', label: 'Resources', icon: Package, path: '/resources' },
26
+ { id: 'prompts', label: 'Prompts', icon: FileText, path: '/prompts' },
27
+ { id: 'health', label: 'Health', icon: Activity, path: '/health' },
28
+ { id: 'logs', label: 'Logs', icon: Terminal, path: '/logs' },
29
+ { id: 'auth', label: 'OAuth 2.1', icon: Shield, path: '/auth' },
30
+ { id: 'ping', label: 'Ping', icon: Wifi, path: '/ping' },
31
+ { id: 'settings', label: 'Settings', icon: Settings, path: '/settings' },
32
+ ];
33
+
34
+ export function Sidebar() {
35
+ const { connection } = useStudioStore();
36
+ const router = useRouter();
37
+ const pathname = usePathname();
38
+ const [mounted, setMounted] = useState(false);
39
+ const [isCollapsed, setIsCollapsed] = useState(false);
40
+
41
+ useEffect(() => {
42
+ setMounted(true);
43
+ // Force dark mode
44
+ document.documentElement.className = 'dark antialiased';
45
+ // Load collapse state from localStorage
46
+ const saved = localStorage.getItem('sidebar_collapsed');
47
+ if (saved !== null) setIsCollapsed(saved === 'true');
48
+ }, []);
49
+
50
+ const handleNavigation = (path: string) => {
51
+ router.push(path);
52
+ };
53
+
54
+ const getConnectionStatus = () => {
55
+ if (connection.status === 'connected') return 'connected';
56
+ if (connection.status === 'connecting') return 'connecting';
57
+ return 'disconnected';
58
+ };
59
+
60
+ const toggleSidebar = () => {
61
+ const newState = !isCollapsed;
62
+ setIsCollapsed(newState);
63
+ localStorage.setItem('sidebar_collapsed', String(newState));
64
+ // Dispatch custom event to update layout
65
+ window.dispatchEvent(new Event('sidebar-toggle'));
66
+ };
67
+
68
+ if (!mounted) return null;
69
+
70
+ return (
71
+ <nav className={`fixed left-0 top-0 h-screen glass flex flex-col z-50 border-r border-border/50 transition-all duration-300 ease-in-out ${
72
+ isCollapsed ? 'w-16' : 'w-60 md:w-60'
73
+ } ${isCollapsed ? '' : 'max-md:w-16'}`}>
74
+ {/* Compact Professional Header */}
75
+ <div className="relative p-3 border-b border-border/50 bg-gradient-to-b from-card/80 to-transparent">
76
+ <div className="flex items-center justify-between mb-2">
77
+ {/* Minimalist Professional Logo */}
78
+ <div
79
+ className="flex items-center gap-2 group cursor-pointer flex-1"
80
+ onClick={() => handleNavigation('/')}
81
+ >
82
+ <div className="relative flex-shrink-0">
83
+ <div className="absolute inset-0 rounded-lg bg-gradient-to-br from-primary to-amber-500 blur opacity-30 group-hover:opacity-50 transition-opacity duration-300" />
84
+ <div className="relative w-9 h-9 rounded-lg bg-gradient-to-br from-slate-900 to-slate-800 border border-primary/30 flex items-center justify-center shadow-lg group-hover:shadow-primary/30 transition-all duration-300 overflow-hidden">
85
+ {/* NitroCloud Logo */}
86
+ <Image
87
+ src="/nitrocloud.png"
88
+ alt="NitroCloud Logo"
89
+ width={36}
90
+ height={36}
91
+ className="relative z-10 object-contain"
92
+ unoptimized
93
+ />
94
+ {/* Subtle glow effect */}
95
+ <div className="absolute inset-0 bg-gradient-to-tr from-primary/20 to-amber-500/20 group-hover:opacity-100 opacity-0 transition-opacity duration-300" />
96
+ </div>
97
+ </div>
98
+
99
+ {!isCollapsed && (
100
+ <div className="flex-1 overflow-hidden">
101
+ <h1 className="text-base font-bold bg-gradient-to-r from-primary to-amber-500 bg-clip-text text-transparent tracking-tight whitespace-nowrap">
102
+ NitroStudio
103
+ </h1>
104
+ <p className="text-[9px] text-muted-foreground font-medium uppercase tracking-wider">
105
+ MCP Suite
106
+ </p>
107
+ </div>
108
+ )}
109
+ </div>
110
+
111
+ {/* Toggle Button */}
112
+ <button
113
+ onClick={toggleSidebar}
114
+ className="flex-shrink-0 w-7 h-7 rounded-md bg-muted/50 hover:bg-muted border border-border/50 hover:border-primary/30 flex items-center justify-center transition-all duration-300 group/toggle"
115
+ title={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
116
+ >
117
+ <svg
118
+ width="14"
119
+ height="14"
120
+ viewBox="0 0 24 24"
121
+ fill="none"
122
+ className={`text-muted-foreground group-hover/toggle:text-primary transition-all duration-300 ${
123
+ isCollapsed ? 'rotate-180' : ''
124
+ }`}
125
+ >
126
+ <path d="M15 18L9 12L15 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
127
+ </svg>
128
+ </button>
129
+ </div>
130
+
131
+ {/* Compact Status Indicators */}
132
+ {!isCollapsed ? (
133
+ <div className="space-y-1.5">
134
+ {/* Connection Status */}
135
+ <div className="relative group">
136
+ <div className="relative flex items-center gap-2 px-2 py-1.5 rounded-md bg-card/60 border border-border/50 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
137
+ <div className="relative flex items-center justify-center flex-shrink-0">
138
+ {getConnectionStatus() === 'connected' && (
139
+ <div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-ping" />
140
+ )}
141
+ <div
142
+ className={`relative w-2 h-2 rounded-full transition-all duration-300 ${
143
+ getConnectionStatus() === 'connected'
144
+ ? 'bg-emerald-500'
145
+ : getConnectionStatus() === 'connecting'
146
+ ? 'bg-amber-500 animate-pulse'
147
+ : 'bg-rose-500'
148
+ }`}
149
+ style={{
150
+ boxShadow: getConnectionStatus() === 'connected'
151
+ ? '0 0 8px rgba(34, 197, 94, 0.6)'
152
+ : getConnectionStatus() === 'connecting'
153
+ ? '0 0 8px rgba(245, 158, 11, 0.6)'
154
+ : '0 0 8px rgba(239, 68, 68, 0.6)'
155
+ }}
156
+ />
157
+ </div>
158
+ <span className="text-[10px] font-semibold uppercase tracking-wide text-foreground">
159
+ {connection.status}
160
+ </span>
161
+ </div>
162
+ </div>
163
+
164
+ {/* Transport Mode */}
165
+ <div className="relative group">
166
+ <div className="relative px-2 py-1.5 rounded-md bg-primary/5 border border-primary/20 backdrop-blur-sm group-hover:border-primary/30 transition-all duration-300">
167
+ <div className="flex items-center justify-between text-[10px]">
168
+ <span className="font-medium text-muted-foreground">Transport:</span>
169
+ <span className="font-bold text-primary uppercase">
170
+ STDIO
171
+ </span>
172
+ </div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ ) : (
177
+ <div className="flex flex-col items-center gap-1.5">
178
+ {/* Collapsed: Just the status dot */}
179
+ <div
180
+ className="relative w-7 h-7 rounded-md bg-card/60 border border-border/50 flex items-center justify-center group hover:border-primary/30 transition-all"
181
+ title={`Status: ${connection.status}`}
182
+ >
183
+ {getConnectionStatus() === 'connected' && (
184
+ <div className="absolute inset-0 rounded-md bg-emerald-500/20 animate-ping" />
185
+ )}
186
+ <div
187
+ className={`relative w-2 h-2 rounded-full ${
188
+ getConnectionStatus() === 'connected'
189
+ ? 'bg-emerald-500'
190
+ : getConnectionStatus() === 'connecting'
191
+ ? 'bg-amber-500 animate-pulse'
192
+ : 'bg-rose-500'
193
+ }`}
194
+ style={{
195
+ boxShadow: getConnectionStatus() === 'connected'
196
+ ? '0 0 8px rgba(34, 197, 94, 0.8)'
197
+ : getConnectionStatus() === 'connecting'
198
+ ? '0 0 8px rgba(245, 158, 11, 0.8)'
199
+ : '0 0 8px rgba(239, 68, 68, 0.8)'
200
+ }}
201
+ />
202
+ </div>
203
+ </div>
204
+ )}
205
+ </div>
206
+
207
+ {/* Compact Navigation */}
208
+ <div className="flex-1 overflow-y-auto py-2 px-2 scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent">
209
+ <div className={`space-y-0.5 ${isCollapsed ? 'flex flex-col items-center' : ''}`}>
210
+ {navItems.map((item) => {
211
+ const Icon = item.icon;
212
+ const isActive = pathname === item.path;
213
+
214
+ return (
215
+ <button
216
+ key={item.id}
217
+ onClick={() => handleNavigation(item.path)}
218
+ title={isCollapsed ? item.label : undefined}
219
+ className={`
220
+ relative flex items-center gap-2 text-sm font-medium rounded-lg transition-all duration-300 group overflow-hidden
221
+ ${isCollapsed ? 'w-10 h-10 justify-center' : 'w-full px-3 py-2'}
222
+ ${isActive
223
+ ? 'bg-gradient-to-r from-primary/15 to-amber-500/10 text-primary shadow-md ring-1 ring-primary/30'
224
+ : 'text-foreground/70 hover:bg-primary/5 hover:text-primary'
225
+ }
226
+ `}
227
+ >
228
+ {/* Active indicator */}
229
+ {isActive && !isCollapsed && (
230
+ <div className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-6 bg-gradient-to-b from-primary to-amber-500 rounded-r-full" />
231
+ )}
232
+
233
+ {/* Icon */}
234
+ <div className={`relative flex-shrink-0 ${isActive ? 'scale-105' : 'group-hover:scale-105'} transition-transform duration-300`}>
235
+ <Icon
236
+ className={`w-4 h-4 ${
237
+ isActive
238
+ ? 'text-primary'
239
+ : 'text-muted-foreground group-hover:text-primary'
240
+ }`}
241
+ strokeWidth={isActive ? 2.5 : 2}
242
+ />
243
+ </div>
244
+
245
+ {/* Label - only show when expanded */}
246
+ {!isCollapsed && (
247
+ <span className="text-xs whitespace-nowrap overflow-hidden">
248
+ {item.label}
249
+ </span>
250
+ )}
251
+
252
+ {/* Subtle shine effect */}
253
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-700 ease-out pointer-events-none" />
254
+ </button>
255
+ );
256
+ })}
257
+ </div>
258
+ </div>
259
+
260
+ {/* Compact Footer */}
261
+ <div className="p-2 border-t border-border/50 bg-gradient-to-t from-card/60 to-transparent backdrop-blur-sm">
262
+ {!isCollapsed ? (
263
+ <div className="space-y-1.5">
264
+ {/* Version info */}
265
+ <div className="px-2 py-1.5 rounded-md bg-muted/30 border border-border/30">
266
+ <div className="flex items-center justify-between text-[9px]">
267
+ <span className="font-medium text-muted-foreground uppercase tracking-wide">MCP v1.0</span>
268
+ <span className="font-bold text-foreground">NitroStack</span>
269
+ </div>
270
+ </div>
271
+
272
+ {/* Copyright */}
273
+ <div className="text-center">
274
+ <p className="text-[8px] text-muted-foreground/50 font-medium">
275
+ © 2025 NitroCloud
276
+ </p>
277
+ </div>
278
+ </div>
279
+ ) : (
280
+ <div className="flex flex-col items-center">
281
+ {/* Collapsed: Minimal version indicator */}
282
+ <div
283
+ className="w-10 h-10 rounded-md bg-muted/30 border border-border/30 flex items-center justify-center group hover:border-primary/30 transition-all"
284
+ title="MCP v1.0 • NitroStack"
285
+ >
286
+ <span className="text-[9px] font-bold text-primary group-hover:scale-110 transition-transform">
287
+ v1
288
+ </span>
289
+ </div>
290
+ </div>
291
+ )}
292
+ </div>
293
+ </nav>
294
+ );
295
+ }
@@ -0,0 +1,139 @@
1
+ 'use client';
2
+
3
+ import type { Tool } from '@/lib/types';
4
+ import { useStudioStore } from '@/lib/store';
5
+ import { WidgetRenderer } from './WidgetRenderer';
6
+ import { useRouter } from 'next/navigation';
7
+ import { Zap, Palette, Maximize2, Play, Sparkles, MessageSquare } from 'lucide-react';
8
+
9
+ interface ToolCardProps {
10
+ tool: Tool;
11
+ onExecute: (tool: Tool) => void;
12
+ }
13
+
14
+ export function ToolCard({ tool, onExecute }: ToolCardProps) {
15
+ const { openEnlargeModal } = useStudioStore();
16
+ const router = useRouter();
17
+
18
+ // Check if tool has widget - check multiple sources
19
+ const widgetUri =
20
+ tool.widget?.route ||
21
+ tool.outputTemplate ||
22
+ tool._meta?.['ui/template'] ||
23
+ tool._meta?.['openai/outputTemplate'];
24
+ const hasWidget = !!widgetUri && widgetUri.trim().length > 0;
25
+
26
+ // Get example data for preview - check both examples and _meta
27
+ const exampleData = tool.examples?.response || tool._meta?.['tool/examples']?.response;
28
+
29
+ // Debug logging for widget detection
30
+ if (hasWidget) {
31
+ console.log('ToolCard - Widget detected:', {
32
+ toolName: tool.name,
33
+ widgetUri,
34
+ hasExampleData: !!exampleData,
35
+ exampleDataType: exampleData ? typeof exampleData : 'none',
36
+ toolExamples: tool.examples,
37
+ toolMeta: tool._meta,
38
+ metaExamples: tool._meta?.['tool/examples'],
39
+ });
40
+ }
41
+
42
+ const handleUseInChat = (e: React.MouseEvent) => {
43
+ e.stopPropagation();
44
+
45
+ // Build the tool execution message
46
+ const toolMessage = `Use the ${tool.name} tool`;
47
+
48
+ // Store the message in localStorage
49
+ if (typeof window !== 'undefined') {
50
+ window.localStorage.setItem('chatInput', toolMessage);
51
+ window.localStorage.setItem('suggestedTool', tool.name);
52
+ }
53
+
54
+ router.push('/chat');
55
+ };
56
+
57
+ const handleEnlarge = (e: React.MouseEvent) => {
58
+ e.stopPropagation();
59
+ openEnlargeModal('tool', { ...tool, responseData: exampleData });
60
+ };
61
+
62
+ return (
63
+ <div
64
+ className="card card-hover p-6 animate-fade-in cursor-pointer"
65
+ onClick={() => onExecute(tool)}
66
+ >
67
+ {/* Header */}
68
+ <div className="flex items-start justify-between mb-4">
69
+ <div className="flex items-center gap-3">
70
+ <div className={`w-12 h-12 rounded-lg flex items-center justify-center ${hasWidget ? 'bg-purple-500/10' : 'bg-primary/10'}`}>
71
+ {hasWidget ? (
72
+ <Palette className="w-6 h-6 text-purple-500" />
73
+ ) : (
74
+ <Zap className="w-6 h-6 text-primary" />
75
+ )}
76
+ </div>
77
+ <div>
78
+ <h3 className="font-semibold text-lg text-foreground">
79
+ {tool.name}
80
+ </h3>
81
+ <span className={`badge ${hasWidget ? 'badge-secondary' : 'badge-primary'} text-xs mt-1`}>
82
+ {hasWidget ? 'tool + widget' : 'tool'}
83
+ </span>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ {/* Description */}
89
+ <p className="text-sm text-muted-foreground mb-4 line-clamp-2">
90
+ {tool.description || 'No description'}
91
+ </p>
92
+
93
+ {/* Widget Preview - Show if widget exists AND has example data */}
94
+ {hasWidget && widgetUri && exampleData && (
95
+ <div className="relative mb-4 rounded-lg overflow-hidden border border-border bg-muted/20">
96
+ <div className="absolute top-2 left-2 z-10 flex items-center gap-1 bg-primary/90 backdrop-blur-sm text-black px-2 py-1 rounded-md text-xs font-semibold shadow-lg">
97
+ <Sparkles className="w-3 h-3" />
98
+ Widget Preview
99
+ </div>
100
+ <div className="h-64 relative">
101
+ <WidgetRenderer
102
+ uri={widgetUri}
103
+ data={exampleData}
104
+ className="w-full h-full"
105
+ />
106
+ </div>
107
+ </div>
108
+ )}
109
+
110
+ {/* Action Buttons */}
111
+ <div className="flex flex-wrap items-center gap-2" onClick={(e) => e.stopPropagation()}>
112
+ {hasWidget && (
113
+ <button
114
+ onClick={handleEnlarge}
115
+ className="btn btn-secondary flex-1 min-w-[90px] text-xs sm:text-sm gap-1.5 px-2.5 py-1.5 sm:px-4 sm:py-2"
116
+ >
117
+ <Maximize2 className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
118
+ <span className="truncate">Enlarge</span>
119
+ </button>
120
+ )}
121
+ <button
122
+ onClick={() => onExecute(tool)}
123
+ className="btn btn-primary flex-1 min-w-[90px] text-xs sm:text-sm gap-1.5 px-2.5 py-1.5 sm:px-4 sm:py-2"
124
+ >
125
+ <Play className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
126
+ <span className="truncate">Execute</span>
127
+ </button>
128
+ <button
129
+ onClick={handleUseInChat}
130
+ className="btn btn-secondary flex-1 min-w-[90px] text-xs sm:text-sm gap-1.5 px-2.5 py-1.5 sm:px-4 sm:py-2"
131
+ title="Use in Chat"
132
+ >
133
+ <MessageSquare className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
134
+ <span className="truncate">Chat</span>
135
+ </button>
136
+ </div>
137
+ </div>
138
+ );
139
+ }