nitrostack 1.0.71 → 1.0.73

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 (253) hide show
  1. package/dist/auth/api-key.js.map +1 -1
  2. package/dist/auth/client.js.map +1 -1
  3. package/dist/auth/index.d.ts +2 -1
  4. package/dist/auth/index.d.ts.map +1 -1
  5. package/dist/auth/index.js +3 -0
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/middleware.d.ts +1 -1
  8. package/dist/auth/middleware.d.ts.map +1 -1
  9. package/dist/auth/middleware.js.map +1 -1
  10. package/dist/auth/secure-secret.d.ts +136 -0
  11. package/dist/auth/secure-secret.d.ts.map +1 -0
  12. package/dist/auth/secure-secret.js +182 -0
  13. package/dist/auth/secure-secret.js.map +1 -0
  14. package/dist/auth/server-metadata.d.ts.map +1 -1
  15. package/dist/auth/server-metadata.js.map +1 -1
  16. package/dist/auth/simple-jwt.d.ts +100 -14
  17. package/dist/auth/simple-jwt.d.ts.map +1 -1
  18. package/dist/auth/simple-jwt.js +19 -9
  19. package/dist/auth/simple-jwt.js.map +1 -1
  20. package/dist/auth/token-store.js +1 -1
  21. package/dist/auth/token-store.js.map +1 -1
  22. package/dist/auth/token-validation.js +1 -1
  23. package/dist/auth/token-validation.js.map +1 -1
  24. package/dist/cli/commands/build.js +1 -1
  25. package/dist/cli/commands/build.js.map +1 -1
  26. package/dist/cli/commands/generate-types.js +12 -12
  27. package/dist/cli/commands/generate-types.js.map +1 -1
  28. package/dist/cli/commands/generate.d.ts +8 -1
  29. package/dist/cli/commands/generate.d.ts.map +1 -1
  30. package/dist/cli/commands/generate.js +13 -12
  31. package/dist/cli/commands/generate.js.map +1 -1
  32. package/dist/cli/commands/init.js +1 -1
  33. package/dist/cli/commands/init.js.map +1 -1
  34. package/dist/cli/commands/upgrade.d.ts +10 -0
  35. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  36. package/dist/cli/commands/upgrade.js +221 -0
  37. package/dist/cli/commands/upgrade.js.map +1 -0
  38. package/dist/cli/index.js +7 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/core/app-decorator.d.ts +4 -3
  41. package/dist/core/app-decorator.d.ts.map +1 -1
  42. package/dist/core/app-decorator.js +67 -28
  43. package/dist/core/app-decorator.js.map +1 -1
  44. package/dist/core/builders.d.ts +19 -7
  45. package/dist/core/builders.d.ts.map +1 -1
  46. package/dist/core/builders.js +15 -8
  47. package/dist/core/builders.js.map +1 -1
  48. package/dist/core/component.d.ts +8 -8
  49. package/dist/core/component.d.ts.map +1 -1
  50. package/dist/core/component.js +3 -2
  51. package/dist/core/component.js.map +1 -1
  52. package/dist/core/config-module.d.ts +11 -4
  53. package/dist/core/config-module.d.ts.map +1 -1
  54. package/dist/core/config-module.js +1 -1
  55. package/dist/core/config-module.js.map +1 -1
  56. package/dist/core/decorators/cache.decorator.d.ts +9 -9
  57. package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
  58. package/dist/core/decorators/cache.decorator.js +3 -3
  59. package/dist/core/decorators/cache.decorator.js.map +1 -1
  60. package/dist/core/decorators/health-check.decorator.d.ts +3 -3
  61. package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
  62. package/dist/core/decorators/health-check.decorator.js +2 -2
  63. package/dist/core/decorators/health-check.decorator.js.map +1 -1
  64. package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
  65. package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
  66. package/dist/core/decorators/rate-limit.decorator.js +3 -3
  67. package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
  68. package/dist/core/decorators.d.ts +47 -29
  69. package/dist/core/decorators.d.ts.map +1 -1
  70. package/dist/core/decorators.js +9 -9
  71. package/dist/core/decorators.js.map +1 -1
  72. package/dist/core/di/container.d.ts +21 -4
  73. package/dist/core/di/container.d.ts.map +1 -1
  74. package/dist/core/di/container.js +11 -7
  75. package/dist/core/di/container.js.map +1 -1
  76. package/dist/core/di/injectable.decorator.d.ts +5 -3
  77. package/dist/core/di/injectable.decorator.d.ts.map +1 -1
  78. package/dist/core/di/injectable.decorator.js.map +1 -1
  79. package/dist/core/errors.d.ts +4 -4
  80. package/dist/core/errors.d.ts.map +1 -1
  81. package/dist/core/errors.js.map +1 -1
  82. package/dist/core/events/event-emitter.d.ts +3 -3
  83. package/dist/core/events/event-emitter.d.ts.map +1 -1
  84. package/dist/core/events/event-emitter.js.map +1 -1
  85. package/dist/core/events/event.decorator.d.ts +5 -5
  86. package/dist/core/events/event.decorator.d.ts.map +1 -1
  87. package/dist/core/events/event.decorator.js +10 -6
  88. package/dist/core/events/event.decorator.js.map +1 -1
  89. package/dist/core/events/log-emitter.d.ts +7 -1
  90. package/dist/core/events/log-emitter.d.ts.map +1 -1
  91. package/dist/core/events/log-emitter.js.map +1 -1
  92. package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
  93. package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
  94. package/dist/core/filters/exception-filter.decorator.js +3 -3
  95. package/dist/core/filters/exception-filter.decorator.js.map +1 -1
  96. package/dist/core/filters/exception-filter.interface.d.ts +14 -5
  97. package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
  98. package/dist/core/guards/apikey.guard.d.ts +1 -1
  99. package/dist/core/guards/apikey.guard.d.ts.map +1 -1
  100. package/dist/core/guards/guard.interface.d.ts +1 -1
  101. package/dist/core/guards/guard.interface.d.ts.map +1 -1
  102. package/dist/core/guards/jwt.guard.d.ts +1 -1
  103. package/dist/core/guards/jwt.guard.d.ts.map +1 -1
  104. package/dist/core/guards/oauth.guard.d.ts +1 -1
  105. package/dist/core/guards/oauth.guard.d.ts.map +1 -1
  106. package/dist/core/guards/use-guards.decorator.d.ts +3 -3
  107. package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
  108. package/dist/core/guards/use-guards.decorator.js +1 -1
  109. package/dist/core/guards/use-guards.decorator.js.map +1 -1
  110. package/dist/core/index.d.ts +2 -2
  111. package/dist/core/index.d.ts.map +1 -1
  112. package/dist/core/index.js.map +1 -1
  113. package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
  114. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  115. package/dist/core/interceptors/interceptor.decorator.js +2 -2
  116. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  117. package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
  118. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  119. package/dist/core/logger.d.ts.map +1 -1
  120. package/dist/core/logger.js.map +1 -1
  121. package/dist/core/middleware/middleware.decorator.d.ts +4 -4
  122. package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
  123. package/dist/core/middleware/middleware.decorator.js +2 -2
  124. package/dist/core/middleware/middleware.decorator.js.map +1 -1
  125. package/dist/core/middleware/middleware.interface.d.ts +3 -3
  126. package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
  127. package/dist/core/module.d.ts +33 -14
  128. package/dist/core/module.d.ts.map +1 -1
  129. package/dist/core/module.js +11 -6
  130. package/dist/core/module.js.map +1 -1
  131. package/dist/core/oauth-module.d.ts +9 -3
  132. package/dist/core/oauth-module.d.ts.map +1 -1
  133. package/dist/core/oauth-module.js +4 -3
  134. package/dist/core/oauth-module.js.map +1 -1
  135. package/dist/core/pipes/pipe.decorator.d.ts +14 -5
  136. package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
  137. package/dist/core/pipes/pipe.decorator.js +2 -2
  138. package/dist/core/pipes/pipe.decorator.js.map +1 -1
  139. package/dist/core/pipes/pipe.interface.d.ts +9 -4
  140. package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
  141. package/dist/core/prompt.d.ts +13 -4
  142. package/dist/core/prompt.d.ts.map +1 -1
  143. package/dist/core/prompt.js +2 -2
  144. package/dist/core/prompt.js.map +1 -1
  145. package/dist/core/resource.d.ts +7 -2
  146. package/dist/core/resource.d.ts.map +1 -1
  147. package/dist/core/resource.js +2 -2
  148. package/dist/core/resource.js.map +1 -1
  149. package/dist/core/server.d.ts +49 -3
  150. package/dist/core/server.d.ts.map +1 -1
  151. package/dist/core/server.js +61 -34
  152. package/dist/core/server.js.map +1 -1
  153. package/dist/core/tool.d.ts +44 -16
  154. package/dist/core/tool.d.ts.map +1 -1
  155. package/dist/core/tool.js +19 -6
  156. package/dist/core/tool.js.map +1 -1
  157. package/dist/core/transports/discovery-http-server.d.ts +7 -1
  158. package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
  159. package/dist/core/transports/discovery-http-server.js.map +1 -1
  160. package/dist/core/transports/http-server.d.ts +2 -2
  161. package/dist/core/transports/http-server.d.ts.map +1 -1
  162. package/dist/core/transports/http-server.js +1 -1
  163. package/dist/core/transports/http-server.js.map +1 -1
  164. package/dist/core/transports/streamable-http.d.ts +4 -4
  165. package/dist/core/transports/streamable-http.d.ts.map +1 -1
  166. package/dist/core/transports/streamable-http.js +1 -1
  167. package/dist/core/transports/streamable-http.js.map +1 -1
  168. package/dist/core/types.d.ts +87 -15
  169. package/dist/core/types.d.ts.map +1 -1
  170. package/dist/core/widgets/widget-registry.d.ts +2 -2
  171. package/dist/core/widgets/widget-registry.d.ts.map +1 -1
  172. package/dist/core/widgets/widget-registry.js +1 -1
  173. package/dist/core/widgets/widget-registry.js.map +1 -1
  174. package/dist/testing/index.d.ts +44 -17
  175. package/dist/testing/index.d.ts.map +1 -1
  176. package/dist/testing/index.js +5 -8
  177. package/dist/testing/index.js.map +1 -1
  178. package/dist/ui-next/index.d.ts +1 -1
  179. package/dist/ui-next/index.d.ts.map +1 -1
  180. package/dist/ui-next/index.js.map +1 -1
  181. package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
  182. package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
  183. package/dist/widgets/sdk.d.ts +5 -5
  184. package/dist/widgets/sdk.d.ts.map +1 -1
  185. package/dist/widgets/sdk.js.map +1 -1
  186. package/package.json +1 -1
  187. package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
  188. package/src/studio/app/api/auth/register-client/route.ts +3 -2
  189. package/src/studio/app/api/chat/route.ts +33 -17
  190. package/src/studio/app/api/health/checks/route.ts +5 -4
  191. package/src/studio/app/api/init/route.ts +3 -2
  192. package/src/studio/app/api/ping/route.ts +3 -2
  193. package/src/studio/app/api/prompts/[name]/route.ts +4 -3
  194. package/src/studio/app/api/prompts/route.ts +3 -2
  195. package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
  196. package/src/studio/app/api/resources/route.ts +3 -2
  197. package/src/studio/app/api/roots/route.ts +3 -2
  198. package/src/studio/app/api/sampling/route.ts +3 -2
  199. package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
  200. package/src/studio/app/api/tools/route.ts +4 -3
  201. package/src/studio/app/api/widget-examples/route.ts +5 -4
  202. package/src/studio/app/auth/callback/page.tsx +9 -8
  203. package/src/studio/app/chat/page.tsx +1535 -468
  204. package/src/studio/app/chat/page.tsx.backup +1046 -187
  205. package/src/studio/app/globals.css +361 -191
  206. package/src/studio/app/health/page.tsx +73 -77
  207. package/src/studio/app/layout.tsx +9 -11
  208. package/src/studio/app/logs/page.tsx +31 -32
  209. package/src/studio/app/page.tsx +136 -232
  210. package/src/studio/app/prompts/page.tsx +115 -97
  211. package/src/studio/app/resources/page.tsx +115 -124
  212. package/src/studio/app/settings/page.tsx +1083 -127
  213. package/src/studio/app/tools/page.tsx +343 -0
  214. package/src/studio/components/EnlargeModal.tsx +76 -65
  215. package/src/studio/components/LogMessage.tsx +6 -6
  216. package/src/studio/components/MarkdownRenderer.tsx +246 -349
  217. package/src/studio/components/Sidebar.tsx +165 -210
  218. package/src/studio/components/SplashScreen.tsx +109 -0
  219. package/src/studio/components/ToolCard.tsx +50 -41
  220. package/src/studio/components/VoiceOrbOverlay.tsx +475 -0
  221. package/src/studio/components/WidgetErrorBoundary.tsx +48 -0
  222. package/src/studio/components/WidgetRenderer.tsx +169 -211
  223. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  224. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  225. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  226. package/src/studio/components/ops/index.ts +5 -0
  227. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  228. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  229. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  230. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  231. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  232. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  233. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  234. package/src/studio/components/ops/nodes/index.ts +8 -0
  235. package/src/studio/components/tools/ToolsCanvas.tsx +327 -0
  236. package/src/studio/lib/api.ts +61 -42
  237. package/src/studio/lib/http-client-transport.ts +2 -2
  238. package/src/studio/lib/llm-service.ts +126 -47
  239. package/src/studio/lib/mcp-client.ts +9 -6
  240. package/src/studio/lib/ops-store.ts +427 -0
  241. package/src/studio/lib/ops-tracker.ts +416 -0
  242. package/src/studio/lib/ops-types.ts +164 -0
  243. package/src/studio/lib/store.ts +23 -11
  244. package/src/studio/lib/types.ts +228 -38
  245. package/src/studio/lib/widget-loader.ts +2 -2
  246. package/src/studio/package-lock.json +3303 -0
  247. package/src/studio/package.json +3 -1
  248. package/src/studio/public/NitroStudio Isotype Color.png +0 -0
  249. package/src/studio/tailwind.config.ts +63 -17
  250. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  251. package/dist/cli/build-widgets.mjs +0 -165
  252. package/src/studio/app/auth/page.tsx +0 -560
  253. package/src/studio/app/ping/page.tsx +0 -209
@@ -1,410 +1,307 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { Copy, Check } from 'lucide-react';
4
+ import { ClipboardDocumentIcon, CheckIcon } from '@heroicons/react/24/outline';
5
5
 
6
6
  interface MarkdownRendererProps {
7
7
  content: string;
8
8
  }
9
9
 
10
10
  export function MarkdownRenderer({ content }: MarkdownRendererProps) {
11
- const [copied, setCopied] = useState(false);
12
- const [copiedCode, setCopiedCode] = useState<string | null>(null);
11
+ const [copiedBlock, setCopiedBlock] = useState<string | null>(null);
13
12
 
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
- };
13
+ // Guard against null/undefined content
14
+ if (!content || typeof content !== 'string') {
15
+ return null;
16
+ }
23
17
 
24
18
  const handleCopyCode = async (code: string, blockId: string) => {
25
19
  try {
26
20
  await navigator.clipboard.writeText(code);
27
- setCopiedCode(blockId);
28
- setTimeout(() => setCopiedCode(null), 2000);
21
+ setCopiedBlock(blockId);
22
+ setTimeout(() => setCopiedBlock(null), 2000);
29
23
  } catch (error) {
30
24
  console.error('Failed to copy code:', error);
31
25
  }
32
26
  };
33
27
 
34
- // Simple syntax highlighting for common languages
28
+ const escapeHtml = (text: string) => {
29
+ return text
30
+ .replace(/&/g, '&amp;')
31
+ .replace(/</g, '&lt;')
32
+ .replace(/>/g, '&gt;')
33
+ .replace(/"/g, '&quot;')
34
+ .replace(/'/g, '&#039;');
35
+ };
36
+
37
+ // Simple syntax highlighting
35
38
  const highlightCode = (code: string, language: string) => {
36
39
  let highlighted = escapeHtml(code);
37
40
 
38
- // Keywords for different languages
39
41
  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'],
42
+ javascript: ['function', 'const', 'let', 'var', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'new', 'this', 'try', 'catch', 'throw', 'true', 'false', 'null', 'undefined'],
43
+ typescript: ['function', 'const', 'let', 'var', 'return', 'if', 'else', 'for', 'while', 'class', 'import', 'export', 'from', 'async', 'await', 'interface', 'type', 'enum', 'new', 'this', 'try', 'catch', 'throw', 'true', 'false', 'null', 'undefined'],
44
+ python: ['def', 'class', 'return', 'if', 'else', 'elif', 'for', 'while', 'import', 'from', 'try', 'except', 'with', 'as', 'pass', 'break', 'continue', 'lambda', 'yield', 'True', 'False', 'None'],
45
+ json: [],
45
46
  };
46
47
 
47
48
  const lang = language.toLowerCase();
48
49
  const langKeywords = keywords[lang] || keywords['javascript'];
49
50
 
50
51
  // 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>');
52
+ highlighted = highlighted.replace(/(\/\/.*$|\/\*[\s\S]*?\*\/)/gm, '<span style="color: #6b7280; font-style: italic;">$1</span>');
53
+ highlighted = highlighted.replace(/(#.*$)/gm, '<span style="color: #6b7280; font-style: italic;">$1</span>');
53
54
 
54
55
  // Strings
55
- highlighted = highlighted.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, '<span class="syntax-string">$1</span>');
56
+ highlighted = highlighted.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, '<span style="color: #22c55e;">$1</span>');
56
57
 
57
58
  // Numbers
58
- highlighted = highlighted.replace(/\b(\d+\.?\d*)\b/g, '<span class="syntax-number">$1</span>');
59
+ highlighted = highlighted.replace(/\b(\d+\.?\d*)\b/g, '<span style="color: #f97316;">$1</span>');
59
60
 
60
61
  // Keywords
61
62
  langKeywords.forEach(keyword => {
62
63
  const regex = new RegExp(`\\b${keyword}\\b`, 'g');
63
- highlighted = highlighted.replace(regex, `<span class="syntax-keyword">${keyword}</span>`);
64
+ highlighted = highlighted.replace(regex, `<span style="color: #3b82f6; font-weight: 500;">${keyword}</span>`);
64
65
  });
65
66
 
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
67
  return highlighted;
70
68
  };
71
69
 
72
- // Simple markdown rendering using regex (basic implementation)
70
+ // Parse and render markdown
73
71
  const renderMarkdown = (text: string) => {
74
- let html = text;
72
+ const elements: React.ReactNode[] = [];
75
73
  let codeBlockCounter = 0;
76
74
 
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>`;
75
+ // Split by code blocks first
76
+ const parts = text.split(/(```[\s\S]*?```)/g);
77
+
78
+ parts.forEach((part, partIndex) => {
79
+ if (part.startsWith('```')) {
80
+ // Code block
81
+ const match = part.match(/```(\w+)?\n?([\s\S]*?)```/);
82
+ if (match) {
83
+ const language = match[1] || 'text';
84
+ const code = match[2].trim();
85
+ const blockId = `code-${partIndex}-${codeBlockCounter++}`;
86
+ const isCopied = copiedBlock === blockId;
87
+
88
+ elements.push(
89
+ <div key={blockId} className="my-3 rounded-lg overflow-hidden border border-border/50 bg-zinc-900/50">
90
+ <div className="flex items-center justify-between px-3 py-1.5 bg-zinc-800/50 border-b border-border/30">
91
+ <span className="text-xs text-muted-foreground font-medium">{language}</span>
92
+ <button
93
+ onClick={() => handleCopyCode(code, blockId)}
94
+ className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
95
+ >
96
+ {isCopied ? (
97
+ <>
98
+ <CheckIcon className="w-3.5 h-3.5 text-green-500" />
99
+ <span className="text-green-500">Copied!</span>
100
+ </>
101
+ ) : (
102
+ <>
103
+ <ClipboardDocumentIcon className="w-3.5 h-3.5" />
104
+ <span>Copy</span>
105
+ </>
106
+ )}
107
+ </button>
108
+ </div>
109
+ <pre className="p-3 overflow-x-auto text-sm font-mono leading-relaxed">
110
+ <code dangerouslySetInnerHTML={{ __html: highlightCode(code, language) }} />
111
+ </pre>
112
+ </div>
113
+ );
114
+ }
115
+ } else {
116
+ // Regular text - process inline markdown
117
+ const lines = part.split('\n');
118
+ const textElements: React.ReactNode[] = [];
119
+
120
+ lines.forEach((line, lineIndex) => {
121
+ let processedLine: React.ReactNode = line;
122
+
123
+ // Headers
124
+ if (line.startsWith('### ')) {
125
+ textElements.push(
126
+ <h3 key={`h3-${partIndex}-${lineIndex}`} className="text-base font-semibold text-foreground mt-4 mb-2">
127
+ {line.slice(4)}
128
+ </h3>
129
+ );
130
+ return;
131
+ }
132
+ if (line.startsWith('## ')) {
133
+ textElements.push(
134
+ <h2 key={`h2-${partIndex}-${lineIndex}`} className="text-lg font-semibold text-foreground mt-4 mb-2">
135
+ {line.slice(3)}
136
+ </h2>
137
+ );
138
+ return;
139
+ }
140
+ if (line.startsWith('# ')) {
141
+ textElements.push(
142
+ <h1 key={`h1-${partIndex}-${lineIndex}`} className="text-xl font-bold text-foreground mt-4 mb-2">
143
+ {line.slice(2)}
144
+ </h1>
145
+ );
146
+ return;
147
+ }
148
+
149
+ // Blockquotes
150
+ if (line.startsWith('> ')) {
151
+ textElements.push(
152
+ <blockquote key={`quote-${partIndex}-${lineIndex}`} className="border-l-2 border-primary/50 pl-3 my-2 text-muted-foreground italic">
153
+ {line.slice(2)}
154
+ </blockquote>
155
+ );
156
+ return;
157
+ }
158
+
159
+ // Horizontal rule
160
+ if (line.trim() === '---' || line.trim() === '***') {
161
+ textElements.push(
162
+ <hr key={`hr-${partIndex}-${lineIndex}`} className="my-4 border-border/50" />
163
+ );
164
+ return;
165
+ }
166
+
167
+ // Lists
168
+ const unorderedMatch = line.match(/^(\s*)[*-]\s+(.+)$/);
169
+ if (unorderedMatch) {
170
+ const indent = unorderedMatch[1].length;
171
+ textElements.push(
172
+ <div key={`li-${partIndex}-${lineIndex}`} className="flex items-start gap-2 my-0.5" style={{ marginLeft: `${indent * 8}px` }}>
173
+ <span className="text-muted-foreground mt-1.5">•</span>
174
+ <span>{renderInlineMarkdown(unorderedMatch[2])}</span>
175
+ </div>
176
+ );
177
+ return;
178
+ }
179
+
180
+ const orderedMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
181
+ if (orderedMatch) {
182
+ const indent = orderedMatch[1].length;
183
+ textElements.push(
184
+ <div key={`oli-${partIndex}-${lineIndex}`} className="flex items-start gap-2 my-0.5" style={{ marginLeft: `${indent * 8}px` }}>
185
+ <span className="text-muted-foreground min-w-[1.5rem]">{orderedMatch[2]}.</span>
186
+ <span>{renderInlineMarkdown(orderedMatch[3])}</span>
187
+ </div>
188
+ );
189
+ return;
190
+ }
191
+
192
+ // Empty lines
193
+ if (line.trim() === '') {
194
+ textElements.push(<div key={`empty-${partIndex}-${lineIndex}`} className="h-2" />);
195
+ return;
196
+ }
197
+
198
+ // Regular paragraph with inline formatting
199
+ textElements.push(
200
+ <p key={`p-${partIndex}-${lineIndex}`} className="my-0.5">
201
+ {renderInlineMarkdown(line)}
202
+ </p>
203
+ );
204
+ });
205
+
206
+ if (textElements.length > 0) {
207
+ elements.push(<div key={`text-${partIndex}`}>{textElements}</div>);
208
+ }
209
+ }
97
210
  });
98
211
 
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));
212
+ return elements;
160
213
  };
161
214
 
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);
215
+ // Render inline markdown (bold, italic, code, links)
216
+ const renderInlineMarkdown = (text: string): React.ReactNode => {
217
+ const parts: React.ReactNode[] = [];
218
+ let remaining = text;
219
+ let keyCounter = 0;
220
+
221
+ while (remaining.length > 0) {
222
+ // Inline code
223
+ const codeMatch = remaining.match(/^`([^`]+)`/);
224
+ if (codeMatch) {
225
+ parts.push(
226
+ <code key={keyCounter++} className="px-1.5 py-0.5 rounded bg-zinc-800/50 text-orange-400 text-sm font-mono">
227
+ {codeMatch[1]}
228
+ </code>
229
+ );
230
+ remaining = remaining.slice(codeMatch[0].length);
231
+ continue;
178
232
  }
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
233
 
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
- }
234
+ // Bold
235
+ const boldMatch = remaining.match(/^\*\*(.+?)\*\*/);
236
+ if (boldMatch) {
237
+ parts.push(<strong key={keyCounter++} className="font-semibold">{boldMatch[1]}</strong>);
238
+ remaining = remaining.slice(boldMatch[0].length);
239
+ continue;
240
+ }
359
241
 
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
- }
242
+ // Bold with underscores
243
+ const boldUnderMatch = remaining.match(/^__(.+?)__/);
244
+ if (boldUnderMatch) {
245
+ parts.push(<strong key={keyCounter++} className="font-semibold">{boldUnderMatch[1]}</strong>);
246
+ remaining = remaining.slice(boldUnderMatch[0].length);
247
+ continue;
248
+ }
366
249
 
367
- :global(.markdown-content .md-link:hover) {
368
- text-decoration-color: rgb(96, 165, 250);
369
- }
250
+ // Italic
251
+ const italicMatch = remaining.match(/^\*([^*]+)\*/);
252
+ if (italicMatch) {
253
+ parts.push(<em key={keyCounter++} className="italic">{italicMatch[1]}</em>);
254
+ remaining = remaining.slice(italicMatch[0].length);
255
+ continue;
256
+ }
370
257
 
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
- }
258
+ // Italic with underscores
259
+ const italicUnderMatch = remaining.match(/^_([^_]+)_/);
260
+ if (italicUnderMatch) {
261
+ parts.push(<em key={keyCounter++} className="italic">{italicUnderMatch[1]}</em>);
262
+ remaining = remaining.slice(italicUnderMatch[0].length);
263
+ continue;
264
+ }
377
265
 
378
- :global(.markdown-content .md-li) {
379
- margin-top: 0.25rem;
380
- margin-bottom: 0.25rem;
381
- }
266
+ // Links
267
+ const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)]+)\)/);
268
+ if (linkMatch) {
269
+ parts.push(
270
+ <a
271
+ key={keyCounter++}
272
+ href={linkMatch[2]}
273
+ target="_blank"
274
+ rel="noopener noreferrer"
275
+ className="text-primary hover:underline"
276
+ >
277
+ {linkMatch[1]}
278
+ </a>
279
+ );
280
+ remaining = remaining.slice(linkMatch[0].length);
281
+ continue;
282
+ }
382
283
 
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
- }
284
+ // Regular text - take one character at a time until we hit a special character
285
+ const nextSpecial = remaining.search(/[`*_\[]/);
286
+ if (nextSpecial === -1) {
287
+ parts.push(remaining);
288
+ break;
289
+ } else if (nextSpecial === 0) {
290
+ // Special char that didn't match any pattern - just add it
291
+ parts.push(remaining[0]);
292
+ remaining = remaining.slice(1);
293
+ } else {
294
+ parts.push(remaining.slice(0, nextSpecial));
295
+ remaining = remaining.slice(nextSpecial);
296
+ }
297
+ }
394
298
 
395
- :global(.markdown-content .md-hr) {
396
- border: none;
397
- border-top: 1px solid rgb(51, 65, 85);
398
- margin: 1.5rem 0;
399
- }
299
+ return parts.length === 1 ? parts[0] : <>{parts}</>;
300
+ };
400
301
 
401
- :global(.markdown-content br) {
402
- display: block;
403
- content: "";
404
- margin-top: 0.5rem;
405
- }
406
- `}</style>
302
+ return (
303
+ <div className="text-foreground/90 leading-relaxed">
304
+ {renderMarkdown(content)}
407
305
  </div>
408
306
  );
409
307
  }
410
-