nitrostack 1.0.43 → 1.0.45
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/package.json +1 -1
- package/src/studio/app/page.tsx +156 -156
- package/src/studio/middleware.ts +1 -1
package/package.json
CHANGED
package/src/studio/app/page.tsx
CHANGED
|
@@ -15,7 +15,7 @@ export default function ToolsPage() {
|
|
|
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
|
-
|
|
18
|
+
|
|
19
19
|
// Get effective token - check both jwtToken and OAuth token
|
|
20
20
|
const effectiveToken = jwtToken || oauthState?.currentToken;
|
|
21
21
|
|
|
@@ -164,162 +164,162 @@ export default function ToolsPage() {
|
|
|
164
164
|
|
|
165
165
|
{/* Tool Executor Modal */}
|
|
166
166
|
{selectedTool && (
|
|
167
|
+
<div
|
|
168
|
+
className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
|
|
169
|
+
style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
|
|
170
|
+
onClick={() => setSelectedTool(null)}
|
|
171
|
+
>
|
|
167
172
|
<div
|
|
168
|
-
className="
|
|
169
|
-
|
|
170
|
-
onClick={() => setSelectedTool(null)}
|
|
173
|
+
className="w-[700px] max-h-[90vh] overflow-auto rounded-2xl border border-border p-6 bg-card shadow-2xl animate-scale-in"
|
|
174
|
+
onClick={(e) => e.stopPropagation()}
|
|
171
175
|
>
|
|
172
|
-
<div
|
|
173
|
-
className="
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<div className="flex items-center justify-between mb-4">
|
|
177
|
-
<div className="flex items-center gap-3">
|
|
178
|
-
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
179
|
-
<Wrench className="w-5 h-5 text-primary" />
|
|
180
|
-
</div>
|
|
181
|
-
<h2 className="text-xl font-bold text-foreground">{selectedTool.name}</h2>
|
|
176
|
+
<div className="flex items-center justify-between mb-4">
|
|
177
|
+
<div className="flex items-center gap-3">
|
|
178
|
+
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
|
179
|
+
<Wrench className="w-5 h-5 text-primary" />
|
|
182
180
|
</div>
|
|
183
|
-
<
|
|
184
|
-
onClick={() => setSelectedTool(null)}
|
|
185
|
-
className="btn btn-ghost w-10 h-10 p-0"
|
|
186
|
-
>
|
|
187
|
-
<X className="w-5 h-5" />
|
|
188
|
-
</button>
|
|
181
|
+
<h2 className="text-xl font-bold text-foreground">{selectedTool.name}</h2>
|
|
189
182
|
</div>
|
|
183
|
+
<button
|
|
184
|
+
onClick={() => setSelectedTool(null)}
|
|
185
|
+
className="btn btn-ghost w-10 h-10 p-0"
|
|
186
|
+
>
|
|
187
|
+
<X className="w-5 h-5" />
|
|
188
|
+
</button>
|
|
189
|
+
</div>
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
<p className="text-sm text-muted-foreground mb-6">
|
|
192
|
+
{selectedTool.description || 'No description'}
|
|
193
|
+
</p>
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<input
|
|
235
|
-
type="checkbox"
|
|
236
|
-
className="w-4 h-4"
|
|
237
|
-
checked={toolArgs[key] || false}
|
|
238
|
-
onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.checked })}
|
|
239
|
-
/>
|
|
240
|
-
<span className="text-sm font-medium text-foreground">
|
|
241
|
-
{prop.title || key}
|
|
242
|
-
{isRequired && <span className="text-destructive ml-1">*</span>}
|
|
243
|
-
</span>
|
|
244
|
-
</label>
|
|
245
|
-
{prop.description && (
|
|
246
|
-
<p className="text-xs text-muted-foreground mt-1 ml-6">{prop.description}</p>
|
|
247
|
-
)}
|
|
248
|
-
</div>
|
|
249
|
-
);
|
|
250
|
-
} else {
|
|
251
|
-
// Text/Number field
|
|
252
|
-
return (
|
|
253
|
-
<div key={key} className="mb-4">
|
|
254
|
-
<label className="block text-sm font-medium text-foreground mb-2">
|
|
255
|
-
{prop.title || key}
|
|
256
|
-
{isRequired && <span className="text-destructive ml-1">*</span>}
|
|
257
|
-
</label>
|
|
195
|
+
<form onSubmit={handleSubmitTool}>
|
|
196
|
+
{/* Generate form inputs from schema */}
|
|
197
|
+
{selectedTool.inputSchema?.properties && typeof selectedTool.inputSchema.properties === 'object' && Object.keys(selectedTool.inputSchema.properties).length > 0 ? (
|
|
198
|
+
<>
|
|
199
|
+
{Object.entries(selectedTool.inputSchema.properties).map(([key, prop]: [string, any]) => {
|
|
200
|
+
const isRequired = selectedTool.inputSchema?.required?.includes(key);
|
|
201
|
+
|
|
202
|
+
// Handle different input types
|
|
203
|
+
if (prop.enum) {
|
|
204
|
+
// Enum/Select field
|
|
205
|
+
return (
|
|
206
|
+
<div key={key} className="mb-4">
|
|
207
|
+
<label className="block text-sm font-medium text-foreground mb-2">
|
|
208
|
+
{prop.title || key}
|
|
209
|
+
{isRequired && <span className="text-destructive ml-1">*</span>}
|
|
210
|
+
</label>
|
|
211
|
+
<select
|
|
212
|
+
className="input"
|
|
213
|
+
value={toolArgs[key] || prop.default || ''}
|
|
214
|
+
onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.value })}
|
|
215
|
+
required={isRequired}
|
|
216
|
+
>
|
|
217
|
+
<option value="">Select...</option>
|
|
218
|
+
{prop.enum.map((val: any) => (
|
|
219
|
+
<option key={val} value={val}>
|
|
220
|
+
{val}
|
|
221
|
+
</option>
|
|
222
|
+
))}
|
|
223
|
+
</select>
|
|
224
|
+
{prop.description && (
|
|
225
|
+
<p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
} else if (prop.type === 'boolean') {
|
|
230
|
+
// Checkbox field
|
|
231
|
+
return (
|
|
232
|
+
<div key={key} className="mb-4">
|
|
233
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
258
234
|
<input
|
|
259
|
-
type=
|
|
260
|
-
className="
|
|
261
|
-
|
|
262
|
-
onChange={(e) => {
|
|
263
|
-
const value = prop.type === 'number' || prop.type === 'integer'
|
|
264
|
-
? (e.target.value ? Number(e.target.value) : '')
|
|
265
|
-
: e.target.value;
|
|
266
|
-
setToolArgs({ ...toolArgs, [key]: value });
|
|
267
|
-
}}
|
|
268
|
-
required={isRequired}
|
|
269
|
-
placeholder={prop.description}
|
|
270
|
-
min={prop.minimum}
|
|
271
|
-
max={prop.maximum}
|
|
235
|
+
type="checkbox"
|
|
236
|
+
className="w-4 h-4"
|
|
237
|
+
checked={toolArgs[key] || false}
|
|
238
|
+
onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.checked })}
|
|
272
239
|
/>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
240
|
+
<span className="text-sm font-medium text-foreground">
|
|
241
|
+
{prop.title || key}
|
|
242
|
+
{isRequired && <span className="text-destructive ml-1">*</span>}
|
|
243
|
+
</span>
|
|
244
|
+
</label>
|
|
245
|
+
{prop.description && (
|
|
246
|
+
<p className="text-xs text-muted-foreground mt-1 ml-6">{prop.description}</p>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
} else {
|
|
251
|
+
// Text/Number field
|
|
252
|
+
return (
|
|
253
|
+
<div key={key} className="mb-4">
|
|
254
|
+
<label className="block text-sm font-medium text-foreground mb-2">
|
|
255
|
+
{prop.title || key}
|
|
256
|
+
{isRequired && <span className="text-destructive ml-1">*</span>}
|
|
257
|
+
</label>
|
|
258
|
+
<input
|
|
259
|
+
type={prop.type === 'number' || prop.type === 'integer' ? 'number' : 'text'}
|
|
260
|
+
className="input"
|
|
261
|
+
value={toolArgs[key] || prop.default || ''}
|
|
262
|
+
onChange={(e) => {
|
|
263
|
+
const value = prop.type === 'number' || prop.type === 'integer'
|
|
264
|
+
? (e.target.value ? Number(e.target.value) : '')
|
|
265
|
+
: e.target.value;
|
|
266
|
+
setToolArgs({ ...toolArgs, [key]: value });
|
|
267
|
+
}}
|
|
268
|
+
required={isRequired}
|
|
269
|
+
placeholder={prop.description}
|
|
270
|
+
min={prop.minimum}
|
|
271
|
+
max={prop.maximum}
|
|
272
|
+
/>
|
|
273
|
+
{prop.description && (
|
|
274
|
+
<p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
})}
|
|
280
|
+
</>
|
|
281
|
+
) : (
|
|
282
|
+
<div className="mb-4 p-4 bg-muted/30 rounded-lg border border-border">
|
|
283
|
+
<p className="text-sm text-muted-foreground">No input required</p>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
286
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
287
|
+
<button
|
|
288
|
+
type="submit"
|
|
289
|
+
className="btn btn-primary w-full gap-2"
|
|
290
|
+
disabled={executingTool}
|
|
291
|
+
>
|
|
292
|
+
<Play className="w-4 h-4" />
|
|
293
|
+
{executingTool ? 'Executing...' : 'Execute Tool'}
|
|
294
|
+
</button>
|
|
295
|
+
</form>
|
|
296
|
+
|
|
297
|
+
{/* Result */}
|
|
298
|
+
{toolResult && (
|
|
299
|
+
<div className="mt-6 space-y-4">
|
|
300
|
+
<div>
|
|
301
|
+
<h3 className="font-semibold text-foreground mb-3">Result:</h3>
|
|
302
|
+
<pre className="bg-muted/30 p-4 rounded-lg text-sm overflow-auto max-h-64 text-foreground font-mono border border-border">
|
|
303
|
+
{JSON.stringify(toolResult, null, 2)}
|
|
304
|
+
</pre>
|
|
305
|
+
</div>
|
|
296
306
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
selectedTool._meta?.['ui/template'] ||
|
|
314
|
-
selectedTool._meta?.['openai/outputTemplate'];
|
|
315
|
-
|
|
316
|
-
return widgetUri && toolResult ? (
|
|
317
|
-
<div>
|
|
318
|
-
<h3 className="font-semibold text-foreground mb-3">UI Widget:</h3>
|
|
319
|
-
<div className="border border-border rounded-lg overflow-hidden h-64 bg-background shadow-inner">
|
|
320
|
-
<WidgetRenderer
|
|
321
|
-
uri={widgetUri}
|
|
322
|
-
data={(() => {
|
|
307
|
+
{/* Widget UI Rendering */}
|
|
308
|
+
{(() => {
|
|
309
|
+
// Get widget URI from multiple sources (same as ToolCard)
|
|
310
|
+
const widgetUri =
|
|
311
|
+
selectedTool.widget?.route ||
|
|
312
|
+
selectedTool.outputTemplate ||
|
|
313
|
+
selectedTool._meta?.['ui/template'] ||
|
|
314
|
+
selectedTool._meta?.['openai/outputTemplate'];
|
|
315
|
+
|
|
316
|
+
return widgetUri && toolResult ? (
|
|
317
|
+
<div>
|
|
318
|
+
<h3 className="font-semibold text-foreground mb-3">UI Widget:</h3>
|
|
319
|
+
<div className="border border-border rounded-lg overflow-hidden h-64 bg-background shadow-inner">
|
|
320
|
+
<WidgetRenderer
|
|
321
|
+
uri={widgetUri}
|
|
322
|
+
data={(() => {
|
|
323
323
|
// Try to parse JSON from content[0].text, otherwise use raw result
|
|
324
324
|
if (toolResult.content?.[0]?.text) {
|
|
325
325
|
try {
|
|
@@ -335,17 +335,17 @@ export default function ToolsPage() {
|
|
|
335
335
|
}
|
|
336
336
|
return toolResult;
|
|
337
337
|
})()}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
</div>
|
|
338
|
+
className="w-full h-full"
|
|
339
|
+
/>
|
|
341
340
|
</div>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
341
|
+
</div>
|
|
342
|
+
) : null;
|
|
343
|
+
})()}
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
347
346
|
</div>
|
|
348
|
-
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
349
|
</>
|
|
350
350
|
);
|
|
351
351
|
}
|