nitrostack 1.0.16 → 1.0.18
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/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/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 +151 -35
- package/src/studio/app/auth/callback/page.tsx +17 -2
- package/src/studio/app/chat/page.tsx +86 -31
- package/src/studio/app/layout.tsx +1 -1
- 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 +3 -3
- package/src/studio/components/ToolCard.tsx +27 -9
- package/src/studio/components/WidgetRenderer.tsx +4 -2
- package/src/studio/lib/llm-service.ts +23 -1
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Copy, Check } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
interface MarkdownRendererProps {
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MarkdownRenderer({ content }: MarkdownRendererProps) {
|
|
11
|
+
const [copied, setCopied] = useState(false);
|
|
12
|
+
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
|
13
|
+
|
|
14
|
+
const handleCopy = async () => {
|
|
15
|
+
try {
|
|
16
|
+
await navigator.clipboard.writeText(content);
|
|
17
|
+
setCopied(true);
|
|
18
|
+
setTimeout(() => setCopied(false), 2000);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Failed to copy:', error);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleCopyCode = async (code: string, blockId: string) => {
|
|
25
|
+
try {
|
|
26
|
+
await navigator.clipboard.writeText(code);
|
|
27
|
+
setCopiedCode(blockId);
|
|
28
|
+
setTimeout(() => setCopiedCode(null), 2000);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Failed to copy code:', error);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Simple syntax highlighting for common languages
|
|
35
|
+
const highlightCode = (code: string, language: string) => {
|
|
36
|
+
let highlighted = escapeHtml(code);
|
|
37
|
+
|
|
38
|
+
// Keywords for different languages
|
|
39
|
+
const keywords: Record<string, string[]> = {
|
|
40
|
+
javascript: ['function', 'const', 'let', 'var', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'new', 'this', 'try', 'catch'],
|
|
41
|
+
typescript: ['function', 'const', 'let', 'var', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'interface', 'type', 'enum', 'new', 'this', 'try', 'catch'],
|
|
42
|
+
python: ['def', 'class', 'return', 'if', 'else', 'elif', 'for', 'while', 'import', 'from', 'try', 'except', 'with', 'as', 'pass', 'break', 'continue', 'lambda', 'yield'],
|
|
43
|
+
java: ['public', 'private', 'protected', 'class', 'interface', 'void', 'return', 'if', 'else', 'for', 'while', 'new', 'this', 'static', 'final', 'try', 'catch'],
|
|
44
|
+
go: ['func', 'var', 'const', 'return', 'if', 'else', 'for', 'range', 'struct', 'interface', 'type', 'package', 'import', 'go', 'defer', 'chan'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const lang = language.toLowerCase();
|
|
48
|
+
const langKeywords = keywords[lang] || keywords['javascript'];
|
|
49
|
+
|
|
50
|
+
// Comments
|
|
51
|
+
highlighted = highlighted.replace(/(\/\/.*$|\/\*[\s\S]*?\*\/)/gm, '<span class="syntax-comment">$1</span>');
|
|
52
|
+
highlighted = highlighted.replace(/(#.*$)/gm, '<span class="syntax-comment">$1</span>');
|
|
53
|
+
|
|
54
|
+
// Strings
|
|
55
|
+
highlighted = highlighted.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, '<span class="syntax-string">$1</span>');
|
|
56
|
+
|
|
57
|
+
// Numbers
|
|
58
|
+
highlighted = highlighted.replace(/\b(\d+\.?\d*)\b/g, '<span class="syntax-number">$1</span>');
|
|
59
|
+
|
|
60
|
+
// Keywords
|
|
61
|
+
langKeywords.forEach(keyword => {
|
|
62
|
+
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
|
|
63
|
+
highlighted = highlighted.replace(regex, `<span class="syntax-keyword">${keyword}</span>`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Function calls
|
|
67
|
+
highlighted = highlighted.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g, '<span class="syntax-function">$1</span>(');
|
|
68
|
+
|
|
69
|
+
return highlighted;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Simple markdown rendering using regex (basic implementation)
|
|
73
|
+
const renderMarkdown = (text: string) => {
|
|
74
|
+
let html = text;
|
|
75
|
+
let codeBlockCounter = 0;
|
|
76
|
+
|
|
77
|
+
// Code blocks (```language\ncode\n```)
|
|
78
|
+
html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
|
|
79
|
+
const blockId = `code-block-${codeBlockCounter++}`;
|
|
80
|
+
const language = lang || 'text';
|
|
81
|
+
const trimmedCode = code.trim();
|
|
82
|
+
const highlightedCode = language !== 'text' ? highlightCode(trimmedCode, language) : escapeHtml(trimmedCode);
|
|
83
|
+
|
|
84
|
+
return `<div class="code-block-wrapper">
|
|
85
|
+
<div class="code-block-header">
|
|
86
|
+
<span class="code-language">${language}</span>
|
|
87
|
+
<button class="copy-code-btn" data-code="${escapeHtml(trimmedCode)}" data-block-id="${blockId}" onclick="window.copyCodeBlock('${blockId}', \`${escapeHtml(trimmedCode).replace(/`/g, '\\`')}\`)">
|
|
88
|
+
<svg class="copy-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
89
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
90
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
91
|
+
</svg>
|
|
92
|
+
<span class="copy-text">Copy code</span>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
<pre class="code-block"><code class="language-${language}">${highlightedCode}</code></pre>
|
|
96
|
+
</div>`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Inline code (`code`)
|
|
100
|
+
html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
|
|
101
|
+
|
|
102
|
+
// Bold (**text** or __text__)
|
|
103
|
+
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
104
|
+
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
|
105
|
+
|
|
106
|
+
// Italic (*text* or _text_)
|
|
107
|
+
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
108
|
+
html = html.replace(/_(.+?)_/g, '<em>$1</em>');
|
|
109
|
+
|
|
110
|
+
// Headers (# text)
|
|
111
|
+
html = html.replace(/^### (.+)$/gm, '<h3 class="md-h3">$1</h3>');
|
|
112
|
+
html = html.replace(/^## (.+)$/gm, '<h2 class="md-h2">$1</h2>');
|
|
113
|
+
html = html.replace(/^# (.+)$/gm, '<h1 class="md-h1">$1</h1>');
|
|
114
|
+
|
|
115
|
+
// Links ([text](url))
|
|
116
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" class="md-link" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
117
|
+
|
|
118
|
+
// Unordered lists (- item or * item)
|
|
119
|
+
html = html.replace(/^[\*\-] (.+)$/gm, '<li class="md-li">$1</li>');
|
|
120
|
+
html = html.replace(/(<li class="md-li">.*<\/li>\n?)+/g, '<ul class="md-ul">$&</ul>');
|
|
121
|
+
|
|
122
|
+
// Ordered lists (1. item)
|
|
123
|
+
html = html.replace(/^\d+\. (.+)$/gm, '<li class="md-li">$1</li>');
|
|
124
|
+
|
|
125
|
+
// Blockquotes (> text)
|
|
126
|
+
html = html.replace(/^> (.+)$/gm, '<blockquote class="md-blockquote">$1</blockquote>');
|
|
127
|
+
|
|
128
|
+
// Horizontal rule (---)
|
|
129
|
+
html = html.replace(/^---$/gm, '<hr class="md-hr" />');
|
|
130
|
+
|
|
131
|
+
// Line breaks
|
|
132
|
+
html = html.replace(/\n/g, '<br />');
|
|
133
|
+
|
|
134
|
+
return html;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const escapeHtml = (text: string) => {
|
|
138
|
+
const div = document.createElement('div');
|
|
139
|
+
div.textContent = text;
|
|
140
|
+
return div.innerHTML;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Check if content has markdown syntax
|
|
144
|
+
const hasMarkdown = (text: string) => {
|
|
145
|
+
const markdownPatterns = [
|
|
146
|
+
/```[\s\S]*?```/, // Code blocks
|
|
147
|
+
/`[^`]+`/, // Inline code
|
|
148
|
+
/\*\*[^*]+\*\*/, // Bold
|
|
149
|
+
/__[^_]+__/, // Bold
|
|
150
|
+
/\*[^*]+\*/, // Italic
|
|
151
|
+
/_[^_]+_/, // Italic
|
|
152
|
+
/^#{1,6} .+$/m, // Headers
|
|
153
|
+
/\[.+\]\(.+\)/, // Links
|
|
154
|
+
/^[\*\-] .+$/m, // Unordered lists
|
|
155
|
+
/^\d+\. .+$/m, // Ordered lists
|
|
156
|
+
/^> .+$/m, // Blockquotes
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
return markdownPatterns.some(pattern => pattern.test(text));
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const isMarkdown = hasMarkdown(content);
|
|
163
|
+
|
|
164
|
+
if (!isMarkdown) {
|
|
165
|
+
// No markdown detected, render as plain text
|
|
166
|
+
return <div className="whitespace-pre-wrap">{content}</div>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Set up global copy function for code blocks
|
|
170
|
+
if (typeof window !== 'undefined') {
|
|
171
|
+
(window as any).copyCodeBlock = async (blockId: string, code: string) => {
|
|
172
|
+
try {
|
|
173
|
+
await navigator.clipboard.writeText(code);
|
|
174
|
+
setCopiedCode(blockId);
|
|
175
|
+
setTimeout(() => setCopiedCode(null), 2000);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('Failed to copy code:', error);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="relative markdown-container">
|
|
184
|
+
{/* Copy All Button */}
|
|
185
|
+
<button
|
|
186
|
+
onClick={handleCopy}
|
|
187
|
+
className="absolute top-3 right-3 z-10 px-3 py-1.5 rounded-lg bg-slate-700/80 hover:bg-slate-700 border border-slate-600/50 transition-all text-xs font-medium flex items-center gap-2"
|
|
188
|
+
title={copied ? 'Copied!' : 'Copy all content'}
|
|
189
|
+
>
|
|
190
|
+
{copied ? (
|
|
191
|
+
<>
|
|
192
|
+
<Check className="w-3.5 h-3.5 text-green-400" />
|
|
193
|
+
<span className="text-green-400">Copied!</span>
|
|
194
|
+
</>
|
|
195
|
+
) : (
|
|
196
|
+
<>
|
|
197
|
+
<Copy className="w-3.5 h-3.5 text-slate-300" />
|
|
198
|
+
<span className="text-slate-300">Copy all</span>
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
</button>
|
|
202
|
+
|
|
203
|
+
{/* Markdown Content */}
|
|
204
|
+
<div
|
|
205
|
+
className="markdown-content"
|
|
206
|
+
dangerouslySetInnerHTML={{ __html: renderMarkdown(content) }}
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
<style jsx>{`
|
|
210
|
+
.markdown-container {
|
|
211
|
+
position: relative;
|
|
212
|
+
padding: 1.25rem;
|
|
213
|
+
background: linear-gradient(to bottom, rgb(30, 41, 59), rgb(15, 23, 42));
|
|
214
|
+
border: 1px solid rgb(51, 65, 85);
|
|
215
|
+
border-radius: 0.75rem;
|
|
216
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
:global(.markdown-content) {
|
|
220
|
+
font-size: 0.875rem;
|
|
221
|
+
line-height: 1.625;
|
|
222
|
+
color: rgb(226, 232, 240);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
:global(.code-block-wrapper) {
|
|
226
|
+
position: relative;
|
|
227
|
+
margin: 1.5rem 0;
|
|
228
|
+
border-radius: 0.5rem;
|
|
229
|
+
overflow: hidden;
|
|
230
|
+
background: rgb(15, 23, 42);
|
|
231
|
+
border: 1px solid rgb(51, 65, 85);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
:global(.code-block-header) {
|
|
235
|
+
display: flex;
|
|
236
|
+
align-items: center;
|
|
237
|
+
justify-content: space-between;
|
|
238
|
+
padding: 0.5rem 1rem;
|
|
239
|
+
background: rgb(30, 41, 59);
|
|
240
|
+
border-bottom: 1px solid rgb(51, 65, 85);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
:global(.code-language) {
|
|
244
|
+
font-size: 0.75rem;
|
|
245
|
+
font-weight: 500;
|
|
246
|
+
color: rgb(148, 163, 184);
|
|
247
|
+
text-transform: lowercase;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
:global(.copy-code-btn) {
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 0.375rem;
|
|
254
|
+
padding: 0.25rem 0.625rem;
|
|
255
|
+
font-size: 0.75rem;
|
|
256
|
+
color: rgb(203, 213, 225);
|
|
257
|
+
background: transparent;
|
|
258
|
+
border: none;
|
|
259
|
+
border-radius: 0.375rem;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
transition: all 0.2s;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
:global(.copy-code-btn:hover) {
|
|
265
|
+
background: rgb(51, 65, 85);
|
|
266
|
+
color: rgb(226, 232, 240);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
:global(.copy-icon) {
|
|
270
|
+
width: 14px;
|
|
271
|
+
height: 14px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
:global(.markdown-content .md-h1) {
|
|
275
|
+
font-size: 1.5rem;
|
|
276
|
+
font-weight: 700;
|
|
277
|
+
margin-top: 1.5rem;
|
|
278
|
+
margin-bottom: 0.75rem;
|
|
279
|
+
color: rgb(241, 245, 249);
|
|
280
|
+
border-bottom: 1px solid rgb(51, 65, 85);
|
|
281
|
+
padding-bottom: 0.5rem;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
:global(.markdown-content .md-h2) {
|
|
285
|
+
font-size: 1.25rem;
|
|
286
|
+
font-weight: 700;
|
|
287
|
+
margin-top: 1.25rem;
|
|
288
|
+
margin-bottom: 0.5rem;
|
|
289
|
+
color: rgb(226, 232, 240);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
:global(.markdown-content .md-h3) {
|
|
293
|
+
font-size: 1.125rem;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
margin-top: 1rem;
|
|
296
|
+
margin-bottom: 0.5rem;
|
|
297
|
+
color: rgb(203, 213, 225);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
:global(.markdown-content .code-block) {
|
|
301
|
+
background: rgb(15, 23, 42);
|
|
302
|
+
padding: 1rem;
|
|
303
|
+
margin: 0;
|
|
304
|
+
overflow-x: auto;
|
|
305
|
+
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
|
306
|
+
font-size: 0.8125rem;
|
|
307
|
+
line-height: 1.6;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
:global(.markdown-content .code-block code) {
|
|
311
|
+
background: transparent;
|
|
312
|
+
padding: 0;
|
|
313
|
+
border: none;
|
|
314
|
+
color: rgb(226, 232, 240);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Syntax highlighting colors */
|
|
318
|
+
:global(.syntax-keyword) {
|
|
319
|
+
color: rgb(96, 165, 250); /* Blue */
|
|
320
|
+
font-weight: 500;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
:global(.syntax-string) {
|
|
324
|
+
color: rgb(134, 239, 172); /* Green */
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
:global(.syntax-number) {
|
|
328
|
+
color: rgb(251, 146, 60); /* Orange */
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
:global(.syntax-comment) {
|
|
332
|
+
color: rgb(100, 116, 139); /* Gray */
|
|
333
|
+
font-style: italic;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
:global(.syntax-function) {
|
|
337
|
+
color: rgb(253, 224, 71); /* Yellow */
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
:global(.markdown-content .inline-code) {
|
|
341
|
+
background: rgb(30, 41, 59);
|
|
342
|
+
color: rgb(251, 146, 60);
|
|
343
|
+
padding: 0.125rem 0.5rem;
|
|
344
|
+
border-radius: 0.25rem;
|
|
345
|
+
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
|
346
|
+
font-size: 0.875em;
|
|
347
|
+
border: 1px solid rgb(51, 65, 85);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
:global(.markdown-content strong) {
|
|
351
|
+
font-weight: 600;
|
|
352
|
+
color: rgb(241, 245, 249);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
:global(.markdown-content em) {
|
|
356
|
+
font-style: italic;
|
|
357
|
+
color: rgb(203, 213, 225);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
:global(.markdown-content .md-link) {
|
|
361
|
+
color: rgb(96, 165, 250);
|
|
362
|
+
text-decoration: underline;
|
|
363
|
+
text-decoration-color: transparent;
|
|
364
|
+
transition: text-decoration-color 0.2s;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
:global(.markdown-content .md-link:hover) {
|
|
368
|
+
text-decoration-color: rgb(96, 165, 250);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
:global(.markdown-content .md-ul) {
|
|
372
|
+
list-style-type: disc;
|
|
373
|
+
margin-left: 1.5rem;
|
|
374
|
+
margin-top: 0.5rem;
|
|
375
|
+
margin-bottom: 0.5rem;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
:global(.markdown-content .md-li) {
|
|
379
|
+
margin-top: 0.25rem;
|
|
380
|
+
margin-bottom: 0.25rem;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
:global(.markdown-content .md-blockquote) {
|
|
384
|
+
border-left: 3px solid rgb(96, 165, 250);
|
|
385
|
+
padding-left: 1rem;
|
|
386
|
+
padding-top: 0.5rem;
|
|
387
|
+
padding-bottom: 0.5rem;
|
|
388
|
+
margin: 1rem 0;
|
|
389
|
+
background: rgb(30, 41, 59);
|
|
390
|
+
color: rgb(148, 163, 184);
|
|
391
|
+
font-style: italic;
|
|
392
|
+
border-radius: 0 0.25rem 0.25rem 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
:global(.markdown-content .md-hr) {
|
|
396
|
+
border: none;
|
|
397
|
+
border-top: 1px solid rgb(51, 65, 85);
|
|
398
|
+
margin: 1.5rem 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
:global(.markdown-content br) {
|
|
402
|
+
display: block;
|
|
403
|
+
content: "";
|
|
404
|
+
margin-top: 0.5rem;
|
|
405
|
+
}
|
|
406
|
+
`}</style>
|
|
407
|
+
</div>
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
@@ -68,8 +68,8 @@ export function Sidebar() {
|
|
|
68
68
|
|
|
69
69
|
return (
|
|
70
70
|
<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 ${
|
|
71
|
-
isCollapsed ? 'w-16' : 'w-60'
|
|
72
|
-
}`}>
|
|
71
|
+
isCollapsed ? 'w-16' : 'w-60 md:w-60'
|
|
72
|
+
} ${isCollapsed ? '' : 'max-md:w-16'}`}>
|
|
73
73
|
{/* Compact Professional Header */}
|
|
74
74
|
<div className="relative p-3 border-b border-border/50 bg-gradient-to-b from-card/80 to-transparent">
|
|
75
75
|
<div className="flex items-center justify-between mb-2">
|
|
@@ -272,7 +272,7 @@ export function Sidebar() {
|
|
|
272
272
|
{/* Copyright */}
|
|
273
273
|
<div className="text-center">
|
|
274
274
|
<p className="text-[8px] text-muted-foreground/50 font-medium">
|
|
275
|
-
© 2025
|
|
275
|
+
© 2025 NitroCloud
|
|
276
276
|
</p>
|
|
277
277
|
</div>
|
|
278
278
|
</div>
|
|
@@ -4,7 +4,7 @@ import type { Tool } from '@/lib/types';
|
|
|
4
4
|
import { useStudioStore } from '@/lib/store';
|
|
5
5
|
import { WidgetRenderer } from './WidgetRenderer';
|
|
6
6
|
import { useRouter } from 'next/navigation';
|
|
7
|
-
import { Zap, Palette, Maximize2, Play, Sparkles } from 'lucide-react';
|
|
7
|
+
import { Zap, Palette, Maximize2, Play, Sparkles, MessageSquare } from 'lucide-react';
|
|
8
8
|
|
|
9
9
|
interface ToolCardProps {
|
|
10
10
|
tool: Tool;
|
|
@@ -28,7 +28,17 @@ export function ToolCard({ tool, onExecute }: ToolCardProps) {
|
|
|
28
28
|
|
|
29
29
|
const handleUseInChat = (e: React.MouseEvent) => {
|
|
30
30
|
e.stopPropagation();
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
// Build the tool execution message
|
|
33
|
+
const toolMessage = `Use the ${tool.name} tool`;
|
|
34
|
+
|
|
35
|
+
// Store the message in localStorage
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
window.localStorage.setItem('chatInput', toolMessage);
|
|
38
|
+
window.localStorage.setItem('suggestedTool', tool.name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
router.push('/chat');
|
|
32
42
|
};
|
|
33
43
|
|
|
34
44
|
const handleEnlarge = (e: React.MouseEvent) => {
|
|
@@ -85,22 +95,30 @@ export function ToolCard({ tool, onExecute }: ToolCardProps) {
|
|
|
85
95
|
)}
|
|
86
96
|
|
|
87
97
|
{/* Action Buttons */}
|
|
88
|
-
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
|
98
|
+
<div className="flex flex-wrap items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
|
89
99
|
{hasWidget && exampleData && (
|
|
90
100
|
<button
|
|
91
101
|
onClick={handleEnlarge}
|
|
92
|
-
className="btn btn-secondary flex-1 text-sm gap-2"
|
|
102
|
+
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"
|
|
93
103
|
>
|
|
94
|
-
<Maximize2 className="w-4 h-4" />
|
|
95
|
-
<span>Enlarge</span>
|
|
104
|
+
<Maximize2 className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
|
|
105
|
+
<span className="truncate">Enlarge</span>
|
|
96
106
|
</button>
|
|
97
107
|
)}
|
|
98
108
|
<button
|
|
99
109
|
onClick={() => onExecute(tool)}
|
|
100
|
-
className="btn btn-primary flex-1 text-sm gap-2"
|
|
110
|
+
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"
|
|
101
111
|
>
|
|
102
|
-
<Play className="w-4 h-4" />
|
|
103
|
-
<span>Execute</span>
|
|
112
|
+
<Play className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
|
|
113
|
+
<span className="truncate">Execute</span>
|
|
114
|
+
</button>
|
|
115
|
+
<button
|
|
116
|
+
onClick={handleUseInChat}
|
|
117
|
+
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"
|
|
118
|
+
title="Use in Chat"
|
|
119
|
+
>
|
|
120
|
+
<MessageSquare className="w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" />
|
|
121
|
+
<span className="truncate">Chat</span>
|
|
104
122
|
</button>
|
|
105
123
|
</div>
|
|
106
124
|
</div>
|
|
@@ -82,14 +82,16 @@ export function WidgetRenderer({ uri, data, className = '' }: WidgetRendererProp
|
|
|
82
82
|
}
|
|
83
83
|
}, [uri, data, isDevMode]);
|
|
84
84
|
|
|
85
|
+
const isInChat = className?.includes('widget-in-chat');
|
|
86
|
+
|
|
85
87
|
return (
|
|
86
88
|
<iframe
|
|
87
89
|
ref={iframeRef}
|
|
88
90
|
className={className}
|
|
89
91
|
sandbox="allow-scripts allow-same-origin"
|
|
90
92
|
style={{
|
|
91
|
-
width: '100%',
|
|
92
|
-
height: '100%',
|
|
93
|
+
width: isInChat ? '650px' : '100%',
|
|
94
|
+
height: isInChat ? '450px' : '100%',
|
|
93
95
|
border: 'none',
|
|
94
96
|
background: 'transparent',
|
|
95
97
|
}}
|
|
@@ -101,10 +101,32 @@ User: "what's in my cart" → Call view_cart directly (don't ask for login)
|
|
|
101
101
|
|
|
102
102
|
Only if tool returns auth error → THEN suggest: "Please login with your credentials"
|
|
103
103
|
|
|
104
|
+
**PRESENTING TOOL RESULTS:**
|
|
105
|
+
|
|
106
|
+
When you call a tool and receive results, you MUST present the information to the user in a clear, formatted way:
|
|
107
|
+
|
|
108
|
+
- For **list_resources**: Show each resource's URI, name, description, and mime type in a formatted list
|
|
109
|
+
- For **list_prompts**: Show each prompt's name, description, and required arguments
|
|
110
|
+
- For **read_resource**: Display the resource content in an appropriate format
|
|
111
|
+
- For **execute_prompt**: Show the prompt result clearly
|
|
112
|
+
- For ANY tool result: Extract and format the key information for the user to understand
|
|
113
|
+
|
|
114
|
+
**Example:**
|
|
115
|
+
User: "list all resources"
|
|
116
|
+
1. Call list_resources tool
|
|
117
|
+
2. Receive JSON array of resources
|
|
118
|
+
3. Format and present: "Here are the available resources:
|
|
119
|
+
- **Resource Name** (uri) - Description
|
|
120
|
+
- **Another Resource** (uri) - Description"
|
|
121
|
+
|
|
122
|
+
**NEVER** just say "I have the results" without showing them!
|
|
123
|
+
**ALWAYS** format and display the actual data you receive from tools!
|
|
124
|
+
|
|
104
125
|
**REMEMBER:**
|
|
105
126
|
|
|
106
127
|
- You have access to real, functional tools - use them!
|
|
107
|
-
- Call tools directly - don't ask for permission or credentials first
|
|
128
|
+
- Call tools directly - don't ask for permission or credentials first
|
|
129
|
+
- **After calling a tool, ALWAYS present the results to the user clearly**
|
|
108
130
|
- Your goal is to be helpful, efficient, and reduce user friction
|
|
109
131
|
- Think through multi-step workflows and execute them seamlessly
|
|
110
132
|
- Use your intelligence to fill gaps rather than always asking questions`;
|