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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/dev.d.ts.map +1 -1
  3. package/dist/cli/commands/dev.js +2 -1
  4. package/dist/cli/commands/dev.js.map +1 -1
  5. package/dist/cli/mcp-dev-wrapper.js +30 -17
  6. package/dist/cli/mcp-dev-wrapper.js.map +1 -1
  7. package/dist/core/app-decorator.js +2 -2
  8. package/dist/core/app-decorator.js.map +1 -1
  9. package/dist/core/builders.js +2 -2
  10. package/dist/core/builders.js.map +1 -1
  11. package/dist/core/resource.js +1 -1
  12. package/dist/core/resource.js.map +1 -1
  13. package/dist/core/server.js +2 -2
  14. package/dist/core/server.js.map +1 -1
  15. package/dist/core/transports/http-server.d.ts.map +1 -1
  16. package/dist/core/transports/http-server.js +21 -1
  17. package/dist/core/transports/http-server.js.map +1 -1
  18. package/dist/core/types.d.ts +1 -1
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/package.json +1 -1
  21. package/src/studio/app/api/chat/route.ts +155 -28
  22. package/src/studio/app/api/init/route.ts +28 -4
  23. package/src/studio/app/auth/page.tsx +13 -9
  24. package/src/studio/app/chat/page.tsx +599 -133
  25. package/src/studio/app/health/page.tsx +101 -99
  26. package/src/studio/app/layout.tsx +24 -4
  27. package/src/studio/app/page.tsx +61 -56
  28. package/src/studio/app/ping/page.tsx +13 -8
  29. package/src/studio/app/prompts/page.tsx +72 -70
  30. package/src/studio/app/resources/page.tsx +88 -86
  31. package/src/studio/app/settings/page.tsx +270 -0
  32. package/src/studio/components/EnlargeModal.tsx +21 -15
  33. package/src/studio/components/LogMessage.tsx +153 -0
  34. package/src/studio/components/MarkdownRenderer.tsx +410 -0
  35. package/src/studio/components/Sidebar.tsx +197 -35
  36. package/src/studio/components/ToolCard.tsx +27 -9
  37. package/src/studio/components/WidgetRenderer.tsx +4 -2
  38. package/src/studio/lib/http-client-transport.ts +222 -0
  39. package/src/studio/lib/llm-service.ts +119 -0
  40. package/src/studio/lib/log-manager.ts +76 -0
  41. package/src/studio/lib/mcp-client.ts +103 -13
  42. package/src/studio/package-lock.json +3129 -0
  43. package/src/studio/package.json +1 -0
  44. package/templates/typescript-auth/README.md +3 -1
  45. package/templates/typescript-auth/src/db/database.ts +5 -8
  46. package/templates/typescript-auth/src/index.ts +13 -2
  47. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
  48. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
  49. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
  50. package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
  51. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
  52. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
  53. package/templates/typescript-auth-api-key/README.md +3 -1
  54. package/templates/typescript-auth-api-key/src/index.ts +11 -3
  55. package/templates/typescript-starter/README.md +3 -1
@@ -0,0 +1,153 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Copy, Check, ChevronDown, ChevronRight } from 'lucide-react';
5
+
6
+ interface LogMessageProps {
7
+ message: string;
8
+ }
9
+
10
+ export function LogMessage({ message }: LogMessageProps) {
11
+ const [copied, setCopied] = useState(false);
12
+ const [isExpanded, setIsExpanded] = useState(true);
13
+
14
+ const handleCopy = async (text: string) => {
15
+ try {
16
+ await navigator.clipboard.writeText(text);
17
+ setCopied(true);
18
+ setTimeout(() => setCopied(false), 2000);
19
+ } catch (error) {
20
+ console.error('Failed to copy:', error);
21
+ }
22
+ };
23
+
24
+ // Try to parse and format JSON
25
+ const tryParseJSON = (text: string): { isJSON: boolean; formatted?: string; parsed?: any } => {
26
+ try {
27
+ const trimmed = text.trim();
28
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
29
+ return { isJSON: false };
30
+ }
31
+
32
+ const parsed = JSON.parse(trimmed);
33
+
34
+ // Pretty print with proper indentation
35
+ const formatted = JSON.stringify(parsed, (key, value) => {
36
+ // If value is a string that looks like JSON, try to parse it
37
+ if (typeof value === 'string' && (value.trim().startsWith('{') || value.trim().startsWith('['))) {
38
+ try {
39
+ return JSON.parse(value);
40
+ } catch {
41
+ return value;
42
+ }
43
+ }
44
+ return value;
45
+ }, 2);
46
+
47
+ return { isJSON: true, formatted, parsed };
48
+ } catch {
49
+ return { isJSON: false };
50
+ }
51
+ };
52
+
53
+ // Syntax highlight JSON
54
+ const highlightJSON = (jsonString: string) => {
55
+ return jsonString
56
+ .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g, (match) => {
57
+ let cls = 'json-string';
58
+ if (/:$/.test(match)) {
59
+ cls = 'json-key';
60
+ }
61
+ return `<span class="${cls}">${match}</span>`;
62
+ })
63
+ .replace(/\b(true|false|null)\b/g, '<span class="json-boolean">$1</span>')
64
+ .replace(/\b(-?\d+\.?\d*)\b/g, '<span class="json-number">$1</span>')
65
+ .replace(/([{}[\],])/g, '<span class="json-punctuation">$1</span>');
66
+ };
67
+
68
+ const result = tryParseJSON(message);
69
+
70
+ if (!result.isJSON) {
71
+ // Plain text message
72
+ return <span className="text-slate-300 break-all">{message}</span>;
73
+ }
74
+
75
+ // JSON message - collapsible with syntax highlighting
76
+ return (
77
+ <div className="flex-1 min-w-0">
78
+ <div className="flex items-center gap-2 mb-1">
79
+ <button
80
+ onClick={() => setIsExpanded(!isExpanded)}
81
+ className="text-slate-400 hover:text-slate-200 transition-colors"
82
+ >
83
+ {isExpanded ? (
84
+ <ChevronDown className="w-4 h-4" />
85
+ ) : (
86
+ <ChevronRight className="w-4 h-4" />
87
+ )}
88
+ </button>
89
+ <span className="text-slate-500 text-xs font-semibold">JSON Response</span>
90
+ <button
91
+ onClick={() => handleCopy(result.formatted || message)}
92
+ className="ml-auto text-slate-500 hover:text-slate-300 transition-colors p-1 rounded hover:bg-slate-800"
93
+ title={copied ? 'Copied!' : 'Copy JSON'}
94
+ >
95
+ {copied ? (
96
+ <Check className="w-3.5 h-3.5 text-green-400" />
97
+ ) : (
98
+ <Copy className="w-3.5 h-3.5" />
99
+ )}
100
+ </button>
101
+ </div>
102
+
103
+ {isExpanded && (
104
+ <div className="json-container">
105
+ <pre
106
+ className="json-content"
107
+ dangerouslySetInnerHTML={{ __html: highlightJSON(result.formatted || message) }}
108
+ />
109
+ </div>
110
+ )}
111
+
112
+ <style jsx>{`
113
+ .json-container {
114
+ background: rgb(15, 23, 42);
115
+ border: 1px solid rgb(51, 65, 85);
116
+ border-radius: 0.5rem;
117
+ padding: 0.75rem;
118
+ overflow-x: auto;
119
+ margin-top: 0.5rem;
120
+ }
121
+
122
+ .json-content {
123
+ margin: 0;
124
+ font-family: 'JetBrains Mono', 'Courier New', monospace;
125
+ font-size: 0.75rem;
126
+ line-height: 1.5;
127
+ color: rgb(226, 232, 240);
128
+ }
129
+
130
+ :global(.json-key) {
131
+ color: rgb(96, 165, 250); /* Blue */
132
+ }
133
+
134
+ :global(.json-string) {
135
+ color: rgb(134, 239, 172); /* Green */
136
+ }
137
+
138
+ :global(.json-number) {
139
+ color: rgb(251, 146, 60); /* Orange */
140
+ }
141
+
142
+ :global(.json-boolean) {
143
+ color: rgb(167, 139, 250); /* Purple */
144
+ }
145
+
146
+ :global(.json-punctuation) {
147
+ color: rgb(148, 163, 184); /* Gray */
148
+ }
149
+ `}</style>
150
+ </div>
151
+ );
152
+ }
153
+
@@ -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
+