nitrostack 1.0.15 → 1.0.17
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.
- package/README.md +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +2 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/mcp-dev-wrapper.js +30 -17
- package/dist/cli/mcp-dev-wrapper.js.map +1 -1
- package/dist/core/app-decorator.js +2 -2
- package/dist/core/app-decorator.js.map +1 -1
- package/dist/core/builders.js +2 -2
- package/dist/core/builders.js.map +1 -1
- package/dist/core/resource.js +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/server.js +2 -2
- package/dist/core/server.js.map +1 -1
- package/dist/core/transports/http-server.d.ts.map +1 -1
- package/dist/core/transports/http-server.js +21 -1
- package/dist/core/transports/http-server.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/studio/app/api/chat/route.ts +155 -28
- package/src/studio/app/api/init/route.ts +28 -4
- package/src/studio/app/auth/page.tsx +13 -9
- package/src/studio/app/chat/page.tsx +599 -133
- package/src/studio/app/health/page.tsx +101 -99
- package/src/studio/app/layout.tsx +24 -4
- package/src/studio/app/page.tsx +61 -56
- package/src/studio/app/ping/page.tsx +13 -8
- package/src/studio/app/prompts/page.tsx +72 -70
- package/src/studio/app/resources/page.tsx +88 -86
- package/src/studio/app/settings/page.tsx +270 -0
- package/src/studio/components/EnlargeModal.tsx +21 -15
- package/src/studio/components/LogMessage.tsx +153 -0
- package/src/studio/components/MarkdownRenderer.tsx +410 -0
- package/src/studio/components/Sidebar.tsx +197 -35
- package/src/studio/components/ToolCard.tsx +27 -9
- package/src/studio/components/WidgetRenderer.tsx +4 -2
- package/src/studio/lib/http-client-transport.ts +222 -0
- package/src/studio/lib/llm-service.ts +119 -0
- package/src/studio/lib/log-manager.ts +76 -0
- package/src/studio/lib/mcp-client.ts +103 -13
- package/src/studio/package-lock.json +3129 -0
- package/src/studio/package.json +1 -0
- package/templates/typescript-auth/README.md +3 -1
- package/templates/typescript-auth/src/db/database.ts +5 -8
- package/templates/typescript-auth/src/index.ts +13 -2
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
- package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
- package/templates/typescript-auth-api-key/README.md +3 -1
- package/templates/typescript-auth-api-key/src/index.ts +11 -3
- package/templates/typescript-starter/README.md +3 -1
|
@@ -59,119 +59,121 @@ export default function HealthPage() {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
<div className="
|
|
63
|
-
{/* Header */}
|
|
64
|
-
<div className="
|
|
65
|
-
<div className="flex items-center
|
|
66
|
-
<div className="flex items-center
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
<h1 className="text-3xl font-bold text-foreground">Health Checks</h1>
|
|
72
|
-
<p className="text-muted-foreground mt-1">System health monitoring</p>
|
|
73
|
-
</div>
|
|
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>
|
|
74
71
|
</div>
|
|
75
|
-
<button onClick={loadHealth} className="btn btn-primary gap-2" disabled={loading}>
|
|
76
|
-
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
|
77
|
-
{loading ? 'Checking...' : 'Refresh'}
|
|
78
|
-
</button>
|
|
79
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'}
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
{/* Content - ONLY this scrolls */}
|
|
80
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
|
81
|
+
<div className="max-w-4xl mx-auto px-6 py-6">
|
|
82
|
+
{/* Overall Status */}
|
|
83
|
+
<div className="card p-8 bg-gradient-to-br from-card to-muted/20 mb-6">
|
|
84
|
+
<div className="flex items-center gap-6">
|
|
85
|
+
{getStatusIcon(overallStatus)}
|
|
86
|
+
<div>
|
|
87
|
+
<h2 className="text-3xl font-bold capitalize text-foreground">{overallStatus}</h2>
|
|
88
|
+
<p className="text-muted-foreground mt-1">
|
|
89
|
+
{healthChecks.length} health check{healthChecks.length !== 1 ? 's' : ''} configured
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
90
92
|
</div>
|
|
91
93
|
</div>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
check.status === 'up'
|
|
118
|
-
? 'bg-emerald-500/10'
|
|
119
|
-
: 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 ${
|
|
95
|
+
{/* Health Checks Grid */}
|
|
96
|
+
{loading && healthChecks.length === 0 ? (
|
|
97
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
98
|
+
{[1, 2].map((i) => (
|
|
99
|
+
<div key={i} className="card skeleton h-48"></div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
) : healthChecks.length === 0 ? (
|
|
103
|
+
<div className="empty-state">
|
|
104
|
+
<Activity className="empty-state-icon" />
|
|
105
|
+
<p className="empty-state-title">No health checks configured</p>
|
|
106
|
+
<p className="empty-state-description">
|
|
107
|
+
Add health checks using the @HealthCheck decorator
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
112
|
+
{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 ${
|
|
135
117
|
check.status === 'up'
|
|
136
|
-
? '
|
|
118
|
+
? 'bg-emerald-500/10'
|
|
137
119
|
: check.status === 'degraded'
|
|
138
|
-
? '
|
|
139
|
-
: '
|
|
140
|
-
}`}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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'
|
|
137
|
+
: check.status === 'degraded'
|
|
138
|
+
? 'badge-warning'
|
|
139
|
+
: 'badge-error'
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
{check.status}
|
|
143
|
+
</span>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
144
146
|
</div>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
147
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
{check.message && (
|
|
149
|
+
<p className="text-sm text-muted-foreground mb-3">{check.message}</p>
|
|
150
|
+
)}
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
))}
|
|
160
162
|
</div>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
{check.timestamp && (
|
|
167
|
+
<p className="text-xs text-muted-foreground mt-3">
|
|
168
|
+
Last check: {new Date(check.timestamp).toLocaleString()}
|
|
169
|
+
</p>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
171
173
|
</div>
|
|
172
|
-
)
|
|
174
|
+
)}
|
|
173
175
|
</div>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
176
178
|
);
|
|
177
179
|
}
|
|
@@ -10,8 +10,13 @@ import { Sidebar } from '@/components/Sidebar';
|
|
|
10
10
|
import { EnlargeModal } from '@/components/EnlargeModal';
|
|
11
11
|
|
|
12
12
|
export const metadata: Metadata = {
|
|
13
|
-
title: '
|
|
14
|
-
description: 'Professional development environment for
|
|
13
|
+
title: 'NitroStudio - MCP Development Suite',
|
|
14
|
+
description: 'Professional AI-powered development environment for Model Context Protocol (MCP) servers. Built with NitroStack.',
|
|
15
|
+
keywords: ['MCP', 'Model Context Protocol', 'AI Development', 'NitroStack', 'NitroStudio'],
|
|
16
|
+
authors: [{ name: 'NitroStack Team' }],
|
|
17
|
+
icons: {
|
|
18
|
+
icon: '/favicon.ico',
|
|
19
|
+
},
|
|
15
20
|
};
|
|
16
21
|
|
|
17
22
|
export default function RootLayout({
|
|
@@ -34,14 +39,29 @@ export default function RootLayout({
|
|
|
34
39
|
<body className="min-h-screen font-sans">
|
|
35
40
|
<div className="flex min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
|
|
36
41
|
<Sidebar />
|
|
37
|
-
<main className="flex-1
|
|
38
|
-
<div className="min-h-screen p-8">
|
|
42
|
+
<main className="flex-1 transition-all duration-300 ease-in-out" style={{ marginLeft: 'var(--sidebar-width, 15rem)' }}>
|
|
43
|
+
<div className="min-h-screen p-3 sm:p-6 md:p-8">
|
|
39
44
|
{children}
|
|
40
45
|
</div>
|
|
41
46
|
</main>
|
|
42
47
|
</div>
|
|
43
48
|
{/* Global Modal - Available on all pages */}
|
|
44
49
|
<EnlargeModal />
|
|
50
|
+
{/* CSS Variable for sidebar width */}
|
|
51
|
+
<script
|
|
52
|
+
dangerouslySetInnerHTML={{
|
|
53
|
+
__html: `
|
|
54
|
+
function updateSidebarWidth() {
|
|
55
|
+
const isCollapsed = localStorage.getItem('sidebar_collapsed') === 'true';
|
|
56
|
+
document.documentElement.style.setProperty('--sidebar-width', isCollapsed ? '4rem' : '15rem');
|
|
57
|
+
}
|
|
58
|
+
updateSidebarWidth();
|
|
59
|
+
window.addEventListener('storage', updateSidebarWidth);
|
|
60
|
+
// Update on custom event
|
|
61
|
+
window.addEventListener('sidebar-toggle', updateSidebarWidth);
|
|
62
|
+
`,
|
|
63
|
+
}}
|
|
64
|
+
/>
|
|
45
65
|
</body>
|
|
46
66
|
</html>
|
|
47
67
|
);
|
package/src/studio/app/page.tsx
CHANGED
|
@@ -9,12 +9,15 @@ import type { Tool } from '@/lib/types';
|
|
|
9
9
|
import { Wrench, RefreshCw, X, Play, AlertCircle } from 'lucide-react';
|
|
10
10
|
|
|
11
11
|
export default function ToolsPage() {
|
|
12
|
-
const { tools, setTools, loading, setLoading, connection, setConnection, jwtToken, apiKey } = useStudioStore();
|
|
12
|
+
const { tools, setTools, loading, setLoading, connection, setConnection, jwtToken, apiKey, oauthState } = useStudioStore();
|
|
13
13
|
const [searchQuery, setSearchQuery] = useState('');
|
|
14
14
|
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
|
|
15
15
|
const [toolArgs, setToolArgs] = useState<Record<string, any>>({});
|
|
16
16
|
const [toolResult, setToolResult] = useState<any>(null);
|
|
17
17
|
const [executingTool, setExecutingTool] = useState(false);
|
|
18
|
+
|
|
19
|
+
// Get effective token - check both jwtToken and OAuth token
|
|
20
|
+
const effectiveToken = jwtToken || oauthState?.currentToken;
|
|
18
21
|
|
|
19
22
|
// Initialize MCP, load tools and check connection on mount
|
|
20
23
|
useEffect(() => {
|
|
@@ -64,8 +67,8 @@ export default function ToolsPage() {
|
|
|
64
67
|
setToolResult(null);
|
|
65
68
|
|
|
66
69
|
try {
|
|
67
|
-
// Pass JWT token and API key if available
|
|
68
|
-
const result = await api.callTool(selectedTool.name, toolArgs,
|
|
70
|
+
// Pass JWT token and API key if available (check both jwtToken and OAuth token)
|
|
71
|
+
const result = await api.callTool(selectedTool.name, toolArgs, effectiveToken || undefined, apiKey || undefined);
|
|
69
72
|
setToolResult(result);
|
|
70
73
|
|
|
71
74
|
// Extract JWT token from ANY tool response (not just 'login')
|
|
@@ -100,64 +103,67 @@ export default function ToolsPage() {
|
|
|
100
103
|
|
|
101
104
|
return (
|
|
102
105
|
<>
|
|
103
|
-
<div className="
|
|
104
|
-
{/* Header */}
|
|
105
|
-
<div className="
|
|
106
|
-
<div className="flex items-center
|
|
107
|
-
<div className="flex items-center
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
<h1 className="text-3xl font-bold text-foreground">Tools</h1>
|
|
113
|
-
<p className="text-muted-foreground mt-1">
|
|
114
|
-
Browse and execute MCP tools
|
|
115
|
-
</p>
|
|
116
|
-
</div>
|
|
106
|
+
<div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
|
|
107
|
+
{/* Sticky Header */}
|
|
108
|
+
<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">
|
|
109
|
+
<div className="flex items-center gap-3">
|
|
110
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-amber-500 flex items-center justify-center shadow-md">
|
|
111
|
+
<Wrench className="w-5 h-5 text-white" strokeWidth={2.5} />
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<h1 className="text-lg font-bold text-foreground">Tools</h1>
|
|
117
115
|
</div>
|
|
118
|
-
<button onClick={loadTools} className="btn btn-primary gap-2">
|
|
119
|
-
<RefreshCw className="w-4 h-4" />
|
|
120
|
-
Refresh
|
|
121
|
-
</button>
|
|
122
116
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
placeholder="Search tools..."
|
|
128
|
-
value={searchQuery}
|
|
129
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
130
|
-
className="input"
|
|
131
|
-
/>
|
|
117
|
+
<button onClick={loadTools} className="btn btn-primary text-sm px-4 py-2 gap-2">
|
|
118
|
+
<RefreshCw className="w-4 h-4" />
|
|
119
|
+
Refresh
|
|
120
|
+
</button>
|
|
132
121
|
</div>
|
|
133
122
|
|
|
134
|
-
{/*
|
|
135
|
-
|
|
136
|
-
<div className="
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
123
|
+
{/* Content - ONLY this scrolls */}
|
|
124
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
|
125
|
+
<div className="max-w-7xl mx-auto px-6 py-6">
|
|
126
|
+
{/* Search */}
|
|
127
|
+
<div className="mb-6">
|
|
128
|
+
<input
|
|
129
|
+
type="text"
|
|
130
|
+
placeholder="Search tools..."
|
|
131
|
+
value={searchQuery}
|
|
132
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
133
|
+
className="input w-full"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Tools Grid */}
|
|
138
|
+
{loading.tools ? (
|
|
139
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
140
|
+
{[1, 2, 3].map((i) => (
|
|
141
|
+
<div key={i} className="card skeleton h-64"></div>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
) : filteredTools.length === 0 ? (
|
|
145
|
+
<div className="empty-state">
|
|
146
|
+
<AlertCircle className="empty-state-icon" />
|
|
147
|
+
<p className="empty-state-title">
|
|
148
|
+
{searchQuery ? 'No tools found matching your search' : 'No tools available'}
|
|
149
|
+
</p>
|
|
150
|
+
<p className="empty-state-description">
|
|
151
|
+
{searchQuery ? 'Try a different search term' : 'No MCP tools have been registered'}
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
) : (
|
|
155
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
156
|
+
{filteredTools.map((tool) => (
|
|
157
|
+
<ToolCard key={tool.name} tool={tool} onExecute={handleExecuteTool} />
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
156
161
|
</div>
|
|
157
|
-
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
158
164
|
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
{/* Tool Executor Modal */}
|
|
166
|
+
{selectedTool && (
|
|
161
167
|
<div
|
|
162
168
|
className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
|
|
163
169
|
style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
|
|
@@ -331,7 +337,6 @@ export default function ToolsPage() {
|
|
|
331
337
|
</div>
|
|
332
338
|
</div>
|
|
333
339
|
)}
|
|
334
|
-
</div>
|
|
335
340
|
</>
|
|
336
341
|
);
|
|
337
342
|
}
|
|
@@ -60,20 +60,23 @@ export default function PingPage() {
|
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
return (
|
|
63
|
-
<div className="
|
|
64
|
-
{/* Header */}
|
|
65
|
-
<div className="
|
|
66
|
-
<div className="flex items-center gap-3
|
|
67
|
-
<div className="w-
|
|
68
|
-
<Wifi className="w-
|
|
63
|
+
<div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
|
|
64
|
+
{/* Sticky Header */}
|
|
65
|
+
<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">
|
|
66
|
+
<div className="flex items-center gap-3">
|
|
67
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center shadow-md">
|
|
68
|
+
<Wifi className="w-5 h-5 text-white" strokeWidth={2.5} />
|
|
69
69
|
</div>
|
|
70
70
|
<div>
|
|
71
|
-
<h1 className="text-
|
|
72
|
-
<p className="text-muted-foreground mt-1">Test server connectivity and latency</p>
|
|
71
|
+
<h1 className="text-lg font-bold text-foreground">Ping</h1>
|
|
73
72
|
</div>
|
|
74
73
|
</div>
|
|
75
74
|
</div>
|
|
76
75
|
|
|
76
|
+
{/* Content - ONLY this scrolls */}
|
|
77
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
|
78
|
+
<div className="max-w-4xl mx-auto px-6 py-6">
|
|
79
|
+
|
|
77
80
|
{/* Main Ping Card */}
|
|
78
81
|
<div className="card card-hover p-8 mb-8 text-center bg-gradient-to-br from-card via-card to-muted/20">
|
|
79
82
|
<button
|
|
@@ -199,6 +202,8 @@ export default function PingPage() {
|
|
|
199
202
|
</p>
|
|
200
203
|
</div>
|
|
201
204
|
)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
202
207
|
</div>
|
|
203
208
|
);
|
|
204
209
|
}
|
|
@@ -53,85 +53,87 @@ export default function PromptsPage() {
|
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
<div className="
|
|
57
|
-
{/* Header */}
|
|
58
|
-
<div className="
|
|
59
|
-
<div className="flex items-center
|
|
60
|
-
<div className="flex items-center
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
<h1 className="text-3xl font-bold text-foreground">Prompts</h1>
|
|
66
|
-
<p className="text-muted-foreground mt-1">Browse and execute MCP prompts</p>
|
|
67
|
-
</div>
|
|
56
|
+
<div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
|
|
57
|
+
{/* Sticky Header */}
|
|
58
|
+
<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">
|
|
59
|
+
<div className="flex items-center gap-3">
|
|
60
|
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center shadow-md">
|
|
61
|
+
<FileText className="w-5 h-5 text-white" strokeWidth={2.5} />
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<h1 className="text-lg font-bold text-foreground">Prompts</h1>
|
|
68
65
|
</div>
|
|
69
|
-
<button onClick={loadPrompts} className="btn btn-primary gap-2">
|
|
70
|
-
<RefreshCw className="w-4 h-4" />
|
|
71
|
-
Refresh
|
|
72
|
-
</button>
|
|
73
66
|
</div>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
value={searchQuery}
|
|
79
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
80
|
-
className="input"
|
|
81
|
-
/>
|
|
67
|
+
<button onClick={loadPrompts} className="btn btn-primary text-sm px-4 py-2 gap-2">
|
|
68
|
+
<RefreshCw className="w-4 h-4" />
|
|
69
|
+
Refresh
|
|
70
|
+
</button>
|
|
82
71
|
</div>
|
|
83
72
|
|
|
84
|
-
{/*
|
|
85
|
-
|
|
86
|
-
<div className="
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
onClick={() => {
|
|
108
|
-
setSelectedPrompt(prompt);
|
|
109
|
-
setPromptArgs({});
|
|
110
|
-
setPromptResult(null);
|
|
111
|
-
}}
|
|
112
|
-
>
|
|
113
|
-
<div className="flex items-center gap-3 mb-3">
|
|
114
|
-
<div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
|
|
115
|
-
<FileText className="w-5 h-5 text-blue-500" />
|
|
116
|
-
</div>
|
|
117
|
-
<h3 className="font-semibold text-foreground">{prompt.name}</h3>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
|
|
121
|
-
{prompt.description || 'No description'}
|
|
73
|
+
{/* Content - ONLY this scrolls */}
|
|
74
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden">
|
|
75
|
+
<div className="max-w-7xl mx-auto px-6 py-6">
|
|
76
|
+
<input
|
|
77
|
+
type="text"
|
|
78
|
+
placeholder="Search prompts..."
|
|
79
|
+
value={searchQuery}
|
|
80
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
81
|
+
className="input mb-6"
|
|
82
|
+
/>
|
|
83
|
+
|
|
84
|
+
{/* Prompts Grid */}
|
|
85
|
+
{loading.prompts ? (
|
|
86
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
87
|
+
{[1, 2, 3].map((i) => (
|
|
88
|
+
<div key={i} className="card skeleton h-40"></div>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
) : filteredPrompts.length === 0 ? (
|
|
92
|
+
<div className="empty-state">
|
|
93
|
+
<AlertCircle className="empty-state-icon" />
|
|
94
|
+
<p className="empty-state-title">
|
|
95
|
+
{searchQuery ? 'No prompts found' : 'No prompts available'}
|
|
122
96
|
</p>
|
|
97
|
+
<p className="empty-state-description">
|
|
98
|
+
{searchQuery ? 'Try a different search term' : 'No prompts have been registered'}
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
103
|
+
{filteredPrompts.map((prompt) => (
|
|
104
|
+
<div
|
|
105
|
+
key={prompt.name}
|
|
106
|
+
className="card card-hover p-6 cursor-pointer animate-fade-in"
|
|
107
|
+
onClick={() => {
|
|
108
|
+
setSelectedPrompt(prompt);
|
|
109
|
+
setPromptArgs({});
|
|
110
|
+
setPromptResult(null);
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
<div className="flex items-center gap-3 mb-3">
|
|
114
|
+
<div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
|
|
115
|
+
<FileText className="w-5 h-5 text-blue-500" />
|
|
116
|
+
</div>
|
|
117
|
+
<h3 className="font-semibold text-foreground">{prompt.name}</h3>
|
|
118
|
+
</div>
|
|
123
119
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
<p className="text-sm text-muted-foreground line-clamp-2 mb-3">
|
|
121
|
+
{prompt.description || 'No description'}
|
|
122
|
+
</p>
|
|
123
|
+
|
|
124
|
+
{prompt.arguments && prompt.arguments.length > 0 && (
|
|
125
|
+
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
126
|
+
<span className="badge badge-secondary">
|
|
127
|
+
{prompt.arguments.length} argument{prompt.arguments.length !== 1 ? 's' : ''}
|
|
128
|
+
</span>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
129
131
|
</div>
|
|
130
|
-
)}
|
|
132
|
+
))}
|
|
131
133
|
</div>
|
|
132
|
-
)
|
|
134
|
+
)}
|
|
133
135
|
</div>
|
|
134
|
-
|
|
136
|
+
</div>
|
|
135
137
|
|
|
136
138
|
{/* Prompt Executor Modal */}
|
|
137
139
|
{selectedPrompt && (
|