nitrostack 1.0.71 → 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.
@@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
4
4
  import { useStudioStore } from '@/lib/store';
5
5
  import { api } from '@/lib/api';
6
6
  import type { Prompt } from '@/lib/types';
7
- import { FileText, RefreshCw, X, Play, AlertCircle } from 'lucide-react';
7
+ import { DocumentTextIcon, ArrowPathIcon, XMarkIcon, PlayIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline';
8
8
 
9
9
  export default function PromptsPage() {
10
10
  const { prompts, setPrompts, loading, setLoading } = useStudioStore();
@@ -54,23 +54,16 @@ export default function PromptsPage() {
54
54
 
55
55
  return (
56
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>
65
- </div>
66
- </div>
57
+ {/* Minimal Professional Header */}
58
+ <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">
59
+ <h1 className="text-lg font-semibold text-foreground">Prompts</h1>
67
60
  <button onClick={loadPrompts} className="btn btn-primary text-sm px-4 py-2 gap-2">
68
- <RefreshCw className="w-4 h-4" />
61
+ <ArrowPathIcon className="h-4 w-4" />
69
62
  Refresh
70
63
  </button>
71
64
  </div>
72
65
 
73
- {/* Content - ONLY this scrolls */}
66
+ {/* Content */}
74
67
  <div className="flex-1 overflow-y-auto overflow-x-hidden">
75
68
  <div className="max-w-7xl mx-auto px-6 py-6">
76
69
  <input
@@ -90,7 +83,7 @@ export default function PromptsPage() {
90
83
  </div>
91
84
  ) : filteredPrompts.length === 0 ? (
92
85
  <div className="empty-state">
93
- <AlertCircle className="empty-state-icon" />
86
+ <ExclamationCircleIcon className="empty-state-icon" />
94
87
  <p className="empty-state-title">
95
88
  {searchQuery ? 'No prompts found' : 'No prompts available'}
96
89
  </p>
@@ -99,35 +92,42 @@ export default function PromptsPage() {
99
92
  </p>
100
93
  </div>
101
94
  ) : (
102
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
95
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
103
96
  {filteredPrompts.map((prompt) => (
104
97
  <div
105
98
  key={prompt.name}
106
- className="card card-hover p-6 cursor-pointer animate-fade-in"
99
+ className="group relative bg-card/50 border border-border/50 rounded-xl p-5 cursor-pointer transition-all duration-200 hover:bg-card hover:border-border hover:shadow-lg animate-fade-in"
107
100
  onClick={() => {
108
101
  setSelectedPrompt(prompt);
109
102
  setPromptArgs({});
110
103
  setPromptResult(null);
111
104
  }}
112
105
  >
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" />
106
+ {/* Gradient hover effect */}
107
+ <div className="absolute inset-0 rounded-xl bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
108
+
109
+ <div className="relative">
110
+ <div className="flex items-center gap-3 mb-3">
111
+ <div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
112
+ <DocumentTextIcon className="w-4 h-4 text-primary" />
113
+ </div>
114
+ <h3 className="text-sm font-medium text-foreground group-hover:text-primary transition-colors truncate">
115
+ {prompt.name}
116
+ </h3>
116
117
  </div>
117
- <h3 className="font-semibold text-foreground">{prompt.name}</h3>
118
- </div>
119
118
 
120
- <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
121
- {prompt.description || 'No description'}
122
- </p>
119
+ <p className="text-xs text-muted-foreground/80 line-clamp-2 mb-3 leading-relaxed">
120
+ {prompt.description || 'No description'}
121
+ </p>
123
122
 
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
- )}
123
+ {prompt.arguments && prompt.arguments.length > 0 && (
124
+ <div className="flex items-center gap-1.5">
125
+ <span className="text-[10px] font-medium px-2 py-0.5 rounded-full bg-muted/50 text-muted-foreground">
126
+ {prompt.arguments.length} arg{prompt.arguments.length !== 1 ? 's' : ''}
127
+ </span>
128
+ </div>
129
+ )}
130
+ </div>
131
131
  </div>
132
132
  ))}
133
133
  </div>
@@ -135,93 +135,111 @@ export default function PromptsPage() {
135
135
  </div>
136
136
  </div>
137
137
 
138
- {/* Prompt Executor Modal */}
138
+ {/* Prompt Executor Side Drawer */}
139
139
  {selectedPrompt && (
140
140
  <div
141
- className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
142
- style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
141
+ className="fixed inset-0 z-50 flex justify-end bg-background/80 backdrop-blur-sm animate-fade-in"
143
142
  onClick={() => setSelectedPrompt(null)}
144
143
  >
145
144
  <div
146
- className="bg-card rounded-2xl p-6 w-[600px] max-h-[80vh] overflow-auto border border-border shadow-2xl animate-scale-in"
145
+ className="relative w-full max-w-2xl h-full bg-card border-l border-border shadow-2xl overflow-hidden animate-slide-in-right flex flex-col"
147
146
  onClick={(e) => e.stopPropagation()}
148
147
  >
149
- <div className="flex items-center justify-between mb-4">
148
+ {/* Header */}
149
+ <div className="flex items-center justify-between px-6 py-4 border-b border-border bg-card">
150
150
  <div className="flex items-center gap-3">
151
151
  <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
152
- <FileText className="w-5 h-5 text-blue-500" />
152
+ <DocumentTextIcon className="w-5 h-5 text-blue-500" />
153
+ </div>
154
+ <div>
155
+ <h2 className="text-lg font-bold text-foreground">{selectedPrompt.name}</h2>
156
+ <p className="text-xs text-muted-foreground">Prompt Executor</p>
153
157
  </div>
154
- <h2 className="text-xl font-bold text-foreground">{selectedPrompt.name}</h2>
155
158
  </div>
156
159
  <button
157
160
  onClick={() => setSelectedPrompt(null)}
158
- className="btn btn-ghost w-10 h-10 p-0"
161
+ className="btn btn-ghost w-8 h-8 p-0 flex items-center justify-center rounded-full hover:bg-muted"
162
+ aria-label="Close"
159
163
  >
160
- <X className="w-5 h-5" />
164
+ <XMarkIcon className="w-5 h-5" />
161
165
  </button>
162
166
  </div>
163
167
 
164
- <p className="text-sm text-muted-foreground mb-6">
165
- {selectedPrompt.description || 'No description'}
166
- </p>
167
-
168
- <form onSubmit={handleExecutePrompt}>
169
- {selectedPrompt.arguments && selectedPrompt.arguments.length > 0 ? (
170
- selectedPrompt.arguments.map((arg) => (
171
- <div key={arg.name} className="mb-4">
172
- <label className="block text-sm font-medium text-foreground mb-2">
173
- {arg.name}
174
- {arg.required && <span className="text-destructive ml-1">*</span>}
175
- </label>
176
- <input
177
- type="text"
178
- className="input"
179
- value={promptArgs[arg.name] || ''}
180
- onChange={(e) =>
181
- setPromptArgs({ ...promptArgs, [arg.name]: e.target.value })
182
- }
183
- required={arg.required}
184
- placeholder={arg.description || `Enter ${arg.name}`}
185
- />
186
- {arg.description && (
187
- <p className="text-xs text-muted-foreground mt-1">{arg.description}</p>
188
- )}
189
- </div>
190
- ))
191
- ) : (
192
- <div className="bg-muted/30 rounded-lg p-4 mb-4">
193
- <p className="text-sm text-muted-foreground">No arguments required</p>
168
+ {/* Scrollable Content */}
169
+ <div className="flex-1 overflow-y-auto bg-muted/5 p-6">
170
+
171
+ <div className="bg-card p-4 rounded-xl border border-border mb-6">
172
+ <p className="text-sm text-foreground leading-relaxed">
173
+ {selectedPrompt.description || 'No description available'}
174
+ </p>
175
+ </div>
176
+
177
+ <form onSubmit={handleExecutePrompt} className="space-y-6">
178
+ <div className="space-y-4">
179
+ {selectedPrompt.arguments && selectedPrompt.arguments.length > 0 ? (
180
+ selectedPrompt.arguments.map((arg) => (
181
+ <div key={arg.name}>
182
+ <label className="block text-sm font-medium text-foreground mb-2">
183
+ {arg.name}
184
+ {arg.required && <span className="text-destructive ml-1">*</span>}
185
+ </label>
186
+ <input
187
+ type="text"
188
+ className="input w-full"
189
+ value={promptArgs[arg.name] || ''}
190
+ onChange={(e) =>
191
+ setPromptArgs({ ...promptArgs, [arg.name]: e.target.value })
192
+ }
193
+ required={arg.required}
194
+ placeholder={arg.description || `Enter ${arg.name}`}
195
+ />
196
+ {arg.description && (
197
+ <p className="text-xs text-muted-foreground mt-1">{arg.description}</p>
198
+ )}
199
+ </div>
200
+ ))
201
+ ) : (
202
+ <div className="p-4 bg-muted/30 rounded-lg border border-border border-dashed text-center">
203
+ <p className="text-sm text-muted-foreground">No arguments required</p>
204
+ </div>
205
+ )}
194
206
  </div>
195
- )}
196
207
 
197
- <button type="submit" className="btn btn-primary w-full gap-2" disabled={executing}>
198
- <Play className="w-4 h-4" />
199
- {executing ? 'Executing...' : 'Execute Prompt'}
200
- </button>
201
- </form>
202
-
203
- {promptResult && (
204
- <div className="mt-6">
205
- <h3 className="font-semibold text-foreground mb-3">Messages:</h3>
206
- <div className="space-y-3">
207
- {promptResult.messages?.map((msg: any, idx: number) => {
208
- // Extract text from content (can be string or object with type/text)
209
- const contentText = typeof msg.content === 'string'
210
- ? msg.content
211
- : msg.content?.text || JSON.stringify(msg.content);
212
-
213
- return (
214
- <div key={idx} className="bg-muted/30 border border-border p-4 rounded-lg">
215
- <div className="text-xs text-muted-foreground mb-2 uppercase font-semibold">
216
- {msg.role}
208
+ <div className="sticky bottom-0 -mx-6 -mb-6 p-6 bg-card border-t border-border mt-auto">
209
+ <button type="submit" className="btn btn-primary w-full gap-2 py-3" disabled={executing}>
210
+ <PlayIcon className="w-4 h-4" />
211
+ {executing ? 'Executing...' : 'Execute Prompt'}
212
+ </button>
213
+ </div>
214
+ </form>
215
+
216
+ {/* Result */}
217
+ {promptResult && (
218
+ <div className="mt-8 pt-8 border-t border-border animate-fade-in">
219
+ <div className="flex items-center justify-between mb-3">
220
+ <h3 className="font-semibold text-foreground">Messages</h3>
221
+ <div className="badge badge-success text-xs">Generated</div>
222
+ </div>
223
+ <div className="space-y-3">
224
+ {promptResult.messages?.map((msg: any, idx: number) => {
225
+ // Extract text from content (can be string or object with type/text)
226
+ const contentText = typeof msg.content === 'string'
227
+ ? msg.content
228
+ : msg.content?.text || JSON.stringify(msg.content);
229
+
230
+ return (
231
+ <div key={idx} className="bg-card border border-border p-4 rounded-xl shadow-sm">
232
+ <div className="text-xs text-muted-foreground mb-2 uppercase font-bold tracking-wider opacity-70">
233
+ {msg.role}
234
+ </div>
235
+ <div className="text-sm whitespace-pre-wrap text-foreground font-mono leading-relaxed">{contentText}</div>
217
236
  </div>
218
- <div className="text-sm whitespace-pre-wrap text-foreground">{contentText}</div>
219
- </div>
220
- );
221
- })}
237
+ );
238
+ })}
239
+ </div>
222
240
  </div>
223
- </div>
224
- )}
241
+ )}
242
+ </div>
225
243
  </div>
226
244
  </div>
227
245
  )}
@@ -5,7 +5,7 @@ import { useStudioStore } from '@/lib/store';
5
5
  import { api } from '@/lib/api';
6
6
  import { WidgetRenderer } from '@/components/WidgetRenderer';
7
7
  import type { Resource } from '@/lib/types';
8
- import { Package, RefreshCw, Palette, Maximize2, Sparkles, AlertCircle } from 'lucide-react';
8
+ import { CubeIcon, ArrowPathIcon, PaintBrushIcon, ArrowsPointingOutIcon, SparklesIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline';
9
9
 
10
10
  interface WidgetExample {
11
11
  name: string;
@@ -52,13 +52,13 @@ export default function ResourcesPage() {
52
52
  const data = await api.getWidgetExamples();
53
53
  console.log('📦 Widget examples response:', data);
54
54
  console.log('📦 Number of widgets loaded:', data.widgets?.length || 0);
55
-
55
+
56
56
  if (data.widgets && data.widgets.length > 0) {
57
57
  console.log('📦 First widget:', data.widgets[0]);
58
58
  } else {
59
59
  console.warn('⚠️ No widgets returned from API. Check MCP server logs.');
60
60
  }
61
-
61
+
62
62
  setWidgets(data.widgets || []);
63
63
  } catch (error) {
64
64
  console.error('❌ Failed to load widget examples:', error);
@@ -68,9 +68,9 @@ export default function ResourcesPage() {
68
68
  };
69
69
 
70
70
  const isUIWidget = (resource: Resource) => {
71
- return resource.mimeType === 'text/html' ||
72
- resource.uri.startsWith('widget://') ||
73
- resource._meta?.['ui/widget'] === true;
71
+ return resource.mimeType === 'text/html' ||
72
+ resource.uri.startsWith('widget://') ||
73
+ resource._meta?.['ui/widget'] === true;
74
74
  };
75
75
 
76
76
  const filteredResources = resources.filter(
@@ -97,9 +97,9 @@ export default function ResourcesPage() {
97
97
  const handleWidgetEnlarge = useCallback((e: React.MouseEvent, widget: WidgetMetadata) => {
98
98
  e.preventDefault();
99
99
  e.stopPropagation();
100
-
100
+
101
101
  const exampleIndex = getSelectedExample(widget.uri);
102
-
102
+
103
103
  setTimeout(() => {
104
104
  openEnlargeModal('resource', {
105
105
  uri: widget.uri,
@@ -112,18 +112,11 @@ export default function ResourcesPage() {
112
112
 
113
113
  return (
114
114
  <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
115
- {/* Sticky Header */}
116
- <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">
117
- <div className="flex items-center gap-3">
118
- <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center shadow-md">
119
- <Package className="w-5 h-5 text-white" strokeWidth={2.5} />
120
- </div>
121
- <div>
122
- <h1 className="text-lg font-bold text-foreground">Resources</h1>
123
- </div>
124
- </div>
115
+ {/* Minimal Professional Header */}
116
+ <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">
117
+ <h1 className="text-lg font-semibold text-foreground">Resources</h1>
125
118
  <button onClick={loadResources} className="btn btn-primary text-sm px-4 py-2 gap-2">
126
- <RefreshCw className="w-4 h-4" />
119
+ <ArrowPathIcon className="h-4 w-4" />
127
120
  Refresh
128
121
  </button>
129
122
  </div>
@@ -142,114 +135,115 @@ export default function ResourcesPage() {
142
135
  {/* UI Widgets Section */}
143
136
  {(loadingWidgets || widgets.length > 0) && (
144
137
  <div className="mb-12">
145
- <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
146
- <Palette className="w-6 h-6 text-primary" />
147
- UI Widgets {widgets.length > 0 && `(${filteredWidgets.length})`}
148
- </h2>
149
-
150
- {loadingWidgets ? (
151
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
152
- {[1, 2].map((i) => (
153
- <div key={i} className="card skeleton h-96"></div>
154
- ))}
155
- </div>
156
- ) : filteredWidgets.length === 0 ? (
157
- <div className="card p-8 text-center">
158
- <AlertCircle className="w-12 h-12 text-muted-foreground mx-auto mb-3" />
159
- <p className="text-muted-foreground">
160
- {searchQuery ? 'No widgets match your search' : 'No UI widgets configured'}
161
- </p>
162
- </div>
163
- ) : (
164
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
165
- {filteredWidgets.map((widget) => (
166
- <div key={widget.uri} className="card card-hover p-6 animate-fade-in">
167
- <div className="flex items-center gap-3 mb-4">
168
- <div className="w-10 h-10 rounded-lg bg-purple-500/10 flex items-center justify-center">
169
- <Palette className="w-5 h-5 text-purple-500" />
170
- </div>
171
- <div className="flex-1">
172
- <h3 className="font-semibold text-foreground">{widget.name}</h3>
173
- <span className="badge badge-success text-xs mt-1">
174
- {widget.examples.length} example{widget.examples.length !== 1 ? 's' : ''}
175
- </span>
176
- </div>
177
- </div>
138
+ <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
139
+ <PaintBrushIcon className="w-6 h-6 text-primary" />
140
+ UI Widgets {widgets.length > 0 && `(${filteredWidgets.length})`}
141
+ </h2>
178
142
 
179
- <p className="text-sm text-muted-foreground mb-3 line-clamp-2">
180
- {widget.description}
143
+ {loadingWidgets ? (
144
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
145
+ {[1, 2].map((i) => (
146
+ <div key={i} className="card skeleton h-96"></div>
147
+ ))}
148
+ </div>
149
+ ) : filteredWidgets.length === 0 ? (
150
+ <div className="card p-8 text-center">
151
+ <ExclamationCircleIcon className="w-12 h-12 text-muted-foreground mx-auto mb-3" />
152
+ <p className="text-muted-foreground">
153
+ {searchQuery ? 'No widgets match your search' : 'No UI widgets configured'}
181
154
  </p>
155
+ </div>
156
+ ) : (
157
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
158
+ {filteredWidgets.map((widget) => (
159
+ <div key={widget.uri} className="card card-hover p-6 animate-fade-in">
160
+ {/* Clean Header */}
161
+ <div className="flex items-center gap-3 mb-3">
162
+ <PaintBrushIcon className="h-5 w-5 text-secondary flex-shrink-0" />
163
+ <div className="flex-1 min-w-0">
164
+ <h3 className="font-medium text-[15px] text-foreground leading-tight truncate">{widget.name}</h3>
165
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground leading-none mt-1">
166
+ <span>Widget</span>
167
+ <span className="text-muted-foreground/40">•</span>
168
+ <span>{widget.examples.length} example{widget.examples.length !== 1 ? 's' : ''}</span>
169
+ </div>
170
+ </div>
171
+ </div>
182
172
 
183
- <p className="text-xs text-muted-foreground mb-4 font-mono truncate bg-muted/30 px-2 py-1 rounded">
184
- {widget.uri}
185
- </p>
173
+ <p className="text-sm text-muted-foreground mb-3 line-clamp-2">
174
+ {widget.description}
175
+ </p>
186
176
 
187
- {widget.tags && widget.tags.length > 0 && (
188
- <div className="flex flex-wrap gap-2 mb-4">
189
- {widget.tags.map((tag) => (
190
- <span key={tag} className="badge badge-secondary text-xs">
191
- #{tag}
192
- </span>
193
- ))}
194
- </div>
195
- )}
196
-
197
- {/* Widget Preview with selected example */}
198
- {widget.examples.length > 0 && (() => {
199
- const selectedIdx = getSelectedExample(widget.uri);
200
- const selectedExample = widget.examples[selectedIdx];
201
- return (
202
- <div className="relative mb-4 rounded-lg overflow-hidden border border-border bg-muted/20">
203
- <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">
204
- <Sparkles className="w-3 h-3" />
205
- {selectedExample.name}
206
- </div>
207
- <div className="h-64">
208
- <WidgetRenderer
209
- uri={widget.uri}
210
- data={selectedExample.data}
211
- />
177
+ <p className="text-xs text-muted-foreground mb-4 font-mono truncate bg-muted/30 px-2 py-1 rounded">
178
+ {widget.uri}
179
+ </p>
180
+
181
+ {widget.tags && widget.tags.length > 0 && (
182
+ <div className="flex flex-wrap gap-2 mb-4">
183
+ {widget.tags.map((tag) => (
184
+ <span key={tag} className="badge badge-secondary text-xs">
185
+ #{tag}
186
+ </span>
187
+ ))}
212
188
  </div>
213
- </div>
214
- );
215
- })()}
216
-
217
- {/* Example Selector (if more than 1) */}
218
- {widget.examples.length > 1 && (
219
- <select
220
- className="input text-sm mb-4"
221
- value={getSelectedExample(widget.uri)}
222
- onChange={(e) => handleExampleChange(widget.uri, parseInt(e.target.value))}
223
- >
224
- {widget.examples.map((example, idx) => (
225
- <option key={idx} value={idx}>
226
- {example.name}
227
- </option>
228
- ))}
229
- </select>
230
- )}
231
-
232
- <button
233
- onClick={(e) => handleWidgetEnlarge(e, widget)}
234
- className="btn btn-secondary w-full gap-2"
235
- >
236
- <Maximize2 className="w-4 h-4" />
237
- Enlarge
238
- </button>
189
+ )}
190
+
191
+ {/* Widget Preview with selected example */}
192
+ {widget.examples.length > 0 && (() => {
193
+ const selectedIdx = getSelectedExample(widget.uri);
194
+ const selectedExample = widget.examples[selectedIdx];
195
+ return (
196
+ <div className="relative mb-4 rounded-lg overflow-hidden border border-border bg-muted/20">
197
+ <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">
198
+ <SparklesIcon className="w-3 h-3" />
199
+ {selectedExample.name}
200
+ </div>
201
+ <div className="h-64">
202
+ <WidgetRenderer
203
+ uri={widget.uri}
204
+ data={selectedExample.data}
205
+ />
206
+ </div>
207
+ </div>
208
+ );
209
+ })()}
210
+
211
+ {/* Example Selector (if more than 1) */}
212
+ {widget.examples.length > 1 && (
213
+ <select
214
+ className="input text-sm mb-4"
215
+ value={getSelectedExample(widget.uri)}
216
+ onChange={(e) => handleExampleChange(widget.uri, parseInt(e.target.value))}
217
+ >
218
+ {widget.examples.map((example, idx) => (
219
+ <option key={idx} value={idx}>
220
+ {example.name}
221
+ </option>
222
+ ))}
223
+ </select>
224
+ )}
225
+
226
+ <button
227
+ onClick={(e) => handleWidgetEnlarge(e, widget)}
228
+ className="btn btn-secondary w-full gap-2"
229
+ >
230
+ <ArrowsPointingOutIcon className="w-4 h-4" />
231
+ Enlarge
232
+ </button>
233
+ </div>
234
+ ))}
239
235
  </div>
240
- ))}
241
- </div>
242
- )}
236
+ )}
243
237
  </div>
244
238
  )}
245
239
 
246
240
  {/* MCP Resources Section */}
247
241
  <div>
248
242
  <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
249
- <Package className="w-6 h-6 text-primary" />
243
+ <CubeIcon className="w-6 h-6 text-primary" />
250
244
  MCP Resources {resources.length > 0 && `(${filteredResources.length})`}
251
245
  </h2>
252
-
246
+
253
247
  {/* Resources Grid */}
254
248
  {loading.resources ? (
255
249
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@@ -259,7 +253,7 @@ export default function ResourcesPage() {
259
253
  </div>
260
254
  ) : filteredResources.length === 0 ? (
261
255
  <div className="empty-state">
262
- <Package className="empty-state-icon" />
256
+ <CubeIcon className="empty-state-icon" />
263
257
  <p className="empty-state-title">
264
258
  {searchQuery ? 'No resources found' : 'No resources available'}
265
259
  </p>
@@ -271,16 +265,13 @@ export default function ResourcesPage() {
271
265
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
272
266
  {filteredResources.map((resource) => (
273
267
  <div key={resource.uri} className="card card-hover p-6 animate-fade-in">
274
- <div className="flex items-start justify-between mb-4">
275
- <div className="flex items-center gap-3">
276
- <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
277
- <Package className="w-5 h-5 text-blue-500" />
278
- </div>
279
- <div>
280
- <h3 className="font-semibold text-foreground">{resource.name}</h3>
281
- <span className="badge badge-primary text-xs mt-1">
282
- Resource
283
- </span>
268
+ {/* Clean Header */}
269
+ <div className="flex items-center gap-3 mb-3">
270
+ <CubeIcon className="h-5 w-5 text-primary flex-shrink-0" />
271
+ <div className="flex-1 min-w-0">
272
+ <h3 className="font-medium text-[15px] text-foreground leading-tight truncate">{resource.name}</h3>
273
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground leading-none mt-1">
274
+ <span>Resource</span>
284
275
  </div>
285
276
  </div>
286
277
  </div>