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.
- package/README.md +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +2 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/mcp-dev-wrapper.js +30 -17
- package/dist/cli/mcp-dev-wrapper.js.map +1 -1
- 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/transports/http-server.d.ts.map +1 -1
- package/dist/core/transports/http-server.js +21 -1
- package/dist/core/transports/http-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 +155 -28
- package/src/studio/app/api/init/route.ts +28 -4
- package/src/studio/app/auth/page.tsx +13 -9
- package/src/studio/app/chat/page.tsx +599 -133
- package/src/studio/app/health/page.tsx +101 -99
- package/src/studio/app/layout.tsx +24 -4
- package/src/studio/app/page.tsx +61 -56
- package/src/studio/app/ping/page.tsx +13 -8
- package/src/studio/app/prompts/page.tsx +72 -70
- package/src/studio/app/resources/page.tsx +88 -86
- package/src/studio/app/settings/page.tsx +270 -0
- 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 +197 -35
- package/src/studio/components/ToolCard.tsx +27 -9
- package/src/studio/components/WidgetRenderer.tsx +4 -2
- package/src/studio/lib/http-client-transport.ts +222 -0
- package/src/studio/lib/llm-service.ts +119 -0
- package/src/studio/lib/log-manager.ts +76 -0
- package/src/studio/lib/mcp-client.ts +103 -13
- package/src/studio/package-lock.json +3129 -0
- package/src/studio/package.json +1 -0
- package/templates/typescript-auth/README.md +3 -1
- package/templates/typescript-auth/src/db/database.ts +5 -8
- package/templates/typescript-auth/src/index.ts +13 -2
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
- package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
- package/templates/typescript-auth-api-key/README.md +3 -1
- package/templates/typescript-auth-api-key/src/index.ts +11 -3
- 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
|
+
|