nitrostack 1.0.16 → 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.
@@ -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 NitroStudio
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
- router.push(`/chat?tool=${tool.name}`);
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`;