doccupine 0.0.53 → 0.0.55

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 (30) hide show
  1. package/dist/index.js +7 -1
  2. package/dist/templates/app/layout.js +28 -3
  3. package/dist/templates/components/Chat.d.ts +1 -1
  4. package/dist/templates/components/Chat.js +4 -1
  5. package/dist/templates/components/ClickOutside.d.ts +1 -1
  6. package/dist/templates/components/ClickOutside.js +14 -12
  7. package/dist/templates/components/DocsSideBar.d.ts +1 -1
  8. package/dist/templates/components/DocsSideBar.js +1 -1
  9. package/dist/templates/components/MDXComponents.d.ts +1 -1
  10. package/dist/templates/components/MDXComponents.js +7 -4
  11. package/dist/templates/components/layout/Accordion.d.ts +1 -1
  12. package/dist/templates/components/layout/Accordion.js +6 -1
  13. package/dist/templates/components/layout/DocsNavigation.d.ts +1 -1
  14. package/dist/templates/components/layout/DocsNavigation.js +1 -1
  15. package/dist/templates/components/layout/Pictograms.d.ts +1 -1
  16. package/dist/templates/components/layout/Pictograms.js +0 -1
  17. package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
  18. package/dist/templates/components/layout/ThemeToggle.js +2 -2
  19. package/dist/templates/package.js +2 -1
  20. package/dist/templates/prettierignore.d.ts +1 -0
  21. package/dist/templates/prettierignore.js +5 -0
  22. package/dist/templates/prettierrc.d.ts +1 -0
  23. package/dist/templates/prettierrc.js +11 -0
  24. package/dist/templates/proxy.d.ts +1 -1
  25. package/dist/templates/proxy.js +1 -4
  26. package/dist/templates/services/mcp/server.d.ts +1 -1
  27. package/dist/templates/services/mcp/server.js +5 -1
  28. package/dist/templates/tsconfig.d.ts +1 -1
  29. package/dist/templates/tsconfig.js +32 -31
  30. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ import { gitignoreTemplate } from "./templates/gitignore.js";
12
12
  import { eslintConfigTemplate } from "./templates/eslint.config.js";
13
13
  import { nextConfigTemplate } from "./templates/next.config.js";
14
14
  import { packageJsonTemplate } from "./templates/package.js";
15
+ import { prettierrcTemplate } from "./templates/prettierrc.js";
16
+ import { prettierignoreTemplate } from "./templates/prettierignore.js";
15
17
  import { proxyTemplate } from "./templates/proxy.js";
16
18
  import { tsconfigTemplate } from "./templates/tsconfig.js";
17
19
  import { mcpRoutesTemplate } from "./templates/app/api/mcp/route.js";
@@ -230,6 +232,8 @@ class MDXToNextJSGenerator {
230
232
  const structure = {
231
233
  ".env.example": envExampleTemplate,
232
234
  ".gitignore": gitignoreTemplate,
235
+ ".prettierrc": prettierrcTemplate,
236
+ ".prettierignore": prettierignoreTemplate,
233
237
  "config.json": `{}`,
234
238
  "eslint.config.mjs": eslintConfigTemplate,
235
239
  "links.json": `[]`,
@@ -839,7 +843,9 @@ program
839
843
  });
840
844
  devServer.stderr?.on("data", (data) => {
841
845
  const output = data.toString();
842
- if (options.verbose || output.includes("Error") || output.includes("error")) {
846
+ if (options.verbose ||
847
+ output.includes("Error") ||
848
+ output.includes("error")) {
843
849
  process.stderr.write(chalk.red("[Next.js] ") + output);
844
850
  }
845
851
  });
@@ -1,3 +1,24 @@
1
+ function formatPagesArray(pages) {
2
+ const MAX_WIDTH = 80;
3
+ const items = pages.map((page) => {
4
+ const lines = [" {"];
5
+ const entries = Object.entries(page);
6
+ for (const [key, value] of entries) {
7
+ const valueStr = JSON.stringify(value);
8
+ const line = ` ${key}: ${valueStr},`;
9
+ if (line.length > MAX_WIDTH) {
10
+ lines.push(` ${key}:`);
11
+ lines.push(` ${valueStr},`);
12
+ }
13
+ else {
14
+ lines.push(line);
15
+ }
16
+ }
17
+ lines.push(" },");
18
+ return lines.join("\n");
19
+ });
20
+ return "[\n" + items.join("\n") + "\n]";
21
+ }
1
22
  export const layoutTemplate = (pages, fontConfig) => `import type { Metadata } from "next";
2
23
  ${fontConfig?.googleFont?.fontName?.length ? `import { ${fontConfig.googleFont.fontName} } from "next/font/google";` : fontConfig?.localFonts?.length || fontConfig?.localFonts?.src?.length ? 'import localFont from "next/font/local";' : 'import { Inter } from "next/font/google";'}
3
24
  import dynamic from "next/dynamic";
@@ -29,16 +50,20 @@ ${fontConfig?.googleFont?.fontName?.length
29
50
 
30
51
  export const metadata: Metadata = {
31
52
  title: config.name || "Doccupine",
32
- description: config.description || "Doccupine is a free and open-source document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
53
+ description:
54
+ config.description ||
55
+ "Doccupine is a free and open-source document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
33
56
  icons: config.icon || "https://doccupine.com/favicon.ico",
34
57
  openGraph: {
35
58
  title: config.name || "Doccupine",
36
- description: config.description || "Doccupine is a free and open-source document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
59
+ description:
60
+ config.description ||
61
+ "Doccupine is a free and open-source document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
37
62
  images: config.preview || "https://doccupine.com/preview.png",
38
63
  },
39
64
  };
40
65
 
41
- const doccupinePages = ${JSON.stringify(pages, null, 2).replace(/"([^"]+)":/g, "$1:")};
66
+ const doccupinePages = ${formatPagesArray(pages)};
42
67
 
43
68
  export default async function RootLayout({
44
69
  children,
@@ -1 +1 @@
1
- export declare const chatTemplate = "\"use client\";\nimport React, {\n createContext,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Button } from \"cherry-styled-components\";\nimport { ArrowUp, LoaderPinwheel, Sparkles, X } from \"lucide-react\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport { MDXRemote, MDXRemoteSerializeResult } from \"next-mdx-remote\";\nimport { serialize } from \"next-mdx-remote/serialize\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useMDXComponents as getMDXComponents } from \"@/components/MDXComponents\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport links from \"@/links.json\";\n\nconst mdxComponents = getMDXComponents({});\n\nconst styledText = css<{ theme: Theme }>`\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: ${({ theme }) => theme.lineHeights.small.lg};\n }\n`;\n\nconst StyledChat = styled.div<{ theme: Theme; $isVisible: boolean }>`\n margin: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 100%;\n height: calc(100vh - 90px);\n overflow-y: scroll;\n overflow-x: hidden;\n z-index: 1000;\n padding: 0 20px;\n transition: all 0.3s ease;\n transform: translateX(0);\n background: ${({ theme }) => theme.colors.light};\n\n ${({ $isVisible }) =>\n !$isVisible &&\n css`\n transform: translateX(100%);\n `}\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n`;\n\nconst loadingAnimation = keyframes`\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n`;\n\nconst rotateGradient = keyframes`\n 0% {\n --gradient-angle: 0deg;\n }\n 100% {\n --gradient-angle: 360deg;\n }\n`;\n\nconst pulseGlow = keyframes`\n 0%, 100% {\n opacity: 0.5;\n filter: blur(16px);\n }\n 50% {\n opacity: 1;\n filter: blur(22px);\n }\n`;\n\nconst sparkleFloat = keyframes`\n 0%, 100% {\n opacity: 0;\n transform: translateY(0) scale(0);\n }\n 50% {\n opacity: 0.9;\n transform: translateY(-20px) scale(1);\n }\n`;\n\nconst shimmer = keyframes`\n 0% {\n background-position: 0% center;\n }\n 50% {\n background-position: 100% center;\n }\n 100% {\n background-position: 0% center;\n }\n`;\n\nconst StyledRainbowInputWrapper = styled.div<{\n theme: Theme;\n $isActive: boolean;\n}>`\n @property --gradient-angle {\n syntax: \"<angle>\";\n initial-value: 0deg;\n inherits: false;\n }\n\n position: relative;\n flex: 1;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 14px;\n background: conic-gradient(\n from var(--gradient-angle),\n #cc5555,\n #d9a745,\n #3ab0cc,\n #cc7fc2,\n #4380cc,\n #4c1fa3,\n #cc5555\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation: ${rotateGradient} 3s linear infinite;\n z-index: 0;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n inset: -10px;\n border-radius: 20px;\n background: conic-gradient(\n from var(--gradient-angle),\n ${rgba(\"#ff6b6b\", 0.4)},\n ${rgba(\"#feca57\", 0.4)},\n ${rgba(\"#48dbfb\", 0.4)},\n ${rgba(\"#ff9ff3\", 0.4)},\n ${rgba(\"#54a0ff\", 0.4)},\n ${rgba(\"#5f27cd\", 0.4)},\n ${rgba(\"#ff6b6b\", 0.4)}\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation:\n ${rotateGradient} 3s linear infinite,\n ${pulseGlow} 2s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n &:hover::before,\n &:focus-within::before {\n opacity: 1;\n }\n\n &:hover::after,\n &:focus-within::after {\n opacity: 1;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n opacity: 1;\n }\n &::after {\n opacity: 1;\n }\n `}\n`;\n\nconst StyledSparkleContainer = styled.div<{ $isActive: boolean }>`\n position: absolute;\n inset: -30px;\n pointer-events: none;\n overflow: hidden;\n border-radius: 30px;\n z-index: -2;\n opacity: 0;\n transition: opacity 0.4s ease;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n `}\n`;\n\nconst StyledSparkle = styled.div<{\n $color: string;\n $left: number;\n $top: number;\n $delay: number;\n}>`\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: ${({ $color }) => $color};\n box-shadow: 0 0 6px ${({ $color }) => $color};\n left: ${({ $left }) => $left}%;\n top: ${({ $top }) => $top}%;\n animation: ${sparkleFloat} 2s ease-in-out infinite;\n animation-delay: ${({ $delay }) => $delay}s;\n`;\n\nconst StyledRainbowInput = styled.input<{ theme: Theme }>`\n position: relative;\n z-index: 1;\n width: 100%;\n background: ${({ theme }) => theme.colors.light};\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\n border-radius: 12px;\n padding: 14px 18px;\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n font-family: inherit;\n color: ${({ theme }) => theme.colors.dark};\n outline: none;\n transition:\n border-color 0.3s ease,\n box-shadow 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n }\n\n &::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.4)};\n transition: color 0.3s ease;\n }\n\n &:focus::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.6)};\n }\n\n &:focus {\n border-color: transparent;\n }\n`;\n\nconst StyledRainbowButton = styled(Button)<{\n theme: Theme;\n $hasContent: boolean;\n}>`\n padding-top: 10px;\n padding-bottom: 10px;\n position: relative;\n overflow: hidden;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n\n &::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 135deg,\n #ff6b6b,\n #feca57,\n #48dbfb,\n #ff9ff3,\n #54a0ff\n );\n background-size: 300% 300%;\n opacity: 0;\n transition: opacity 0.3s ease;\n z-index: 0;\n animation: ${shimmer} 3s linear infinite;\n width: 200%;\n }\n\n ${({ $hasContent }) =>\n $hasContent &&\n css`\n &::before {\n opacity: 1;\n }\n `}\n\n &:hover::before {\n opacity: 1;\n }\n\n &:hover {\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n\n & svg {\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:disabled,\n &:disabled:hover {\n background: ${({ theme }) => theme.colors.primaryDark};\n transform: none;\n box-shadow: none;\n\n &::before {\n opacity: 0;\n }\n }\n`;\n\nconst StyledChatForm = styled.form<{ theme: Theme; $isVisible: boolean }>`\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n background: ${({ theme }) => theme.colors.light};\n padding: 20px;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 1000;\n width: 100%;\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n transform: translateX(100%);\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n\n ${({ $isVisible }) =>\n $isVisible &&\n css`\n transform: translateX(0);\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedForm = styled.form<{\n theme: Theme;\n $hide: boolean;\n $hasLinks: boolean;\n}>`\n transition: all 0.3s ease;\n position: fixed;\n bottom: 20px;\n left: 20px;\n width: calc(100% - 115px);\n z-index: 998;\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%) translateY(0);\n bottom: initial;\n position: absolute;\n top: 90px;\n width: calc(100% - 320px * 2 - 40px);\n opacity: 1;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n top: 164px;\n `}\n }\n\n ${({ $hide }) =>\n $hide &&\n css`\n transform: translateX(-100px);\n\n ${mq(\"lg\")} {\n opacity: 0;\n transform: translateX(-50%) translateY(-20px);\n }\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedInner = styled.div`\n margin: auto;\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n\n ${mq(\"lg\")} {\n max-width: 640px;\n }\n`;\n\nconst StyledError = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.error};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n`;\n\nconst loadingDotAnimation = keyframes`\n 0% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n`;\n\nconst StyledLoading = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n margin: 20px 0;\n width: 100%;\n font-weight: 600;\n ${styledText};\n color: ${({ theme }) => theme.colors.dark};\n\n & span {\n &:nth-child(1) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n }\n &:nth-child(2) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.2s;\n }\n &:nth-child(3) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.4s;\n }\n }\n`;\n\nconst StyledAnswer = styled.div<{ theme: Theme; $isAnswer: boolean }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n\n & p {\n ${styledText};\n }\n\n ${({ $isAnswer }) =>\n $isAnswer &&\n css`\n background: transparent;\n color: ${({ theme }) => theme.colors.dark};\n padding: 0;\n `}\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n ${stylesLists};\n ${styledTable};\n\n & pre,\n & .hljs {\n margin: 10px 0;\n }\n\n & .code-wrapper pre {\n margin: 0;\n ${styledText};\n }\n\n & > *:first-child {\n margin-top: 0;\n }\n\n & > *:last-child {\n margin-bottom: 0;\n\n & > *:last-child {\n margin-bottom: 0;\n }\n }\n\n & ul,\n & ol {\n & li {\n ${styledText};\n }\n }\n\n & ol {\n & > li {\n padding-left: 20px;\n\n &::before {\n position: absolute;\n top: 0;\n left: 0;\n }\n }\n }\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n margin: 10px 0;\n display: block;\n }\n\n & h1,\n & h2,\n & h3,\n & h4,\n & h5,\n & h6 {\n margin: 10px 0;\n padding: 0;\n }\n`;\n\nconst StyledChatTitle = styled.div<{ theme: Theme }>`\n display: flex;\n flex-wrap: nowrap;\n justify-content: space-between;\n position: sticky;\n margin: 0 -20px;\n padding: 25px 20px;\n height: 73px;\n top: 0;\n background: ${({ theme }) => theme.colors.light};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n z-index: 1000;\n`;\n\nconst StyledChatTitleIconWrapper = styled.span<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 5px;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nconst StyledChatCloseButton = styled.button<{ theme: Theme }>`\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n color: ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\ntype Answer = {\n text: string;\n answer?: boolean;\n mdx?: MDXRemoteSerializeResult;\n};\n\nconst SPARKLE_COLORS = [\n \"#ff6b6b\",\n \"#feca57\",\n \"#48dbfb\",\n \"#ff9ff3\",\n \"#54a0ff\",\n \"#5f27cd\",\n];\n\n// Deterministic sparkle positions to avoid hydration mismatch\nconst SPARKLE_POSITIONS = [\n { left: 8, top: 35 },\n { left: 17, top: 55 },\n { left: 26, top: 28 },\n { left: 35, top: 68 },\n { left: 44, top: 42 },\n { left: 53, top: 75 },\n { left: 62, top: 32 },\n { left: 71, top: 58 },\n { left: 80, top: 45 },\n { left: 89, top: 65 },\n];\n\ninterface RainbowInputProps {\n id?: string;\n value: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n autoComplete?: string;\n \"aria-label\"?: string;\n}\n\nfunction RainbowInput({\n id,\n value,\n onChange,\n placeholder,\n autoComplete,\n \"aria-label\": ariaLabel,\n}: RainbowInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const isActive = isFocused || isHovered;\n\n const sparkles = SPARKLE_POSITIONS.map((pos, i) => ({\n color: SPARKLE_COLORS[i % SPARKLE_COLORS.length],\n left: pos.left,\n top: pos.top,\n delay: i * 0.12,\n }));\n\n return (\n <StyledRainbowInputWrapper\n $isActive={isActive}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <StyledSparkleContainer $isActive={isActive}>\n {sparkles.map((sparkle, i) => (\n <StyledSparkle\n key={i}\n $color={sparkle.color}\n $left={sparkle.left}\n $top={sparkle.top}\n $delay={sparkle.delay}\n />\n ))}\n </StyledSparkleContainer>\n <StyledRainbowInput\n id={id}\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n autoComplete={autoComplete}\n aria-label={ariaLabel}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n />\n </StyledRainbowInputWrapper>\n );\n}\n\nfunction Chat() {\n const [question, setQuestion] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [answer, setAnswer] = useState<Answer[]>([]);\n const endRef = useRef<HTMLDivElement | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const { isOpen, setIsOpen } = useContext(ChatContext);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"end\" });\n }, [answer]);\n\n useEffect(() => {\n if (answer?.length > 0) {\n const el = document.getElementById(\n \"chat-bottom-input\",\n ) as HTMLInputElement | null;\n el?.focus();\n }\n }, [answer]);\n\n async function ask(e: React.FormEvent) {\n e.preventDefault();\n if (loading || question.trim() === \"\") return;\n const currentQuestion = question;\n setQuestion(\"\");\n setIsOpen(true);\n setLoading(true);\n setError(null);\n\n const mergedQuestions =\n answer.length > 0\n ? [...answer, { text: currentQuestion, answer: false }]\n : [{ text: currentQuestion, answer: false }];\n\n setAnswer(mergedQuestions);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const res = await fetch(\"/api/rag\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ question: currentQuestion }),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n throw new Error(errorData.error || \"Request failed\");\n }\n\n const reader = res.body?.getReader();\n const decoder = new TextDecoder();\n const contentParts: string[] = [];\n if (!reader) {\n throw new Error(\"Failed to get response reader\");\n }\n\n // Add a placeholder for the streaming answer\n const streamingAnswerIndex = mergedQuestions.length;\n setAnswer([...mergedQuestions, { text: \"\", answer: true }]);\n\n let buffer = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const parts = buffer.split(\"\\n\");\n // Keep the last (potentially incomplete) part in the buffer\n buffer = parts.pop() ?? \"\";\n\n for (const line of parts) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6));\n\n if (data.type === \"content\") {\n contentParts.push(data.data);\n const streamedContent = contentParts.join(\"\");\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n };\n return newAnswers;\n });\n } else if (data.type === \"error\") {\n throw new Error(data.data);\n } else if (data.type === \"done\") {\n const streamedContent = contentParts.join(\"\");\n // Finalize with MDX serialization\n let mdxSource: MDXRemoteSerializeResult | null = null;\n try {\n mdxSource = await serialize(streamedContent, {\n parseFrontmatter: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n rehypePlugins: [rehypeHighlight],\n format: \"md\",\n development: false,\n },\n });\n } catch (mdxError: unknown) {\n console.error(\"MDX serialization error:\", mdxError);\n }\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n mdx: mdxSource || undefined,\n };\n return newAnswers;\n });\n }\n } catch (parseError) {\n if (parseError instanceof Error && parseError.message !== \"Unknown error\") {\n console.error(\"Failed to parse SSE data:\", parseError);\n }\n }\n }\n }\n }\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setError(err instanceof Error ? err.message : \"Unknown error\");\n } finally {\n abortRef.current = null;\n setLoading(false);\n }\n }\n\n return (\n <>\n <StyledChatFixedForm\n onSubmit={ask}\n $hide={answer?.length > 0}\n $hasLinks={links.length > 0}\n >\n <StyledChatFixedInner>\n <RainbowInput\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n aria-label=\"Ask a question about the documentation\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading}\n $hasContent={question.trim().length > 0}\n aria-label={loading ? \"Loading response\" : \"Submit question\"}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatFixedInner>\n </StyledChatFixedForm>\n\n <StyledChat $isVisible={isOpen}>\n <StyledChatTitle>\n <StyledChatTitleIconWrapper>\n <Sparkles />\n <h3>AI Assistant</h3>\n </StyledChatTitleIconWrapper>\n <StyledChatCloseButton\n onClick={() => {\n abortRef.current?.abort();\n setAnswer([]);\n setIsOpen(false);\n }}\n aria-label=\"Close chat\"\n >\n <X />\n </StyledChatCloseButton>\n </StyledChatTitle>\n {answer &&\n answer.map((a, i) => (\n <StyledAnswer key={i} $isAnswer={a.answer ?? false}>\n {a.answer && a.mdx ? (\n <MDXRemote {...a.mdx} components={mdxComponents} />\n ) : (\n a.text\n )}\n </StyledAnswer>\n ))}\n {loading && (\n <StyledLoading>\n Answering<span>.</span>\n <span>.</span>\n <span>.</span>\n </StyledLoading>\n )}\n {error && (\n <StyledError>\n <strong>Error:</strong> {error}\n </StyledError>\n )}\n <div ref={endRef} />\n </StyledChat>\n\n <StyledChatForm onSubmit={ask} $isVisible={isOpen}>\n <RainbowInput\n id=\"chat-bottom-input\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n aria-label=\"Ask a follow-up question\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading || question.trim() === \"\"}\n $hasContent={question.trim().length > 0}\n aria-label={loading ? \"Loading response\" : \"Submit question\"}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatForm>\n </>\n );\n}\n\nconst ChatContext = createContext<{\n isOpen: boolean;\n setIsOpen: (isOpen: boolean) => void;\n isChatActive: boolean;\n}>({\n isOpen: false,\n setIsOpen: () => {},\n isChatActive: false,\n});\n\ninterface ChatContextProviderProps {\n children: React.ReactNode;\n isChatActive: boolean;\n}\n\nconst ChtProvider = ({ children, isChatActive }: ChatContextProviderProps) => {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <ChatContext.Provider\n value={{\n isOpen,\n setIsOpen,\n isChatActive,\n }}\n >\n {children}\n </ChatContext.Provider>\n );\n};\n\nexport { Chat, ChtProvider, ChatContext };\n";
1
+ export declare const chatTemplate = "\"use client\";\nimport React, {\n createContext,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport styled, { css, keyframes } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Button } from \"cherry-styled-components\";\nimport { ArrowUp, LoaderPinwheel, Sparkles, X } from \"lucide-react\";\nimport remarkGfm from \"remark-gfm\";\nimport rehypeHighlight from \"rehype-highlight\";\nimport { MDXRemote, MDXRemoteSerializeResult } from \"next-mdx-remote\";\nimport { serialize } from \"next-mdx-remote/serialize\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useMDXComponents as getMDXComponents } from \"@/components/MDXComponents\";\nimport { styledTable, stylesLists } from \"@/components/layout/SharedStyled\";\nimport links from \"@/links.json\";\n\nconst mdxComponents = getMDXComponents({});\n\nconst styledText = css<{ theme: Theme }>`\n font-size: ${({ theme }) => theme.fontSizes.text.xs};\n line-height: ${({ theme }) => theme.lineHeights.text.xs};\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: ${({ theme }) => theme.lineHeights.small.lg};\n }\n`;\n\nconst StyledChat = styled.div<{ theme: Theme; $isVisible: boolean }>`\n margin: 0;\n position: fixed;\n top: 0;\n right: 0;\n width: 100%;\n height: calc(100vh - 90px);\n overflow-y: scroll;\n overflow-x: hidden;\n z-index: 1000;\n padding: 0 20px;\n transition: all 0.3s ease;\n transform: translateX(0);\n background: ${({ theme }) => theme.colors.light};\n\n ${({ $isVisible }) =>\n !$isVisible &&\n css`\n transform: translateX(100%);\n `}\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n`;\n\nconst loadingAnimation = keyframes`\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n`;\n\nconst rotateGradient = keyframes`\n 0% {\n --gradient-angle: 0deg;\n }\n 100% {\n --gradient-angle: 360deg;\n }\n`;\n\nconst pulseGlow = keyframes`\n 0%, 100% {\n opacity: 0.5;\n filter: blur(16px);\n }\n 50% {\n opacity: 1;\n filter: blur(22px);\n }\n`;\n\nconst sparkleFloat = keyframes`\n 0%, 100% {\n opacity: 0;\n transform: translateY(0) scale(0);\n }\n 50% {\n opacity: 0.9;\n transform: translateY(-20px) scale(1);\n }\n`;\n\nconst shimmer = keyframes`\n 0% {\n background-position: 0% center;\n }\n 50% {\n background-position: 100% center;\n }\n 100% {\n background-position: 0% center;\n }\n`;\n\nconst StyledRainbowInputWrapper = styled.div<{\n theme: Theme;\n $isActive: boolean;\n}>`\n @property --gradient-angle {\n syntax: \"<angle>\";\n initial-value: 0deg;\n inherits: false;\n }\n\n position: relative;\n flex: 1;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: -2px;\n border-radius: 14px;\n background: conic-gradient(\n from var(--gradient-angle),\n #cc5555,\n #d9a745,\n #3ab0cc,\n #cc7fc2,\n #4380cc,\n #4c1fa3,\n #cc5555\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation: ${rotateGradient} 3s linear infinite;\n z-index: 0;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n inset: -10px;\n border-radius: 20px;\n background: conic-gradient(\n from var(--gradient-angle),\n ${rgba(\"#ff6b6b\", 0.4)},\n ${rgba(\"#feca57\", 0.4)},\n ${rgba(\"#48dbfb\", 0.4)},\n ${rgba(\"#ff9ff3\", 0.4)},\n ${rgba(\"#54a0ff\", 0.4)},\n ${rgba(\"#5f27cd\", 0.4)},\n ${rgba(\"#ff6b6b\", 0.4)}\n );\n opacity: 0;\n transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n animation:\n ${rotateGradient} 3s linear infinite,\n ${pulseGlow} 2s ease-in-out infinite;\n z-index: -1;\n pointer-events: none;\n }\n\n &:hover::before,\n &:focus-within::before {\n opacity: 1;\n }\n\n &:hover::after,\n &:focus-within::after {\n opacity: 1;\n }\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n &::before {\n opacity: 1;\n }\n &::after {\n opacity: 1;\n }\n `}\n`;\n\nconst StyledSparkleContainer = styled.div<{ $isActive: boolean }>`\n position: absolute;\n inset: -30px;\n pointer-events: none;\n overflow: hidden;\n border-radius: 30px;\n z-index: -2;\n opacity: 0;\n transition: opacity 0.4s ease;\n\n ${({ $isActive }) =>\n $isActive &&\n css`\n opacity: 1;\n `}\n`;\n\nconst StyledSparkle = styled.div<{\n $color: string;\n $left: number;\n $top: number;\n $delay: number;\n}>`\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: ${({ $color }) => $color};\n box-shadow: 0 0 6px ${({ $color }) => $color};\n left: ${({ $left }) => $left}%;\n top: ${({ $top }) => $top}%;\n animation: ${sparkleFloat} 2s ease-in-out infinite;\n animation-delay: ${({ $delay }) => $delay}s;\n`;\n\nconst StyledRainbowInput = styled.input<{ theme: Theme }>`\n position: relative;\n z-index: 1;\n width: 100%;\n background: ${({ theme }) => theme.colors.light};\n border: 1px solid ${({ theme }) => theme.colors.grayLight};\n border-radius: 12px;\n padding: 14px 18px;\n font-size: ${({ theme }) => theme.fontSizes.text.lg};\n font-family: inherit;\n color: ${({ theme }) => theme.colors.dark};\n outline: none;\n transition:\n border-color 0.3s ease,\n box-shadow 0.3s ease;\n\n ${mq(\"lg\")} {\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n }\n\n &::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.4)};\n transition: color 0.3s ease;\n }\n\n &:focus::placeholder {\n color: ${({ theme }) => rgba(theme.colors.dark, 0.6)};\n }\n\n &:focus {\n border-color: transparent;\n }\n`;\n\nconst StyledRainbowButton = styled(Button)<{\n theme: Theme;\n $hasContent: boolean;\n}>`\n padding-top: 10px;\n padding-bottom: 10px;\n position: relative;\n overflow: hidden;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n\n &::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 135deg,\n #ff6b6b,\n #feca57,\n #48dbfb,\n #ff9ff3,\n #54a0ff\n );\n background-size: 300% 300%;\n opacity: 0;\n transition: opacity 0.3s ease;\n z-index: 0;\n animation: ${shimmer} 3s linear infinite;\n width: 200%;\n }\n\n ${({ $hasContent }) =>\n $hasContent &&\n css`\n &::before {\n opacity: 1;\n }\n `}\n\n &:hover::before {\n opacity: 1;\n }\n\n &:hover {\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n\n & svg {\n position: relative;\n z-index: 1;\n transition: transform 0.3s ease;\n }\n\n &:disabled,\n &:disabled:hover {\n background: ${({ theme }) => theme.colors.primaryDark};\n transform: none;\n box-shadow: none;\n\n &::before {\n opacity: 0;\n }\n }\n`;\n\nconst StyledChatForm = styled.form<{ theme: Theme; $isVisible: boolean }>`\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n background: ${({ theme }) => theme.colors.light};\n padding: 20px;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 1000;\n width: 100%;\n border-top: solid 1px ${({ theme }) => theme.colors.grayLight};\n transition: all 0.3s ease;\n transform: translateX(100%);\n\n ${mq(\"lg\")} {\n width: 420px;\n border-left: solid 1px ${({ theme }) => theme.colors.grayLight};\n }\n\n ${({ $isVisible }) =>\n $isVisible &&\n css`\n transform: translateX(0);\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedForm = styled.form<{\n theme: Theme;\n $hide: boolean;\n $hasLinks: boolean;\n}>`\n transition: all 0.3s ease;\n position: fixed;\n bottom: 20px;\n left: 20px;\n width: calc(100% - 115px);\n z-index: 998;\n\n ${mq(\"lg\")} {\n left: 50%;\n transform: translateX(-50%) translateY(0);\n bottom: initial;\n position: absolute;\n top: 90px;\n width: calc(100% - 320px * 2 - 40px);\n opacity: 1;\n\n ${({ $hasLinks }) =>\n $hasLinks &&\n css`\n top: 164px;\n `}\n }\n\n ${({ $hide }) =>\n $hide &&\n css`\n transform: translateX(-100px);\n\n ${mq(\"lg\")} {\n opacity: 0;\n transform: translateX(-50%) translateY(-20px);\n }\n `}\n\n & .loading {\n animation: ${loadingAnimation} 1s linear infinite;\n }\n`;\n\nconst StyledChatFixedInner = styled.div`\n margin: auto;\n display: flex;\n gap: 10px;\n justify-content: center;\n align-items: center;\n\n ${mq(\"lg\")} {\n max-width: 640px;\n }\n`;\n\nconst StyledError = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.error};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n`;\n\nconst loadingDotAnimation = keyframes`\n 0% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n`;\n\nconst StyledLoading = styled.div<{ theme: Theme }>`\n overflow-x: auto;\n margin: 20px 0;\n width: 100%;\n font-weight: 600;\n ${styledText};\n color: ${({ theme }) => theme.colors.dark};\n\n & span {\n &:nth-child(1) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n }\n &:nth-child(2) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.2s;\n }\n &:nth-child(3) {\n animation: ${loadingDotAnimation} 1s ease infinite;\n animation-delay: 0.4s;\n }\n }\n`;\n\nconst StyledAnswer = styled.div<{ theme: Theme; $isAnswer: boolean }>`\n overflow-x: auto;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.dark : theme.colors.light};\n padding: 10px;\n border-radius: 8px;\n margin: 20px 0;\n width: 100%;\n ${styledText};\n\n & p {\n ${styledText};\n }\n\n ${({ $isAnswer }) =>\n $isAnswer &&\n css`\n background: transparent;\n color: ${({ theme }) => theme.colors.dark};\n padding: 0;\n `}\n\n & code:not([class]) {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.dark};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n white-space: pre;\n }\n\n ${stylesLists};\n ${styledTable};\n\n & pre,\n & .hljs {\n margin: 10px 0;\n }\n\n & .code-wrapper pre {\n margin: 0;\n ${styledText};\n }\n\n & > *:first-child {\n margin-top: 0;\n }\n\n & > *:last-child {\n margin-bottom: 0;\n\n & > *:last-child {\n margin-bottom: 0;\n }\n }\n\n & ul,\n & ol {\n & li {\n ${styledText};\n }\n }\n\n & ol {\n & > li {\n padding-left: 20px;\n\n &::before {\n position: absolute;\n top: 0;\n left: 0;\n }\n }\n }\n\n & img,\n & video,\n & iframe {\n max-width: 100%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n margin: 10px 0;\n display: block;\n }\n\n & h1,\n & h2,\n & h3,\n & h4,\n & h5,\n & h6 {\n margin: 10px 0;\n padding: 0;\n }\n`;\n\nconst StyledChatTitle = styled.div<{ theme: Theme }>`\n display: flex;\n flex-wrap: nowrap;\n justify-content: space-between;\n position: sticky;\n margin: 0 -20px;\n padding: 25px 20px;\n height: 73px;\n top: 0;\n background: ${({ theme }) => theme.colors.light};\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n z-index: 1000;\n`;\n\nconst StyledChatTitleIconWrapper = styled.span<{ theme: Theme }>`\n display: flex;\n align-items: center;\n gap: 5px;\n color: ${({ theme }) => theme.colors.dark};\n`;\n\nconst StyledChatCloseButton = styled.button<{ theme: Theme }>`\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 0;\n margin: 0;\n color: ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n transform: scale(1.05);\n }\n\n &:active {\n transform: scale(0.95);\n }\n`;\n\ntype Answer = {\n text: string;\n answer?: boolean;\n mdx?: MDXRemoteSerializeResult;\n};\n\nconst SPARKLE_COLORS = [\n \"#ff6b6b\",\n \"#feca57\",\n \"#48dbfb\",\n \"#ff9ff3\",\n \"#54a0ff\",\n \"#5f27cd\",\n];\n\n// Deterministic sparkle positions to avoid hydration mismatch\nconst SPARKLE_POSITIONS = [\n { left: 8, top: 35 },\n { left: 17, top: 55 },\n { left: 26, top: 28 },\n { left: 35, top: 68 },\n { left: 44, top: 42 },\n { left: 53, top: 75 },\n { left: 62, top: 32 },\n { left: 71, top: 58 },\n { left: 80, top: 45 },\n { left: 89, top: 65 },\n];\n\ninterface RainbowInputProps {\n id?: string;\n value: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n autoComplete?: string;\n \"aria-label\"?: string;\n}\n\nfunction RainbowInput({\n id,\n value,\n onChange,\n placeholder,\n autoComplete,\n \"aria-label\": ariaLabel,\n}: RainbowInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const isActive = isFocused || isHovered;\n\n const sparkles = SPARKLE_POSITIONS.map((pos, i) => ({\n color: SPARKLE_COLORS[i % SPARKLE_COLORS.length],\n left: pos.left,\n top: pos.top,\n delay: i * 0.12,\n }));\n\n return (\n <StyledRainbowInputWrapper\n $isActive={isActive}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <StyledSparkleContainer $isActive={isActive}>\n {sparkles.map((sparkle, i) => (\n <StyledSparkle\n key={i}\n $color={sparkle.color}\n $left={sparkle.left}\n $top={sparkle.top}\n $delay={sparkle.delay}\n />\n ))}\n </StyledSparkleContainer>\n <StyledRainbowInput\n id={id}\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n autoComplete={autoComplete}\n aria-label={ariaLabel}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n />\n </StyledRainbowInputWrapper>\n );\n}\n\nfunction Chat() {\n const [question, setQuestion] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [answer, setAnswer] = useState<Answer[]>([]);\n const endRef = useRef<HTMLDivElement | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const { isOpen, setIsOpen } = useContext(ChatContext);\n\n useEffect(() => {\n endRef.current?.scrollIntoView({ behavior: \"smooth\", block: \"end\" });\n }, [answer]);\n\n useEffect(() => {\n if (answer?.length > 0) {\n const el = document.getElementById(\n \"chat-bottom-input\",\n ) as HTMLInputElement | null;\n el?.focus();\n }\n }, [answer]);\n\n async function ask(e: React.FormEvent) {\n e.preventDefault();\n if (loading || question.trim() === \"\") return;\n const currentQuestion = question;\n setQuestion(\"\");\n setIsOpen(true);\n setLoading(true);\n setError(null);\n\n const mergedQuestions =\n answer.length > 0\n ? [...answer, { text: currentQuestion, answer: false }]\n : [{ text: currentQuestion, answer: false }];\n\n setAnswer(mergedQuestions);\n\n const controller = new AbortController();\n abortRef.current = controller;\n\n try {\n const res = await fetch(\"/api/rag\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ question: currentQuestion }),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n throw new Error(errorData.error || \"Request failed\");\n }\n\n const reader = res.body?.getReader();\n const decoder = new TextDecoder();\n const contentParts: string[] = [];\n if (!reader) {\n throw new Error(\"Failed to get response reader\");\n }\n\n // Add a placeholder for the streaming answer\n const streamingAnswerIndex = mergedQuestions.length;\n setAnswer([...mergedQuestions, { text: \"\", answer: true }]);\n\n let buffer = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const parts = buffer.split(\"\\n\");\n // Keep the last (potentially incomplete) part in the buffer\n buffer = parts.pop() ?? \"\";\n\n for (const line of parts) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6));\n\n if (data.type === \"content\") {\n contentParts.push(data.data);\n const streamedContent = contentParts.join(\"\");\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n };\n return newAnswers;\n });\n } else if (data.type === \"error\") {\n throw new Error(data.data);\n } else if (data.type === \"done\") {\n const streamedContent = contentParts.join(\"\");\n // Finalize with MDX serialization\n let mdxSource: MDXRemoteSerializeResult | null = null;\n try {\n mdxSource = await serialize(streamedContent, {\n parseFrontmatter: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n rehypePlugins: [rehypeHighlight],\n format: \"md\",\n development: false,\n },\n });\n } catch (mdxError: unknown) {\n console.error(\"MDX serialization error:\", mdxError);\n }\n\n setAnswer((prev) => {\n const newAnswers = [...prev];\n newAnswers[streamingAnswerIndex] = {\n text: streamedContent,\n answer: true,\n mdx: mdxSource || undefined,\n };\n return newAnswers;\n });\n }\n } catch (parseError) {\n if (\n parseError instanceof Error &&\n parseError.message !== \"Unknown error\"\n ) {\n console.error(\"Failed to parse SSE data:\", parseError);\n }\n }\n }\n }\n }\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"AbortError\") return;\n setError(err instanceof Error ? err.message : \"Unknown error\");\n } finally {\n abortRef.current = null;\n setLoading(false);\n }\n }\n\n return (\n <>\n <StyledChatFixedForm\n onSubmit={ask}\n $hide={answer?.length > 0}\n $hasLinks={links.length > 0}\n >\n <StyledChatFixedInner>\n <RainbowInput\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n aria-label=\"Ask a question about the documentation\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading}\n $hasContent={question.trim().length > 0}\n aria-label={loading ? \"Loading response\" : \"Submit question\"}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatFixedInner>\n </StyledChatFixedForm>\n\n <StyledChat $isVisible={isOpen}>\n <StyledChatTitle>\n <StyledChatTitleIconWrapper>\n <Sparkles />\n <h3>AI Assistant</h3>\n </StyledChatTitleIconWrapper>\n <StyledChatCloseButton\n onClick={() => {\n abortRef.current?.abort();\n setAnswer([]);\n setIsOpen(false);\n }}\n aria-label=\"Close chat\"\n >\n <X />\n </StyledChatCloseButton>\n </StyledChatTitle>\n {answer &&\n answer.map((a, i) => (\n <StyledAnswer key={i} $isAnswer={a.answer ?? false}>\n {a.answer && a.mdx ? (\n <MDXRemote {...a.mdx} components={mdxComponents} />\n ) : (\n a.text\n )}\n </StyledAnswer>\n ))}\n {loading && (\n <StyledLoading>\n Answering<span>.</span>\n <span>.</span>\n <span>.</span>\n </StyledLoading>\n )}\n {error && (\n <StyledError>\n <strong>Error:</strong> {error}\n </StyledError>\n )}\n <div ref={endRef} />\n </StyledChat>\n\n <StyledChatForm onSubmit={ask} $isVisible={isOpen}>\n <RainbowInput\n id=\"chat-bottom-input\"\n value={question}\n onChange={(e) => setQuestion(e.target.value)}\n placeholder=\"Ask AI Assistant...\"\n autoComplete=\"off\"\n aria-label=\"Ask a follow-up question\"\n />\n <StyledRainbowButton\n type=\"submit\"\n disabled={loading || question.trim() === \"\"}\n $hasContent={question.trim().length > 0}\n aria-label={loading ? \"Loading response\" : \"Submit question\"}\n >\n {loading ? <LoaderPinwheel className=\"loading\" /> : <ArrowUp />}\n </StyledRainbowButton>\n </StyledChatForm>\n </>\n );\n}\n\nconst ChatContext = createContext<{\n isOpen: boolean;\n setIsOpen: (isOpen: boolean) => void;\n isChatActive: boolean;\n}>({\n isOpen: false,\n setIsOpen: () => {},\n isChatActive: false,\n});\n\ninterface ChatContextProviderProps {\n children: React.ReactNode;\n isChatActive: boolean;\n}\n\nconst ChtProvider = ({ children, isChatActive }: ChatContextProviderProps) => {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <ChatContext.Provider\n value={{\n isOpen,\n setIsOpen,\n isChatActive,\n }}\n >\n {children}\n </ChatContext.Provider>\n );\n};\n\nexport { Chat, ChtProvider, ChatContext };\n";
@@ -809,7 +809,10 @@ function Chat() {
809
809
  });
810
810
  }
811
811
  } catch (parseError) {
812
- if (parseError instanceof Error && parseError.message !== "Unknown error") {
812
+ if (
813
+ parseError instanceof Error &&
814
+ parseError.message !== "Unknown error"
815
+ ) {
813
816
  console.error("Failed to parse SSE data:", parseError);
814
817
  }
815
818
  }
@@ -1 +1 @@
1
- export declare const clickOutsideTemplate = "import { RefObject, useEffect } from \"react\";\n\nexport function useOnClickOutside(\n refs: RefObject<HTMLElement | null>[],\n cb: () => void,\n) {\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (\n refs &&\n refs\n .map(\n (ref) =>\n ref && ref.current && ref.current.contains(event.target as Node),\n )\n .every((i) => i === false)\n ) {\n cb();\n }\n }\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [refs, cb]);\n}\n";
1
+ export declare const clickOutsideTemplate = "import { RefObject, useCallback, useEffect } from \"react\";\n\nexport function useOnClickOutside(\n refs: RefObject<HTMLElement | null>[],\n cb: () => void,\n) {\n // Stable callback ref to avoid re-subscribing on every render\n const handleClickOutside = useCallback(\n (event: MouseEvent) => {\n if (\n refs.every(\n (ref) => !ref.current || !ref.current.contains(event.target as Node),\n )\n ) {\n cb();\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...refs, cb],\n );\n\n useEffect(() => {\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside);\n };\n }, [handleClickOutside]);\n}\n";
@@ -1,27 +1,29 @@
1
- export const clickOutsideTemplate = `import { RefObject, useEffect } from "react";
1
+ export const clickOutsideTemplate = `import { RefObject, useCallback, useEffect } from "react";
2
2
 
3
3
  export function useOnClickOutside(
4
4
  refs: RefObject<HTMLElement | null>[],
5
5
  cb: () => void,
6
6
  ) {
7
- useEffect(() => {
8
- function handleClickOutside(event: MouseEvent) {
7
+ // Stable callback ref to avoid re-subscribing on every render
8
+ const handleClickOutside = useCallback(
9
+ (event: MouseEvent) => {
9
10
  if (
10
- refs &&
11
- refs
12
- .map(
13
- (ref) =>
14
- ref && ref.current && ref.current.contains(event.target as Node),
15
- )
16
- .every((i) => i === false)
11
+ refs.every(
12
+ (ref) => !ref.current || !ref.current.contains(event.target as Node),
13
+ )
17
14
  ) {
18
15
  cb();
19
16
  }
20
- }
17
+ },
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
19
+ [...refs, cb],
20
+ );
21
+
22
+ useEffect(() => {
21
23
  document.addEventListener("mousedown", handleClickOutside);
22
24
  return () => {
23
25
  document.removeEventListener("mousedown", handleClickOutside);
24
26
  };
25
- }, [refs, cb]);
27
+ }, [handleClickOutside]);
26
28
  }
27
29
  `;
@@ -1 +1 @@
1
- export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const getScrollOffset = useCallback(() => {\n return document.getElementById(\"static-links\") ? 90 : 18;\n }, []);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const offset = getScrollOffset();\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter(Boolean) as HTMLElement[];\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - offset,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top - offset);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= offset) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings, getScrollOffset]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const offset = getScrollOffset();\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({ top: elementPosition - offset, behavior: \"smooth\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </li>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
1
+ export declare const docsSideBarTemplate = "\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { Space } from \"cherry-styled-components\";\nimport {\n StyledIndexSidebar,\n StyledIndexSidebarLink,\n StyledIndexSidebarLabel,\n} from \"@/components/layout/DocsComponents\";\n\nexport interface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nexport function DocsSideBar({ headings }: { headings: Heading[] }) {\n const [activeId, setActiveId] = useState<string>(\"\");\n\n const getScrollOffset = useCallback(() => {\n return document.getElementById(\"static-links\") ? 90 : 18;\n }, []);\n\n const handleScroll = useCallback(() => {\n if (headings.length === 0) return;\n\n const offset = getScrollOffset();\n\n const headingElements = headings\n .map((heading) => document.getElementById(heading.id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (headingElements.length === 0) return;\n\n const windowHeight = window.innerHeight;\n\n const visibleHeadings = headingElements.filter((element) => {\n const rect = element.getBoundingClientRect();\n const elementTop = rect.top;\n const elementBottom = rect.bottom;\n return elementTop < windowHeight && elementBottom > -50;\n });\n\n if (visibleHeadings.length > 0) {\n let closestHeading = visibleHeadings[0];\n let closestDistance = Math.abs(\n closestHeading.getBoundingClientRect().top - offset,\n );\n for (const heading of visibleHeadings) {\n const distance = Math.abs(heading.getBoundingClientRect().top - offset);\n if (\n distance < closestDistance &&\n heading.getBoundingClientRect().top <= windowHeight * 0.3\n ) {\n closestDistance = distance;\n closestHeading = heading;\n }\n }\n setActiveId(closestHeading.id);\n return;\n }\n\n let currentActiveId = headings[0].id;\n for (const element of headingElements) {\n const rect = element.getBoundingClientRect();\n if (rect.top <= offset) {\n currentActiveId = element.id;\n } else {\n break;\n }\n }\n setActiveId(currentActiveId);\n }, [headings, getScrollOffset]);\n\n useEffect(() => {\n if (headings.length === 0) return;\n // Run initial scroll check on next frame to avoid synchronous setState in effect\n const rafId = requestAnimationFrame(handleScroll);\n let timeoutId: NodeJS.Timeout;\n const throttledHandleScroll = () => {\n clearTimeout(timeoutId);\n timeoutId = setTimeout(handleScroll, 50);\n };\n window.addEventListener(\"scroll\", throttledHandleScroll);\n window.addEventListener(\"resize\", handleScroll);\n return () => {\n window.removeEventListener(\"scroll\", throttledHandleScroll);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n clearTimeout(timeoutId);\n };\n }, [handleScroll, headings]);\n\n const handleHeadingClick = (headingId: string) => {\n const element = document.getElementById(headingId);\n if (element) {\n const offset = getScrollOffset();\n const elementPosition =\n element.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({ top: elementPosition - offset, behavior: \"smooth\" });\n }\n };\n\n return (\n <StyledIndexSidebar>\n {headings?.length > 0 && (\n <>\n <StyledIndexSidebarLabel>On this page</StyledIndexSidebarLabel>\n <Space $size={20} />\n </>\n )}\n {headings.map((heading, index) => (\n <li\n key={index}\n style={{ paddingLeft: `${(heading.level - 1) * 16}px` }}\n >\n <StyledIndexSidebarLink\n href={`#${heading.id}`}\n onClick={(e) => {\n e.preventDefault();\n handleHeadingClick(heading.id);\n }}\n $isActive={activeId === heading.id}\n >\n {heading.text}\n </StyledIndexSidebarLink>\n </li>\n ))}\n </StyledIndexSidebar>\n );\n}\n";
@@ -27,7 +27,7 @@ export function DocsSideBar({ headings }: { headings: Heading[] }) {
27
27
 
28
28
  const headingElements = headings
29
29
  .map((heading) => document.getElementById(heading.id))
30
- .filter(Boolean) as HTMLElement[];
30
+ .filter((el): el is HTMLElement => el !== null);
31
31
 
32
32
  if (headingElements.length === 0) return;
33
33
 
@@ -1 +1 @@
1
- export declare const mdxComponentsTemplate = "import React from \"react\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{ children?: React.ReactNode }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(\n props.children,\n ) as React.ReactElement<{ className?: string; children?: React.ReactNode }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
1
+ export declare const mdxComponentsTemplate = "import React from \"react\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MDXComponents = Record<string, React.ComponentType<any>>;\nimport { Space } from \"cherry-styled-components\";\nimport { Code as CodeBlock } from \"@/components/layout/Code\";\nimport { Card } from \"@/components/layout/Card\";\nimport { Accordion } from \"@/components/layout/Accordion\";\nimport { Tabs, TabContent } from \"@/components/layout/Tabs\";\nimport { Callout } from \"@/components/layout/Callout\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { Columns } from \"@/components/layout/Columns\";\nimport { Field } from \"@/components/layout/Field\";\nimport { Update } from \"@/components/layout/Update\";\nimport { Steps, Step } from \"@/components/layout/Steps\";\nimport { Button } from \"@/components/layout/Button\";\nimport { DemoTheme } from \"@/components/layout/DemoTheme\";\n\ninterface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {\n children?: React.ReactNode;\n}\n\ninterface PreProps extends React.HTMLAttributes<HTMLPreElement> {\n children?: React.ReactNode;\n}\n\nfunction extractAllTextFromChildren(children: React.ReactNode): string {\n if (children == null) return \"\";\n if (typeof children === \"string\") return children;\n if (typeof children === \"number\") return String(children);\n if (typeof children === \"boolean\") return \"\";\n if (Array.isArray(children))\n return children.map(extractAllTextFromChildren).join(\"\");\n if (React.isValidElement(children)) {\n const element = children as React.ReactElement<{\n children?: React.ReactNode;\n }>;\n return extractAllTextFromChildren(element.props.children);\n }\n return \"\";\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\n// Map <pre><code class=\"language-xyz\"> to our <Code /> component\nfunction Pre(props: PreProps) {\n const child = React.Children.only(props.children) as React.ReactElement<{\n className?: string;\n children?: React.ReactNode;\n }> | null;\n if (child && child.type === \"code\") {\n const className = child.props.className || \"\";\n const match = /language-(\\w+)/.exec(className);\n const language = match ? match[1] : undefined;\n const code = extractAllTextFromChildren(child.props.children).replace(\n /\\n$/,\n \"\",\n );\n if (language) {\n return (\n <CodeBlock className={className} code={code} language={language} />\n );\n }\n }\n return <pre {...props} />;\n}\n\nexport function useMDXComponents(components: MDXComponents): MDXComponents {\n return {\n // Headings with auto-generated ids for TOC and deep links\n h1: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h1 id={id} {...props}>\n {children}\n </h1>\n );\n },\n h2: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h2 id={id} {...props}>\n {children}\n </h2>\n );\n },\n h3: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h3 id={id} {...props}>\n {children}\n </h3>\n );\n },\n h4: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h4 id={id} {...props}>\n {children}\n </h4>\n );\n },\n h5: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h5 id={id} {...props}>\n {children}\n </h5>\n );\n },\n h6: ({ children, ...props }: HeadingProps) => {\n const id = generateId(extractAllTextFromChildren(children));\n return (\n <h6 id={id} {...props}>\n {children}\n </h6>\n );\n },\n\n // Code blocks\n pre: Pre,\n\n // Expose your custom components for MDX usage\n Card,\n Accordion,\n Tabs,\n TabContent,\n Callout,\n Icon,\n Columns,\n Field,\n Update,\n Steps,\n Step,\n Button,\n DemoTheme,\n Space,\n ...components,\n };\n}\n";
@@ -32,7 +32,9 @@ function extractAllTextFromChildren(children: React.ReactNode): string {
32
32
  if (Array.isArray(children))
33
33
  return children.map(extractAllTextFromChildren).join("");
34
34
  if (React.isValidElement(children)) {
35
- const element = children as React.ReactElement<{ children?: React.ReactNode }>;
35
+ const element = children as React.ReactElement<{
36
+ children?: React.ReactNode;
37
+ }>;
36
38
  return extractAllTextFromChildren(element.props.children);
37
39
  }
38
40
  return "";
@@ -48,9 +50,10 @@ function generateId(text: string): string {
48
50
 
49
51
  // Map <pre><code class="language-xyz"> to our <Code /> component
50
52
  function Pre(props: PreProps) {
51
- const child = React.Children.only(
52
- props.children,
53
- ) as React.ReactElement<{ className?: string; children?: React.ReactNode }> | null;
53
+ const child = React.Children.only(props.children) as React.ReactElement<{
54
+ className?: string;
55
+ children?: React.ReactNode;
56
+ }> | null;
54
57
  if (child && child.type === "code") {
55
58
  const className = child.props.className || "";
56
59
  const match = /language-(\\w+)/.exec(className);
@@ -1 +1 @@
1
- export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n }\n\n & .lucide-chevron-down {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n right: 0;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: translateY(-50%) rotate(180deg);\n `}\n }\n`;\n\nconst StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n margin: 20px 0 0;\n height: auto;\n `}\n`;\n\nexport interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n}\n\nfunction Accordion({ children, title }: AccordionProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <StyledAccordion>\n <StyledAccordionTitle onClick={() => setIsOpen(!isOpen)} $isOpen={isOpen}>\n {title} <Icon name=\"ChevronDown\" />\n </StyledAccordionTitle>\n <StyledAccordionContent $isOpen={isOpen}>\n {children}\n </StyledAccordionContent>\n </StyledAccordion>\n );\n}\n\nexport { Accordion };\n";
1
+ export declare const accordionTemplate = "\"use client\";\nimport { useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\n\nconst StyledAccordion = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 20px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n width: 100%;\n`;\n\nconst StyledAccordionTitle = styled.h3<{ theme: Theme; $isOpen: boolean }>`\n cursor: pointer;\n margin: 0;\n padding: 0 40px 0 0;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.primary};\n transition: color 0.3s ease;\n position: relative;\n\n &:hover {\n color: ${({ theme }) => theme.colors.primaryDark};\n }\n\n & .lucide-chevron-down {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n right: 0;\n transition: transform 0.3s ease;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n transform: translateY(-50%) rotate(180deg);\n `}\n }\n`;\n\nconst StyledAccordionContent = styled.div<{ theme: Theme; $isOpen: boolean }>`\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.grayDark};\n height: 0;\n overflow: clip;\n transition: all 0.3s ease;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n\n ${({ $isOpen }) =>\n $isOpen &&\n css`\n margin: 20px 0 0;\n height: auto;\n `}\n`;\n\nexport interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n title: string;\n}\n\nfunction Accordion({ children, title }: AccordionProps) {\n const [isOpen, setIsOpen] = useState(false);\n\n return (\n <StyledAccordion>\n <StyledAccordionTitle\n onClick={() => setIsOpen(!isOpen)}\n $isOpen={isOpen}\n role=\"button\"\n aria-expanded={isOpen}\n >\n {title} <Icon name=\"ChevronDown\" />\n </StyledAccordionTitle>\n <StyledAccordionContent $isOpen={isOpen}>\n {children}\n </StyledAccordionContent>\n </StyledAccordion>\n );\n}\n\nexport { Accordion };\n";
@@ -72,7 +72,12 @@ function Accordion({ children, title }: AccordionProps) {
72
72
 
73
73
  return (
74
74
  <StyledAccordion>
75
- <StyledAccordionTitle onClick={() => setIsOpen(!isOpen)} $isOpen={isOpen}>
75
+ <StyledAccordionTitle
76
+ onClick={() => setIsOpen(!isOpen)}
77
+ $isOpen={isOpen}
78
+ role="button"
79
+ aria-expanded={isOpen}
80
+ >
76
81
  {title} <Icon name="ChevronDown" />
77
82
  </StyledAccordionTitle>
78
83
  <StyledAccordionContent $isOpen={isOpen}>
@@ -1 +1 @@
1
- export declare const docsNavigationTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst StyledNavigationWrapper = styled.div<{\n $isChatOpen?: boolean;\n}>`\n transition: all 0.3s ease;\n padding: 0 20px 100px 20px;\n ${mq(\"lg\")} {\n padding: 0 340px 80px 340px;\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 340px;\n `}\n }\n`;\n\nconst StyledNavigationInner = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 20px;\n max-width: 640px;\n margin: auto;\n`;\n\nconst StyledNavButton = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n display: flex;\n flex-direction: column;\n text-decoration: none;\n padding: 20px;\n flex: 50%;\n max-width: 50%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.dark};\n \n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &[data-direction=\"prev\"] {\n align-items: flex-start;\n }\n\n &[data-direction=\"next\"] {\n align-items: flex-end;\n margin-left: auto;\n text-align: right;\n }\n`;\n\nconst StyledNavLabel = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.gray};\n display: flex;\n flex-direction: row;\n gap: 4px;\n\n & svg {\n margin: auto 0;\n }\n`;\n\nconst StyledNavTitle = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n margin: 0 0 4px 0;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n max-width: 100%;\n`;\n\nconst StyledSpacer = styled.div`\n flex: 1;\n`;\n\ninterface Page {\n slug: string;\n title: string;\n category?: string;\n [key: string]: unknown;\n}\n\ninterface NavigationItem {\n category?: string;\n slug?: string;\n title?: string;\n links?: Page[];\n items?: Page[];\n [key: string]: unknown;\n}\n\ninterface DocsNavigationProps {\n result: NavigationItem[];\n}\n\nfunction DocsNavigation({ result }: DocsNavigationProps) {\n const { isOpen } = useContext(ChatContext);\n const pathname = usePathname();\n const allPages: Page[] = result.flatMap((item) => {\n if (item.links && Array.isArray(item.links)) {\n return item.links;\n }\n if (item.items && Array.isArray(item.items)) {\n return item.items;\n }\n if (item.slug !== undefined) {\n return [item as Page];\n }\n return [];\n });\n const currentSlug = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n const currentIndex = allPages.findIndex((page) => page.slug === currentSlug);\n const prevPage = currentIndex > 0 ? allPages[currentIndex - 1] : null;\n const nextPage =\n currentIndex < allPages.length - 1 ? allPages[currentIndex + 1] : null;\n if (currentIndex === -1 || allPages.length === 0) {\n return null;\n }\n if (!prevPage && !nextPage) {\n return null;\n }\n return (\n <StyledNavigationWrapper $isChatOpen={isOpen}>\n <StyledNavigationInner>\n {prevPage ? (\n <StyledNavButton href={`/${prevPage.slug}`} data-direction=\"prev\">\n <StyledNavTitle>{prevPage.title}</StyledNavTitle>\n <StyledNavLabel>\n <Icon name=\"arrow-left\" size={16} /> Previous\n </StyledNavLabel>\n </StyledNavButton>\n ) : (\n <StyledSpacer />\n )}\n {nextPage && (\n <StyledNavButton href={`/${nextPage.slug}`} data-direction=\"next\">\n <StyledNavTitle>{nextPage.title}</StyledNavTitle>\n <StyledNavLabel>\n Next <Icon name=\"arrow-right\" size={16} />\n </StyledNavLabel>\n </StyledNavButton>\n )}\n </StyledNavigationInner>\n </StyledNavigationWrapper>\n );\n}\n\nexport { DocsNavigation };\n";
1
+ export declare const docsNavigationTemplate = "\"use client\";\nimport { useContext } from \"react\";\nimport { usePathname } from \"next/navigation\";\nimport Link from \"next/link\";\nimport styled, { css } from \"styled-components\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { ChatContext } from \"@/components/Chat\";\n\nconst StyledNavigationWrapper = styled.div<{\n $isChatOpen?: boolean;\n}>`\n transition: all 0.3s ease;\n padding: 0 20px 100px 20px;\n ${mq(\"lg\")} {\n padding: 0 340px 80px 340px;\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 80px 340px;\n `}\n }\n`;\n\nconst StyledNavigationInner = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 20px;\n max-width: 640px;\n margin: auto;\n`;\n\nconst StyledNavButton = styled(Link)<{ theme: Theme }>`\n ${interactiveStyles};\n display: flex;\n flex-direction: column;\n text-decoration: none;\n padding: 20px;\n flex: 50%;\n max-width: 50%;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.dark};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &[data-direction=\"prev\"] {\n align-items: flex-start;\n }\n\n &[data-direction=\"next\"] {\n align-items: flex-end;\n margin-left: auto;\n text-align: right;\n }\n`;\n\nconst StyledNavLabel = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.gray};\n display: flex;\n flex-direction: row;\n gap: 4px;\n\n & svg {\n margin: auto 0;\n }\n`;\n\nconst StyledNavTitle = styled.span<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n margin: 0 0 4px 0;\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n max-width: 100%;\n`;\n\nconst StyledSpacer = styled.div`\n flex: 1;\n`;\n\ninterface Page {\n slug: string;\n title: string;\n category?: string;\n [key: string]: unknown;\n}\n\ninterface NavigationItem {\n category?: string;\n slug?: string;\n title?: string;\n links?: Page[];\n items?: Page[];\n [key: string]: unknown;\n}\n\ninterface DocsNavigationProps {\n result: NavigationItem[];\n}\n\nfunction DocsNavigation({ result }: DocsNavigationProps) {\n const { isOpen } = useContext(ChatContext);\n const pathname = usePathname();\n const allPages: Page[] = result.flatMap((item) => {\n if (item.links && Array.isArray(item.links)) {\n return item.links;\n }\n if (item.items && Array.isArray(item.items)) {\n return item.items;\n }\n if (item.slug !== undefined) {\n return [item as Page];\n }\n return [];\n });\n const currentSlug = pathname.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n const currentIndex = allPages.findIndex((page) => page.slug === currentSlug);\n const prevPage = currentIndex > 0 ? allPages[currentIndex - 1] : null;\n const nextPage =\n currentIndex < allPages.length - 1 ? allPages[currentIndex + 1] : null;\n if (currentIndex === -1 || allPages.length === 0) {\n return null;\n }\n if (!prevPage && !nextPage) {\n return null;\n }\n return (\n <StyledNavigationWrapper $isChatOpen={isOpen}>\n <StyledNavigationInner>\n {prevPage ? (\n <StyledNavButton href={`/${prevPage.slug}`} data-direction=\"prev\">\n <StyledNavTitle>{prevPage.title}</StyledNavTitle>\n <StyledNavLabel>\n <Icon name=\"arrow-left\" size={16} /> Previous\n </StyledNavLabel>\n </StyledNavButton>\n ) : (\n <StyledSpacer />\n )}\n {nextPage && (\n <StyledNavButton href={`/${nextPage.slug}`} data-direction=\"next\">\n <StyledNavTitle>{nextPage.title}</StyledNavTitle>\n <StyledNavLabel>\n Next <Icon name=\"arrow-right\" size={16} />\n </StyledNavLabel>\n </StyledNavButton>\n )}\n </StyledNavigationInner>\n </StyledNavigationWrapper>\n );\n}\n\nexport { DocsNavigation };\n";
@@ -43,7 +43,7 @@ const StyledNavButton = styled(Link)<{ theme: Theme }>\`
43
43
  border-radius: \${({ theme }) => theme.spacing.radius.lg};
44
44
  border: solid 1px \${({ theme }) => theme.colors.grayLight};
45
45
  color: \${({ theme }) => theme.colors.dark};
46
-
46
+
47
47
  &:hover {
48
48
  border-color: \${({ theme }) => theme.colors.primary};
49
49
  }
@@ -1 +1 @@
1
- export declare const pictogramsTemplate = "\"use client\";\nimport React from \"react\";\n\n\ninterface Props extends React.SVGProps<SVGSVGElement> {\n className?: string;\n}\n\nfunction Logo({ ...props }: Props) {\n return (\n <svg\n width=\"164\"\n height=\"30\"\n viewBox=\"0 0 164 30\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M153.351 22.5766V7.4697H163.531V10.1031H156.545V13.7028H163.007V16.3362H156.545V19.9432H163.56V22.5766H153.351Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M150.712 7.4697V22.5766H147.954L141.381 13.0684H141.271V22.5766H138.077V7.4697H140.88L147.4 16.9705H147.533V7.4697H150.712Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M135.449 7.4697V22.5766H132.255V7.4697H135.449Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M118.806 22.5766V7.4697H124.766C125.912 7.4697 126.888 7.68853 127.694 8.1262C128.501 8.55895 129.115 9.16136 129.538 9.93342C129.966 10.7006 130.18 11.5857 130.18 12.5889C130.18 13.5921 129.964 14.4773 129.531 15.2444C129.098 16.0116 128.471 16.6091 127.65 17.0369C126.834 17.4647 125.845 17.6787 124.685 17.6787H120.886V15.119H124.168C124.783 15.119 125.29 15.0133 125.688 14.8019C126.091 14.5855 126.391 14.288 126.588 13.9093C126.789 13.5257 126.89 13.0856 126.89 12.5889C126.89 12.0873 126.789 11.6497 126.588 11.2759C126.391 10.8973 126.091 10.6047 125.688 10.3981C125.285 10.1867 124.773 10.0809 124.154 10.0809H122V22.5766H118.806Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M112.984 7.4697H116.178V17.2803C116.178 18.3819 115.915 19.3457 115.388 20.1719C114.867 20.9981 114.137 21.6423 113.198 22.1045C112.258 22.5619 111.164 22.7905 109.915 22.7905C108.661 22.7905 107.565 22.5619 106.625 22.1045C105.686 21.6423 104.956 20.9981 104.434 20.1719C103.913 19.3457 103.653 18.3819 103.653 17.2803V7.4697H106.847V17.0074C106.847 17.5828 106.972 18.0942 107.223 18.5417C107.478 18.9892 107.837 19.3408 108.3 19.5965C108.762 19.8523 109.3 19.9801 109.915 19.9801C110.535 19.9801 111.073 19.8523 111.531 19.5965C111.993 19.3408 112.349 18.9892 112.6 18.5417C112.856 18.0942 112.984 17.5828 112.984 17.0074V7.4697Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M101.362 12.7586H98.1313C98.0723 12.3406 97.9518 11.9693 97.7699 11.6448C97.5879 11.3153 97.3543 11.035 97.0691 10.8038C96.7839 10.5727 96.4544 10.3957 96.0807 10.2727C95.7119 10.1498 95.3111 10.0883 94.8783 10.0883C94.0964 10.0883 93.4153 10.2826 92.8351 10.6711C92.2548 11.0546 91.8048 11.6152 91.4852 12.3529C91.1655 13.0856 91.0057 13.9757 91.0057 15.0232C91.0057 16.1001 91.1655 17.005 91.4852 17.7377C91.8097 18.4704 92.2622 19.0236 92.8424 19.3974C93.4227 19.7711 94.094 19.958 94.8562 19.958C95.284 19.958 95.6799 19.9014 96.0438 19.7883C96.4126 19.6752 96.7396 19.5105 97.0249 19.2941C97.3101 19.0728 97.5461 18.8048 97.733 18.4901C97.9248 18.1753 98.0576 17.8164 98.1313 17.4131L101.362 17.4279C101.279 18.1213 101.07 18.79 100.735 19.4343C100.406 20.0735 99.9607 20.6464 99.4001 21.153C98.8444 21.6546 98.1805 22.0529 97.4084 22.3479C96.6413 22.6381 95.7733 22.7832 94.8046 22.7832C93.4571 22.7832 92.2523 22.4783 91.1901 21.8685C90.1328 21.2587 89.2968 20.376 88.6821 19.2203C88.0723 18.0647 87.7675 16.6656 87.7675 15.0232C87.7675 13.3758 88.0773 11.9742 88.6969 10.8186C89.3165 9.66296 90.1574 8.7827 91.2196 8.17784C92.2818 7.56805 93.4768 7.26316 94.8046 7.26316C95.6799 7.26316 96.4913 7.3861 97.2388 7.63198C97.9912 7.87786 98.6575 8.23685 99.2378 8.70894C99.8181 9.17611 100.29 9.74901 100.654 10.4276C101.023 11.1063 101.259 11.8833 101.362 12.7586Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M85.7427 12.7586H82.5118C82.4528 12.3406 82.3323 11.9693 82.1504 11.6448C81.9684 11.3153 81.7348 11.035 81.4496 10.8038C81.1644 10.5727 80.8349 10.3957 80.4612 10.2727C80.0924 10.1498 79.6916 10.0883 79.2588 10.0883C78.4769 10.0883 77.7958 10.2826 77.2156 10.6711C76.6353 11.0546 76.1853 11.6152 75.8657 12.3529C75.546 13.0856 75.3862 13.9757 75.3862 15.0232C75.3862 16.1001 75.546 17.005 75.8657 17.7377C76.1902 18.4704 76.6427 19.0236 77.2229 19.3974C77.8032 19.7711 78.4745 19.958 79.2367 19.958C79.6645 19.958 80.0604 19.9014 80.4243 19.7883C80.7931 19.6752 81.1201 19.5105 81.4054 19.2941C81.6906 19.0728 81.9266 18.8048 82.1135 18.4901C82.3053 18.1753 82.4381 17.8164 82.5118 17.4131L85.7427 17.4279C85.6591 18.1213 85.4501 18.79 85.1157 19.4343C84.7862 20.0735 84.3412 20.6464 83.7806 21.153C83.2249 21.6546 82.561 22.0529 81.7889 22.3479C81.0218 22.6381 80.1538 22.7832 79.1851 22.7832C77.8376 22.7832 76.6328 22.4783 75.5706 21.8685C74.5133 21.2587 73.6773 20.376 73.0626 19.2203C72.4528 18.0647 72.1479 16.6656 72.1479 15.0232C72.1479 13.3758 72.4578 11.9742 73.0774 10.8186C73.697 9.66296 74.5379 8.7827 75.6001 8.17784C76.6623 7.56805 77.8573 7.26316 79.1851 7.26316C80.0604 7.26316 80.8718 7.3861 81.6193 7.63198C82.3717 7.87786 83.038 8.23685 83.6183 8.70894C84.1986 9.17611 84.6707 9.74901 85.0346 10.4276C85.4034 11.1063 85.6394 11.8833 85.7427 12.7586Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M70.0475 15.0232C70.0475 16.6706 69.7352 18.0721 69.1107 19.2277C68.4911 20.3834 67.6453 21.2661 66.5732 21.8759C65.5061 22.4807 64.3062 22.7832 62.9735 22.7832C61.631 22.7832 60.4262 22.4783 59.3591 21.8685C58.292 21.2587 57.4486 20.376 56.829 19.2203C56.2093 18.0647 55.8995 16.6656 55.8995 15.0232C55.8995 13.3758 56.2093 11.9742 56.829 10.8186C57.4486 9.66296 58.292 8.7827 59.3591 8.17784C60.4262 7.56805 61.631 7.26316 62.9735 7.26316C64.3062 7.26316 65.5061 7.56805 66.5732 8.17784C67.6453 8.7827 68.4911 9.66296 69.1107 10.8186C69.7352 11.9742 70.0475 13.3758 70.0475 15.0232ZM66.8093 15.0232C66.8093 13.956 66.6494 13.0561 66.3298 12.3234C66.0151 11.5907 65.57 11.035 64.9947 10.6563C64.4193 10.2777 63.7456 10.0883 62.9735 10.0883C62.2015 10.0883 61.5277 10.2777 60.9524 10.6563C60.377 11.035 59.9295 11.5907 59.6099 12.3234C59.2951 13.0561 59.1378 13.956 59.1378 15.0232C59.1378 16.0903 59.2951 16.9902 59.6099 17.7229C59.9295 18.4557 60.377 19.0113 60.9524 19.39C61.5277 19.7687 62.2015 19.958 62.9735 19.958C63.7456 19.958 64.4193 19.7687 64.9947 19.39C65.57 19.0113 66.0151 18.4557 66.3298 17.7229C66.6494 16.9902 66.8093 16.0903 66.8093 15.0232Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M46.4079 22.5766H41.0526V7.4697H46.4522C47.9717 7.4697 49.2798 7.77213 50.3764 8.377C51.473 8.97694 52.3164 9.83999 52.9065 10.9661C53.5016 12.0923 53.7991 13.4397 53.7991 15.0084C53.7991 16.582 53.5016 17.9344 52.9065 19.0654C52.3164 20.1965 51.4681 21.0644 50.3617 21.6693C49.2601 22.2742 47.9422 22.5766 46.4079 22.5766ZM44.2466 19.84H46.2751C47.2193 19.84 48.0135 19.6728 48.6577 19.3384C49.3068 18.999 49.7937 18.4753 50.1182 17.7672C50.4477 17.0541 50.6125 16.1345 50.6125 15.0084C50.6125 13.8921 50.4477 12.9799 50.1182 12.2717C49.7937 11.5636 49.3093 11.0423 48.6651 10.7079C48.0209 10.3735 47.2267 10.2063 46.2825 10.2063H44.2466V19.84Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M15 0C23.2843 0 30 6.71573 30 15C30 23.2843 23.2843 30 15 30C6.71573 30 0 23.2843 0 15C0 6.71573 6.71573 5.1544e-07 15 0ZM17.46 6.63281C17.3329 6.6583 17.2664 6.76163 17.2344 6.86426C17.2026 6.96625 17.1945 7.09548 17.2002 7.23633C17.2118 7.52094 17.2808 7.89734 17.3779 8.30176C17.5178 8.88356 17.7201 9.53957 17.9082 10.0967C17.3504 9.47631 16.6424 8.72017 15.9775 8.0752C15.5538 7.66414 15.1444 7.29572 14.8018 7.03516C14.6307 6.9051 14.4724 6.79901 14.335 6.72852C14.2041 6.66146 14.0675 6.61313 13.9492 6.63281C13.8849 6.64355 13.8274 6.6734 13.7852 6.72461C13.7448 6.77375 13.7249 6.83378 13.7168 6.8916C13.7012 7.00406 13.7248 7.14351 13.7656 7.29199C13.8488 7.59395 14.0254 7.99367 14.2441 8.42383C14.588 9.09999 15.0456 9.87052 15.4395 10.4961C14.6276 9.98813 13.5693 9.37252 12.5928 8.90137C12.0015 8.6161 11.4342 8.38126 10.9678 8.25586C10.735 8.19331 10.521 8.15609 10.3389 8.15625C10.1589 8.15649 9.99165 8.19337 9.87109 8.29688C9.74997 8.40095 9.70176 8.53855 9.71289 8.68848C9.7235 8.83104 9.78668 8.98219 9.87793 9.13281C10.0612 9.4352 10.385 9.7802 10.7656 10.127C11.2592 10.5766 11.8635 11.0425 12.4219 11.4404C11.614 11.1521 10.6656 10.8394 9.79688 10.6074C9.17224 10.4406 8.58351 10.3143 8.11719 10.2676C7.88462 10.2443 7.67649 10.2402 7.50684 10.2637C7.34178 10.2865 7.1881 10.3389 7.09473 10.4512C6.99513 10.5711 6.99361 10.7136 7.04688 10.8467C7.09752 10.9731 7.20024 11.1014 7.3291 11.2275C7.58899 11.482 7.9953 11.7644 8.45996 12.0449C9.14977 12.4613 9.98572 12.884 10.7051 13.2207C9.89668 13.1479 8.93696 13.0925 8.1084 13.1074C7.55332 13.1174 7.0472 13.1597 6.68359 13.2529C6.50321 13.2992 6.34583 13.3613 6.23438 13.4473C6.11815 13.537 6.04297 13.6616 6.0625 13.8184C6.09513 14.079 6.30546 14.286 6.56738 14.4482C6.83507 14.614 7.18869 14.754 7.56836 14.8701C8.09797 15.032 8.692 15.1512 9.20312 15.2305C8.54341 15.4231 7.73337 15.6854 6.98828 15.9785C6.49407 16.173 6.02484 16.382 5.64648 16.5947C5.2741 16.8041 4.96906 17.0282 4.8252 17.2588C4.75164 17.3767 4.71767 17.5019 4.75879 17.6221C4.79906 17.739 4.8992 17.8159 5.00781 17.8672C5.22324 17.9688 5.56067 18.0105 5.93262 18.0205C6.52855 18.0364 7.26666 17.9713 7.88184 17.8916C7.33281 18.3337 6.65515 18.8997 6.08984 19.415C5.74179 19.7324 5.43264 20.0338 5.22266 20.2764C5.11838 20.3969 5.03364 20.5087 4.98145 20.6035C4.9556 20.6505 4.93442 20.7003 4.9248 20.749C4.91531 20.7976 4.91453 20.8611 4.95215 20.918C4.97838 20.9574 5.01587 20.9879 5.05176 21.0107C5.08893 21.0344 5.13311 21.0557 5.18066 21.0752C5.27578 21.1143 5.39716 21.1506 5.53516 21.1846C5.81212 21.2526 6.17221 21.3144 6.55859 21.3691C7.30406 21.4747 8.16195 21.5543 8.74902 21.5977C9.20157 22.3369 10.2013 23.3651 12.2119 23.3652C13.418 23.3652 14.1421 23.1765 14.5498 22.8857C14.7566 22.7382 14.8822 22.5635 14.9453 22.377C15.0079 22.1918 15.0062 22.0031 14.9717 21.8301C14.9394 21.6689 14.8413 21.534 14.7197 21.4238C14.5977 21.3133 14.4438 21.22 14.2842 21.1436C14.2181 21.1119 14.1494 21.0835 14.0811 21.0566L17.5273 20.8535C17.6778 21.2254 17.9076 21.6963 18.1963 22.0869C18.5019 22.5002 18.901 22.8611 19.3672 22.8613C19.8718 22.8613 20.3874 22.8306 20.7793 22.7324C20.9744 22.6835 21.1493 22.6145 21.2783 22.5176C21.411 22.4178 21.502 22.2817 21.502 22.1064C21.5018 21.9074 21.386 21.6903 21.2471 21.4824C21.1049 21.2697 20.9155 21.0353 20.7383 20.8037C20.5582 20.5682 20.3876 20.3316 20.2695 20.1025C20.1509 19.8723 20.0932 19.6646 20.1191 19.4824C20.143 19.3154 20.2566 19.1777 20.4639 19.0537C20.6723 18.9291 20.9568 18.8304 21.2852 18.7363C21.4483 18.6896 21.6195 18.644 21.7949 18.5977C21.9697 18.5515 22.149 18.5047 22.3252 18.4531C22.6769 18.3501 23.0255 18.2286 23.3203 18.0645C23.9018 17.7406 24.3856 17.288 24.7246 16.835C25.06 16.3867 25.2676 15.9185 25.2676 15.5635C25.2674 15.4487 25.2136 15.3274 25.1406 15.2109C25.0654 15.0908 24.9581 14.9587 24.8301 14.8174C24.5737 14.5343 24.2189 14.2013 23.8232 13.8408C23.0277 13.116 22.0623 12.2734 21.3447 11.4551C20.5698 10.5712 19.7593 9.35811 19.0732 8.36914C18.732 7.87727 18.4198 7.43917 18.1611 7.12988C18.0322 6.97574 17.9119 6.84807 17.8047 6.76172C17.7513 6.71875 17.6968 6.68283 17.6426 6.65918C17.5887 6.63571 17.5253 6.61978 17.46 6.63281ZM20.623 14.0547C21.0389 14.0548 21.376 14.3362 21.376 14.6836C21.3758 15.0309 21.0388 15.3124 20.623 15.3125C20.2072 15.3125 19.8703 15.0309 19.8701 14.6836C19.8701 14.3362 20.2071 14.0547 20.623 14.0547Z\"\n fill=\"#3B82F6\"\n />\n </svg>\n );\n}\n\nfunction GitHubLogo({ ...props }: Props) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <g clipPath=\"url(#clip0_7690_51)\">\n <path\n d=\"M5.43437 12.4187C5.43437 12.4812 5.3625 12.5312 5.27187 12.5312C5.16875 12.5406 5.09688 12.4906 5.09688 12.4187C5.09688 12.3562 5.16875 12.3062 5.25938 12.3062C5.35313 12.2969 5.43437 12.3469 5.43437 12.4187ZM4.4625 12.2781C4.44063 12.3406 4.50313 12.4125 4.59688 12.4312C4.67813 12.4625 4.77188 12.4312 4.79063 12.3687C4.80938 12.3062 4.75 12.2344 4.65625 12.2063C4.575 12.1844 4.48438 12.2156 4.4625 12.2781ZM5.84375 12.225C5.75312 12.2469 5.69062 12.3063 5.7 12.3781C5.70937 12.4406 5.79063 12.4813 5.88438 12.4594C5.975 12.4375 6.0375 12.3781 6.02812 12.3156C6.01875 12.2563 5.93438 12.2156 5.84375 12.225ZM7.9 0.25C3.56563 0.25 0.25 3.54063 0.25 7.875C0.25 11.3406 2.43125 14.3063 5.54688 15.35C5.94688 15.4219 6.0875 15.175 6.0875 14.9719C6.0875 14.7781 6.07812 13.7094 6.07812 13.0531C6.07812 13.0531 3.89063 13.5219 3.43125 12.1219C3.43125 12.1219 3.075 11.2125 2.5625 10.9781C2.5625 10.9781 1.84687 10.4875 2.6125 10.4969C2.6125 10.4969 3.39062 10.5594 3.81875 11.3031C4.50312 12.5094 5.65 12.1625 6.09688 11.9563C6.16875 11.4563 6.37188 11.1094 6.59688 10.9031C4.85 10.7094 3.0875 10.4562 3.0875 7.45C3.0875 6.59062 3.325 6.15938 3.825 5.60938C3.74375 5.40625 3.47813 4.56875 3.90625 3.4875C4.55937 3.28437 6.0625 4.33125 6.0625 4.33125C6.6875 4.15625 7.35937 4.06563 8.025 4.06563C8.69062 4.06563 9.3625 4.15625 9.9875 4.33125C9.9875 4.33125 11.4906 3.28125 12.1438 3.4875C12.5719 4.57187 12.3063 5.40625 12.225 5.60938C12.725 6.1625 13.0312 6.59375 13.0312 7.45C13.0312 10.4656 11.1906 10.7062 9.44375 10.9031C9.73125 11.15 9.975 11.6187 9.975 12.3531C9.975 13.4062 9.96562 14.7094 9.96562 14.9656C9.96562 15.1687 10.1094 15.4156 10.5063 15.3438C13.6313 14.3062 15.75 11.3406 15.75 7.875C15.75 3.54063 12.2344 0.25 7.9 0.25ZM3.2875 11.0281C3.24687 11.0594 3.25625 11.1313 3.30938 11.1906C3.35938 11.2406 3.43125 11.2625 3.47187 11.2219C3.5125 11.1906 3.50313 11.1187 3.45 11.0594C3.4 11.0094 3.32812 10.9875 3.2875 11.0281ZM2.95 10.775C2.92813 10.8156 2.95937 10.8656 3.02187 10.8969C3.07187 10.9281 3.13438 10.9187 3.15625 10.875C3.17812 10.8344 3.14687 10.7844 3.08437 10.7531C3.02187 10.7344 2.97188 10.7437 2.95 10.775ZM3.9625 11.8875C3.9125 11.9281 3.93125 12.0219 4.00313 12.0813C4.075 12.1531 4.16563 12.1625 4.20625 12.1125C4.24688 12.0719 4.22813 11.9781 4.16563 11.9187C4.09688 11.8469 4.00313 11.8375 3.9625 11.8875ZM3.60625 11.4281C3.55625 11.4594 3.55625 11.5406 3.60625 11.6125C3.65625 11.6844 3.74063 11.7156 3.78125 11.6844C3.83125 11.6437 3.83125 11.5625 3.78125 11.4906C3.7375 11.4188 3.65625 11.3875 3.60625 11.4281Z\"\n fill=\"currentColor\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_7690_51\">\n <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n </clipPath>\n </defs>\n </svg>\n );\n}\n\nexport { Logo, GitHubLogo };\n";
1
+ export declare const pictogramsTemplate = "\"use client\";\nimport React from \"react\";\n\ninterface Props extends React.SVGProps<SVGSVGElement> {\n className?: string;\n}\n\nfunction Logo({ ...props }: Props) {\n return (\n <svg\n width=\"164\"\n height=\"30\"\n viewBox=\"0 0 164 30\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <path\n d=\"M153.351 22.5766V7.4697H163.531V10.1031H156.545V13.7028H163.007V16.3362H156.545V19.9432H163.56V22.5766H153.351Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M150.712 7.4697V22.5766H147.954L141.381 13.0684H141.271V22.5766H138.077V7.4697H140.88L147.4 16.9705H147.533V7.4697H150.712Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M135.449 7.4697V22.5766H132.255V7.4697H135.449Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M118.806 22.5766V7.4697H124.766C125.912 7.4697 126.888 7.68853 127.694 8.1262C128.501 8.55895 129.115 9.16136 129.538 9.93342C129.966 10.7006 130.18 11.5857 130.18 12.5889C130.18 13.5921 129.964 14.4773 129.531 15.2444C129.098 16.0116 128.471 16.6091 127.65 17.0369C126.834 17.4647 125.845 17.6787 124.685 17.6787H120.886V15.119H124.168C124.783 15.119 125.29 15.0133 125.688 14.8019C126.091 14.5855 126.391 14.288 126.588 13.9093C126.789 13.5257 126.89 13.0856 126.89 12.5889C126.89 12.0873 126.789 11.6497 126.588 11.2759C126.391 10.8973 126.091 10.6047 125.688 10.3981C125.285 10.1867 124.773 10.0809 124.154 10.0809H122V22.5766H118.806Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M112.984 7.4697H116.178V17.2803C116.178 18.3819 115.915 19.3457 115.388 20.1719C114.867 20.9981 114.137 21.6423 113.198 22.1045C112.258 22.5619 111.164 22.7905 109.915 22.7905C108.661 22.7905 107.565 22.5619 106.625 22.1045C105.686 21.6423 104.956 20.9981 104.434 20.1719C103.913 19.3457 103.653 18.3819 103.653 17.2803V7.4697H106.847V17.0074C106.847 17.5828 106.972 18.0942 107.223 18.5417C107.478 18.9892 107.837 19.3408 108.3 19.5965C108.762 19.8523 109.3 19.9801 109.915 19.9801C110.535 19.9801 111.073 19.8523 111.531 19.5965C111.993 19.3408 112.349 18.9892 112.6 18.5417C112.856 18.0942 112.984 17.5828 112.984 17.0074V7.4697Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M101.362 12.7586H98.1313C98.0723 12.3406 97.9518 11.9693 97.7699 11.6448C97.5879 11.3153 97.3543 11.035 97.0691 10.8038C96.7839 10.5727 96.4544 10.3957 96.0807 10.2727C95.7119 10.1498 95.3111 10.0883 94.8783 10.0883C94.0964 10.0883 93.4153 10.2826 92.8351 10.6711C92.2548 11.0546 91.8048 11.6152 91.4852 12.3529C91.1655 13.0856 91.0057 13.9757 91.0057 15.0232C91.0057 16.1001 91.1655 17.005 91.4852 17.7377C91.8097 18.4704 92.2622 19.0236 92.8424 19.3974C93.4227 19.7711 94.094 19.958 94.8562 19.958C95.284 19.958 95.6799 19.9014 96.0438 19.7883C96.4126 19.6752 96.7396 19.5105 97.0249 19.2941C97.3101 19.0728 97.5461 18.8048 97.733 18.4901C97.9248 18.1753 98.0576 17.8164 98.1313 17.4131L101.362 17.4279C101.279 18.1213 101.07 18.79 100.735 19.4343C100.406 20.0735 99.9607 20.6464 99.4001 21.153C98.8444 21.6546 98.1805 22.0529 97.4084 22.3479C96.6413 22.6381 95.7733 22.7832 94.8046 22.7832C93.4571 22.7832 92.2523 22.4783 91.1901 21.8685C90.1328 21.2587 89.2968 20.376 88.6821 19.2203C88.0723 18.0647 87.7675 16.6656 87.7675 15.0232C87.7675 13.3758 88.0773 11.9742 88.6969 10.8186C89.3165 9.66296 90.1574 8.7827 91.2196 8.17784C92.2818 7.56805 93.4768 7.26316 94.8046 7.26316C95.6799 7.26316 96.4913 7.3861 97.2388 7.63198C97.9912 7.87786 98.6575 8.23685 99.2378 8.70894C99.8181 9.17611 100.29 9.74901 100.654 10.4276C101.023 11.1063 101.259 11.8833 101.362 12.7586Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M85.7427 12.7586H82.5118C82.4528 12.3406 82.3323 11.9693 82.1504 11.6448C81.9684 11.3153 81.7348 11.035 81.4496 10.8038C81.1644 10.5727 80.8349 10.3957 80.4612 10.2727C80.0924 10.1498 79.6916 10.0883 79.2588 10.0883C78.4769 10.0883 77.7958 10.2826 77.2156 10.6711C76.6353 11.0546 76.1853 11.6152 75.8657 12.3529C75.546 13.0856 75.3862 13.9757 75.3862 15.0232C75.3862 16.1001 75.546 17.005 75.8657 17.7377C76.1902 18.4704 76.6427 19.0236 77.2229 19.3974C77.8032 19.7711 78.4745 19.958 79.2367 19.958C79.6645 19.958 80.0604 19.9014 80.4243 19.7883C80.7931 19.6752 81.1201 19.5105 81.4054 19.2941C81.6906 19.0728 81.9266 18.8048 82.1135 18.4901C82.3053 18.1753 82.4381 17.8164 82.5118 17.4131L85.7427 17.4279C85.6591 18.1213 85.4501 18.79 85.1157 19.4343C84.7862 20.0735 84.3412 20.6464 83.7806 21.153C83.2249 21.6546 82.561 22.0529 81.7889 22.3479C81.0218 22.6381 80.1538 22.7832 79.1851 22.7832C77.8376 22.7832 76.6328 22.4783 75.5706 21.8685C74.5133 21.2587 73.6773 20.376 73.0626 19.2203C72.4528 18.0647 72.1479 16.6656 72.1479 15.0232C72.1479 13.3758 72.4578 11.9742 73.0774 10.8186C73.697 9.66296 74.5379 8.7827 75.6001 8.17784C76.6623 7.56805 77.8573 7.26316 79.1851 7.26316C80.0604 7.26316 80.8718 7.3861 81.6193 7.63198C82.3717 7.87786 83.038 8.23685 83.6183 8.70894C84.1986 9.17611 84.6707 9.74901 85.0346 10.4276C85.4034 11.1063 85.6394 11.8833 85.7427 12.7586Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M70.0475 15.0232C70.0475 16.6706 69.7352 18.0721 69.1107 19.2277C68.4911 20.3834 67.6453 21.2661 66.5732 21.8759C65.5061 22.4807 64.3062 22.7832 62.9735 22.7832C61.631 22.7832 60.4262 22.4783 59.3591 21.8685C58.292 21.2587 57.4486 20.376 56.829 19.2203C56.2093 18.0647 55.8995 16.6656 55.8995 15.0232C55.8995 13.3758 56.2093 11.9742 56.829 10.8186C57.4486 9.66296 58.292 8.7827 59.3591 8.17784C60.4262 7.56805 61.631 7.26316 62.9735 7.26316C64.3062 7.26316 65.5061 7.56805 66.5732 8.17784C67.6453 8.7827 68.4911 9.66296 69.1107 10.8186C69.7352 11.9742 70.0475 13.3758 70.0475 15.0232ZM66.8093 15.0232C66.8093 13.956 66.6494 13.0561 66.3298 12.3234C66.0151 11.5907 65.57 11.035 64.9947 10.6563C64.4193 10.2777 63.7456 10.0883 62.9735 10.0883C62.2015 10.0883 61.5277 10.2777 60.9524 10.6563C60.377 11.035 59.9295 11.5907 59.6099 12.3234C59.2951 13.0561 59.1378 13.956 59.1378 15.0232C59.1378 16.0903 59.2951 16.9902 59.6099 17.7229C59.9295 18.4557 60.377 19.0113 60.9524 19.39C61.5277 19.7687 62.2015 19.958 62.9735 19.958C63.7456 19.958 64.4193 19.7687 64.9947 19.39C65.57 19.0113 66.0151 18.4557 66.3298 17.7229C66.6494 16.9902 66.8093 16.0903 66.8093 15.0232Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M46.4079 22.5766H41.0526V7.4697H46.4522C47.9717 7.4697 49.2798 7.77213 50.3764 8.377C51.473 8.97694 52.3164 9.83999 52.9065 10.9661C53.5016 12.0923 53.7991 13.4397 53.7991 15.0084C53.7991 16.582 53.5016 17.9344 52.9065 19.0654C52.3164 20.1965 51.4681 21.0644 50.3617 21.6693C49.2601 22.2742 47.9422 22.5766 46.4079 22.5766ZM44.2466 19.84H46.2751C47.2193 19.84 48.0135 19.6728 48.6577 19.3384C49.3068 18.999 49.7937 18.4753 50.1182 17.7672C50.4477 17.0541 50.6125 16.1345 50.6125 15.0084C50.6125 13.8921 50.4477 12.9799 50.1182 12.2717C49.7937 11.5636 49.3093 11.0423 48.6651 10.7079C48.0209 10.3735 47.2267 10.2063 46.2825 10.2063H44.2466V19.84Z\"\n fill=\"#3B82F6\"\n />\n <path\n d=\"M15 0C23.2843 0 30 6.71573 30 15C30 23.2843 23.2843 30 15 30C6.71573 30 0 23.2843 0 15C0 6.71573 6.71573 5.1544e-07 15 0ZM17.46 6.63281C17.3329 6.6583 17.2664 6.76163 17.2344 6.86426C17.2026 6.96625 17.1945 7.09548 17.2002 7.23633C17.2118 7.52094 17.2808 7.89734 17.3779 8.30176C17.5178 8.88356 17.7201 9.53957 17.9082 10.0967C17.3504 9.47631 16.6424 8.72017 15.9775 8.0752C15.5538 7.66414 15.1444 7.29572 14.8018 7.03516C14.6307 6.9051 14.4724 6.79901 14.335 6.72852C14.2041 6.66146 14.0675 6.61313 13.9492 6.63281C13.8849 6.64355 13.8274 6.6734 13.7852 6.72461C13.7448 6.77375 13.7249 6.83378 13.7168 6.8916C13.7012 7.00406 13.7248 7.14351 13.7656 7.29199C13.8488 7.59395 14.0254 7.99367 14.2441 8.42383C14.588 9.09999 15.0456 9.87052 15.4395 10.4961C14.6276 9.98813 13.5693 9.37252 12.5928 8.90137C12.0015 8.6161 11.4342 8.38126 10.9678 8.25586C10.735 8.19331 10.521 8.15609 10.3389 8.15625C10.1589 8.15649 9.99165 8.19337 9.87109 8.29688C9.74997 8.40095 9.70176 8.53855 9.71289 8.68848C9.7235 8.83104 9.78668 8.98219 9.87793 9.13281C10.0612 9.4352 10.385 9.7802 10.7656 10.127C11.2592 10.5766 11.8635 11.0425 12.4219 11.4404C11.614 11.1521 10.6656 10.8394 9.79688 10.6074C9.17224 10.4406 8.58351 10.3143 8.11719 10.2676C7.88462 10.2443 7.67649 10.2402 7.50684 10.2637C7.34178 10.2865 7.1881 10.3389 7.09473 10.4512C6.99513 10.5711 6.99361 10.7136 7.04688 10.8467C7.09752 10.9731 7.20024 11.1014 7.3291 11.2275C7.58899 11.482 7.9953 11.7644 8.45996 12.0449C9.14977 12.4613 9.98572 12.884 10.7051 13.2207C9.89668 13.1479 8.93696 13.0925 8.1084 13.1074C7.55332 13.1174 7.0472 13.1597 6.68359 13.2529C6.50321 13.2992 6.34583 13.3613 6.23438 13.4473C6.11815 13.537 6.04297 13.6616 6.0625 13.8184C6.09513 14.079 6.30546 14.286 6.56738 14.4482C6.83507 14.614 7.18869 14.754 7.56836 14.8701C8.09797 15.032 8.692 15.1512 9.20312 15.2305C8.54341 15.4231 7.73337 15.6854 6.98828 15.9785C6.49407 16.173 6.02484 16.382 5.64648 16.5947C5.2741 16.8041 4.96906 17.0282 4.8252 17.2588C4.75164 17.3767 4.71767 17.5019 4.75879 17.6221C4.79906 17.739 4.8992 17.8159 5.00781 17.8672C5.22324 17.9688 5.56067 18.0105 5.93262 18.0205C6.52855 18.0364 7.26666 17.9713 7.88184 17.8916C7.33281 18.3337 6.65515 18.8997 6.08984 19.415C5.74179 19.7324 5.43264 20.0338 5.22266 20.2764C5.11838 20.3969 5.03364 20.5087 4.98145 20.6035C4.9556 20.6505 4.93442 20.7003 4.9248 20.749C4.91531 20.7976 4.91453 20.8611 4.95215 20.918C4.97838 20.9574 5.01587 20.9879 5.05176 21.0107C5.08893 21.0344 5.13311 21.0557 5.18066 21.0752C5.27578 21.1143 5.39716 21.1506 5.53516 21.1846C5.81212 21.2526 6.17221 21.3144 6.55859 21.3691C7.30406 21.4747 8.16195 21.5543 8.74902 21.5977C9.20157 22.3369 10.2013 23.3651 12.2119 23.3652C13.418 23.3652 14.1421 23.1765 14.5498 22.8857C14.7566 22.7382 14.8822 22.5635 14.9453 22.377C15.0079 22.1918 15.0062 22.0031 14.9717 21.8301C14.9394 21.6689 14.8413 21.534 14.7197 21.4238C14.5977 21.3133 14.4438 21.22 14.2842 21.1436C14.2181 21.1119 14.1494 21.0835 14.0811 21.0566L17.5273 20.8535C17.6778 21.2254 17.9076 21.6963 18.1963 22.0869C18.5019 22.5002 18.901 22.8611 19.3672 22.8613C19.8718 22.8613 20.3874 22.8306 20.7793 22.7324C20.9744 22.6835 21.1493 22.6145 21.2783 22.5176C21.411 22.4178 21.502 22.2817 21.502 22.1064C21.5018 21.9074 21.386 21.6903 21.2471 21.4824C21.1049 21.2697 20.9155 21.0353 20.7383 20.8037C20.5582 20.5682 20.3876 20.3316 20.2695 20.1025C20.1509 19.8723 20.0932 19.6646 20.1191 19.4824C20.143 19.3154 20.2566 19.1777 20.4639 19.0537C20.6723 18.9291 20.9568 18.8304 21.2852 18.7363C21.4483 18.6896 21.6195 18.644 21.7949 18.5977C21.9697 18.5515 22.149 18.5047 22.3252 18.4531C22.6769 18.3501 23.0255 18.2286 23.3203 18.0645C23.9018 17.7406 24.3856 17.288 24.7246 16.835C25.06 16.3867 25.2676 15.9185 25.2676 15.5635C25.2674 15.4487 25.2136 15.3274 25.1406 15.2109C25.0654 15.0908 24.9581 14.9587 24.8301 14.8174C24.5737 14.5343 24.2189 14.2013 23.8232 13.8408C23.0277 13.116 22.0623 12.2734 21.3447 11.4551C20.5698 10.5712 19.7593 9.35811 19.0732 8.36914C18.732 7.87727 18.4198 7.43917 18.1611 7.12988C18.0322 6.97574 17.9119 6.84807 17.8047 6.76172C17.7513 6.71875 17.6968 6.68283 17.6426 6.65918C17.5887 6.63571 17.5253 6.61978 17.46 6.63281ZM20.623 14.0547C21.0389 14.0548 21.376 14.3362 21.376 14.6836C21.3758 15.0309 21.0388 15.3124 20.623 15.3125C20.2072 15.3125 19.8703 15.0309 19.8701 14.6836C19.8701 14.3362 20.2071 14.0547 20.623 14.0547Z\"\n fill=\"#3B82F6\"\n />\n </svg>\n );\n}\n\nfunction GitHubLogo({ ...props }: Props) {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n >\n <g clipPath=\"url(#clip0_7690_51)\">\n <path\n d=\"M5.43437 12.4187C5.43437 12.4812 5.3625 12.5312 5.27187 12.5312C5.16875 12.5406 5.09688 12.4906 5.09688 12.4187C5.09688 12.3562 5.16875 12.3062 5.25938 12.3062C5.35313 12.2969 5.43437 12.3469 5.43437 12.4187ZM4.4625 12.2781C4.44063 12.3406 4.50313 12.4125 4.59688 12.4312C4.67813 12.4625 4.77188 12.4312 4.79063 12.3687C4.80938 12.3062 4.75 12.2344 4.65625 12.2063C4.575 12.1844 4.48438 12.2156 4.4625 12.2781ZM5.84375 12.225C5.75312 12.2469 5.69062 12.3063 5.7 12.3781C5.70937 12.4406 5.79063 12.4813 5.88438 12.4594C5.975 12.4375 6.0375 12.3781 6.02812 12.3156C6.01875 12.2563 5.93438 12.2156 5.84375 12.225ZM7.9 0.25C3.56563 0.25 0.25 3.54063 0.25 7.875C0.25 11.3406 2.43125 14.3063 5.54688 15.35C5.94688 15.4219 6.0875 15.175 6.0875 14.9719C6.0875 14.7781 6.07812 13.7094 6.07812 13.0531C6.07812 13.0531 3.89063 13.5219 3.43125 12.1219C3.43125 12.1219 3.075 11.2125 2.5625 10.9781C2.5625 10.9781 1.84687 10.4875 2.6125 10.4969C2.6125 10.4969 3.39062 10.5594 3.81875 11.3031C4.50312 12.5094 5.65 12.1625 6.09688 11.9563C6.16875 11.4563 6.37188 11.1094 6.59688 10.9031C4.85 10.7094 3.0875 10.4562 3.0875 7.45C3.0875 6.59062 3.325 6.15938 3.825 5.60938C3.74375 5.40625 3.47813 4.56875 3.90625 3.4875C4.55937 3.28437 6.0625 4.33125 6.0625 4.33125C6.6875 4.15625 7.35937 4.06563 8.025 4.06563C8.69062 4.06563 9.3625 4.15625 9.9875 4.33125C9.9875 4.33125 11.4906 3.28125 12.1438 3.4875C12.5719 4.57187 12.3063 5.40625 12.225 5.60938C12.725 6.1625 13.0312 6.59375 13.0312 7.45C13.0312 10.4656 11.1906 10.7062 9.44375 10.9031C9.73125 11.15 9.975 11.6187 9.975 12.3531C9.975 13.4062 9.96562 14.7094 9.96562 14.9656C9.96562 15.1687 10.1094 15.4156 10.5063 15.3438C13.6313 14.3062 15.75 11.3406 15.75 7.875C15.75 3.54063 12.2344 0.25 7.9 0.25ZM3.2875 11.0281C3.24687 11.0594 3.25625 11.1313 3.30938 11.1906C3.35938 11.2406 3.43125 11.2625 3.47187 11.2219C3.5125 11.1906 3.50313 11.1187 3.45 11.0594C3.4 11.0094 3.32812 10.9875 3.2875 11.0281ZM2.95 10.775C2.92813 10.8156 2.95937 10.8656 3.02187 10.8969C3.07187 10.9281 3.13438 10.9187 3.15625 10.875C3.17812 10.8344 3.14687 10.7844 3.08437 10.7531C3.02187 10.7344 2.97188 10.7437 2.95 10.775ZM3.9625 11.8875C3.9125 11.9281 3.93125 12.0219 4.00313 12.0813C4.075 12.1531 4.16563 12.1625 4.20625 12.1125C4.24688 12.0719 4.22813 11.9781 4.16563 11.9187C4.09688 11.8469 4.00313 11.8375 3.9625 11.8875ZM3.60625 11.4281C3.55625 11.4594 3.55625 11.5406 3.60625 11.6125C3.65625 11.6844 3.74063 11.7156 3.78125 11.6844C3.83125 11.6437 3.83125 11.5625 3.78125 11.4906C3.7375 11.4188 3.65625 11.3875 3.60625 11.4281Z\"\n fill=\"currentColor\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_7690_51\">\n <rect width=\"16\" height=\"16\" fill=\"currentColor\" />\n </clipPath>\n </defs>\n </svg>\n );\n}\n\nexport { Logo, GitHubLogo };\n";
@@ -1,7 +1,6 @@
1
1
  export const pictogramsTemplate = `"use client";
2
2
  import React from "react";
3
3
 
4
-
5
4
  interface Props extends React.SVGProps<SVGSVGElement> {
6
5
  className?: string;
7
6
  }
@@ -1 +1 @@
1
- export declare const themeToggleTemplate = "\"use client\";\nimport { Theme, resetButton } from \"cherry-styled-components\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { theme as themeLight, themeDark } from \"@/app/theme\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 30px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ theme }) =>\n theme.isDark &&\n css`\n transform: translateX(27px);\n `}\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nfunction ToggleTheme({ $hidden }: { $hidden?: boolean }) {\n const { setTheme } = useThemeOverride();\n const theme = useTheme() as Theme;\n\n return (\n <StyledThemeToggle\n onClick={async () => {\n const nextTheme = theme.isDark ? \"light\" : \"dark\";\n setTheme(nextTheme === \"light\" ? themeLight : themeDark);\n await fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: nextTheme }),\n });\n }}\n $hidden={$hidden}\n aria-label=\"Toggle Theme\"\n >\n <Icon name=\"Sun\" className=\"light\" />\n <Icon name=\"MoonStar\" className=\"dark\" />\n </StyledThemeToggle>\n );\n}\n\nfunction ToggleThemeLoading() {\n return (\n <StyledThemeToggle $hidden aria-label=\"Toggle Theme\">\n <Icon name=\"MoonStar\" className=\"dark\" />\n <Icon name=\"Sun\" className=\"light\" />\n </StyledThemeToggle>\n );\n}\n\nexport { ToggleTheme, ToggleThemeLoading };\n";
1
+ export declare const themeToggleTemplate = "\"use client\";\nimport { Theme, resetButton } from \"cherry-styled-components\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { theme as themeLight, themeDark } from \"@/app/theme\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 56px;\n height: 30px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ theme }) =>\n theme.isDark &&\n css`\n transform: translateX(27px);\n `}\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nfunction ToggleTheme({ $hidden }: { $hidden?: boolean }) {\n const { setTheme } = useThemeOverride();\n const theme = useTheme() as Theme;\n\n return (\n <StyledThemeToggle\n onClick={async () => {\n const nextTheme = theme.isDark ? \"light\" : \"dark\";\n setTheme(nextTheme === \"light\" ? themeLight : themeDark);\n await fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: nextTheme }),\n }).catch((err) => console.error(\"Failed to persist theme:\", err));\n }}\n $hidden={$hidden}\n aria-label=\"Toggle Theme\"\n >\n <Icon name=\"Sun\" className=\"light\" />\n <Icon name=\"MoonStar\" className=\"dark\" />\n </StyledThemeToggle>\n );\n}\n\nfunction ToggleThemeLoading() {\n return (\n <StyledThemeToggle $hidden aria-label=\"Toggle Theme\">\n <Icon name=\"MoonStar\" className=\"dark\" />\n <Icon name=\"Sun\" className=\"light\" />\n </StyledThemeToggle>\n );\n}\n\nexport { ToggleTheme, ToggleThemeLoading };\n";
@@ -64,7 +64,7 @@ const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
64
64
  color: \${({ theme }) =>
65
65
  theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
66
66
 
67
- & svg[stroke] {
67
+ & svg[stroke] {
68
68
  stroke: \${({ theme }) =>
69
69
  theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
70
70
  }
@@ -88,7 +88,7 @@ function ToggleTheme({ $hidden }: { $hidden?: boolean }) {
88
88
  method: "POST",
89
89
  headers: { "Content-Type": "application/json" },
90
90
  body: JSON.stringify({ theme: nextTheme }),
91
- });
91
+ }).catch((err) => console.error("Failed to persist theme:", err));
92
92
  }}
93
93
  $hidden={$hidden}
94
94
  aria-label="Toggle Theme"
@@ -7,6 +7,7 @@ export const packageJsonTemplate = JSON.stringify({
7
7
  build: "next build",
8
8
  start: "next start",
9
9
  lint: "eslint .",
10
+ format: "prettier --write .",
10
11
  },
11
12
  dependencies: {
12
13
  "@langchain/anthropic": "^1.3.18",
@@ -41,4 +42,4 @@ export const packageJsonTemplate = JSON.stringify({
41
42
  prettier: "^3.8.1",
42
43
  typescript: "^5",
43
44
  },
44
- }, null, 2);
45
+ }, null, 2) + "\n";
@@ -0,0 +1 @@
1
+ export declare const prettierignoreTemplate = "node_modules\nconvex/_generated\npackage.json\npackage-lock.json\n";
@@ -0,0 +1,5 @@
1
+ export const prettierignoreTemplate = `node_modules
2
+ convex/_generated
3
+ package.json
4
+ package-lock.json
5
+ `;
@@ -0,0 +1 @@
1
+ export declare const prettierrcTemplate: string;
@@ -0,0 +1,11 @@
1
+ export const prettierrcTemplate = JSON.stringify({
2
+ printWidth: 80,
3
+ useTabs: false,
4
+ tabWidth: 2,
5
+ endOfLine: "lf",
6
+ semi: true,
7
+ singleQuote: false,
8
+ trailingComma: "all",
9
+ bracketSpacing: true,
10
+ arrowParens: "always",
11
+ }, null, 2) + "\n";
@@ -1 +1 @@
1
- export declare const proxyTemplate = "import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\nexport function proxy(req: NextRequest) {\n // API key auth for /api/mcp when DOCS_API_KEY is configured\n if (req.nextUrl.pathname.startsWith(\"/api/mcp\")) {\n const apiKey = process.env.DOCS_API_KEY;\n if (apiKey) {\n const authHeader = req.headers.get(\"authorization\");\n const token = authHeader?.startsWith(\"Bearer \")\n ? authHeader.slice(7)\n : null;\n\n if (token !== apiKey) {\n return NextResponse.json(\n { error: \"Unauthorized\" },\n { status: 401 },\n );\n }\n }\n }\n\n const res = NextResponse.next();\n\n res.headers.set(\"Accept-CH\", \"Sec-CH-Prefers-Color-Scheme\");\n res.headers.set(\"Vary\", \"Sec-CH-Prefers-Color-Scheme\");\n res.headers.set(\"Critical-CH\", \"Sec-CH-Prefers-Color-Scheme\");\n\n const existing = req.cookies.get(\"theme\")?.value;\n const hint = req.headers.get(\"Sec-CH-Prefers-Color-Scheme\");\n\n if (!existing && hint) {\n const value = hint === \"dark\" ? \"dark\" : \"light\";\n res.cookies.set(\"theme\", value, {\n path: \"/\",\n maxAge: 60 * 60 * 24 * 365,\n sameSite: \"lax\",\n });\n }\n\n return res;\n}\n\nexport const config = {\n matcher: [\"/:path*\"],\n};\n";
1
+ export declare const proxyTemplate = "import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\nexport function proxy(req: NextRequest) {\n // API key auth for /api/mcp when DOCS_API_KEY is configured\n if (req.nextUrl.pathname.startsWith(\"/api/mcp\")) {\n const apiKey = process.env.DOCS_API_KEY;\n if (apiKey) {\n const authHeader = req.headers.get(\"authorization\");\n const token = authHeader?.startsWith(\"Bearer \")\n ? authHeader.slice(7)\n : null;\n\n if (token !== apiKey) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n }\n }\n\n const res = NextResponse.next();\n\n res.headers.set(\"Accept-CH\", \"Sec-CH-Prefers-Color-Scheme\");\n res.headers.set(\"Vary\", \"Sec-CH-Prefers-Color-Scheme\");\n res.headers.set(\"Critical-CH\", \"Sec-CH-Prefers-Color-Scheme\");\n\n const existing = req.cookies.get(\"theme\")?.value;\n const hint = req.headers.get(\"Sec-CH-Prefers-Color-Scheme\");\n\n if (!existing && hint) {\n const value = hint === \"dark\" ? \"dark\" : \"light\";\n res.cookies.set(\"theme\", value, {\n path: \"/\",\n maxAge: 60 * 60 * 24 * 365,\n sameSite: \"lax\",\n });\n }\n\n return res;\n}\n\nexport const config = {\n matcher: [\"/:path*\"],\n};\n";
@@ -12,10 +12,7 @@ export function proxy(req: NextRequest) {
12
12
  : null;
13
13
 
14
14
  if (token !== apiKey) {
15
- return NextResponse.json(
16
- { error: "Unauthorized" },
17
- { status: 401 },
18
- );
15
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
19
16
  }
20
17
  }
21
18
  }
@@ -1 +1 @@
1
- export declare const mcpServerTemplate = "import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n listDocs,\n getDoc,\n getAllDocsChunks,\n DOCS_TOOLS,\n} from \"@/services/mcp/tools\";\nimport { getLLMConfig, createEmbeddings } from \"@/services/llm\";\nimport type { DocsChunk } from \"@/services/mcp/types\";\n\n/**\n * In-memory cache for document embeddings.\n * Built once at server startup since docs are static.\n */\nlet docsIndex: {\n ready: boolean;\n building: boolean;\n chunks: (DocsChunk & { embedding: number[] })[];\n} = {\n ready: false,\n building: false,\n chunks: [],\n};\n\n/** Resolves when the initial index build completes */\nlet indexReady: Promise<void> | null = null;\n\n/**\n * Cosine similarity between two vectors\n */\nfunction cosineSim(a: number[], b: number[]): number {\n let dot = 0,\n na = 0,\n nb = 0;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n dot += x * y;\n na += x * x;\n nb += y * y;\n }\n if (na === 0 || nb === 0) return 0;\n return dot / (Math.sqrt(na) * Math.sqrt(nb));\n}\n\n/**\n * Build or rebuild the documentation index\n */\nexport async function buildDocsIndex(force = false): Promise<void> {\n if (docsIndex.building) return;\n if (docsIndex.ready && !force) return;\n\n docsIndex.building = true;\n try {\n const chunks = await getAllDocsChunks();\n\n if (chunks.length === 0) {\n docsIndex.chunks = [];\n docsIndex.ready = true;\n return;\n }\n\n const config = getLLMConfig();\n const embeddings = createEmbeddings(config);\n\n // Process embeddings in small batches to avoid exceeding token limits\n const BATCH_SIZE = 10;\n const texts = chunks.map((c) => c.text);\n const vectors: number[][] = [];\n\n for (let i = 0; i < texts.length; i += BATCH_SIZE) {\n const batch = texts.slice(i, i + BATCH_SIZE);\n const batchVectors = await embeddings.embedDocuments(batch);\n vectors.push(...batchVectors);\n }\n\n docsIndex.chunks = chunks.map((c, i) => ({\n ...c,\n embedding: vectors[i],\n }));\n docsIndex.ready = true;\n } catch (error) {\n // Reset so the next call to ensureDocsIndex retries\n indexReady = null;\n throw error;\n } finally {\n docsIndex.building = false;\n }\n}\n\n/**\n * Ensure the docs index is ready.\n * On first call, triggers the build; subsequent calls wait for the same promise.\n */\nexport async function ensureDocsIndex(force = false): Promise<void> {\n if (force) {\n docsIndex.ready = false;\n docsIndex.chunks = [];\n indexReady = buildDocsIndex();\n return indexReady;\n }\n if (!indexReady) {\n indexReady = buildDocsIndex();\n }\n return indexReady;\n}\n\n// Eagerly start building the index on server startup (docs are static)\nindexReady = buildDocsIndex();\n\n/** Cached embeddings instance for search queries */\nlet cachedEmbeddings: ReturnType<typeof createEmbeddings> | null = null;\n\nfunction getEmbeddings() {\n if (!cachedEmbeddings) {\n cachedEmbeddings = createEmbeddings(getLLMConfig());\n }\n return cachedEmbeddings;\n}\n\n/**\n * Search documents using semantic similarity\n */\nexport async function searchDocs(\n query: string,\n limit = 6,\n): Promise<{ chunk: DocsChunk; score: number }[]> {\n await ensureDocsIndex();\n\n const queryVector = await getEmbeddings().embedQuery(query);\n\n const scored = docsIndex.chunks\n .map((c) => ({\n chunk: { id: c.id, text: c.text, path: c.path, uri: c.uri },\n score: cosineSim(queryVector, c.embedding),\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n\n return scored;\n}\n\n/**\n * Get the current index status\n */\nexport function getIndexStatus(): { ready: boolean; chunkCount: number } {\n return {\n ready: docsIndex.ready,\n chunkCount: docsIndex.chunks.length,\n };\n}\n\n/**\n * Create and configure the MCP server with documentation tools\n */\nexport function createMCPServer(): McpServer {\n const server = new McpServer({\n name: \"docs-server\",\n version: \"1.0.0\",\n });\n\n // Register the search_docs tool\n server.tool(\n \"search_docs\",\n DOCS_TOOLS[0].description,\n {\n query: z\n .string()\n .describe(\"The search query to find relevant documentation\"),\n limit: z\n .number()\n .optional()\n .describe(\"Maximum number of results to return (default: 6)\"),\n },\n async ({ query, limit }) => {\n const results = await searchDocs(query, limit ?? 6);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n results.map(({ chunk, score }) => ({\n path: chunk.path,\n uri: chunk.uri,\n score: score.toFixed(3),\n text: chunk.text,\n })),\n null,\n 2,\n ),\n },\n ],\n };\n },\n );\n\n // Register the get_doc tool\n server.tool(\n \"get_doc\",\n DOCS_TOOLS[1].description,\n {\n path: z.string().describe(\"The file path to the documentation page\"),\n },\n async ({ path }) => {\n const doc = await getDoc({ path });\n if (!doc) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ error: \"Document not found\" }),\n },\n ],\n isError: true,\n };\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(doc, null, 2),\n },\n ],\n };\n },\n );\n\n // Register the list_docs tool\n server.tool(\n \"list_docs\",\n DOCS_TOOLS[2].description,\n {\n directory: z\n .string()\n .optional()\n .describe(\"Optional directory to filter results\"),\n },\n async ({ directory }) => {\n const docs = await listDocs({ directory });\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n docs.map((d) => ({\n name: d.name,\n path: d.path,\n uri: d.uri,\n })),\n null,\n 2,\n ),\n },\n ],\n };\n },\n );\n\n // Register documentation as resources\n server.resource(\"docs://list\", \"docs://list\", async () => {\n const docs = await listDocs();\n return {\n contents: [\n {\n uri: \"docs://list\",\n text: JSON.stringify(\n docs.map((d) => ({ name: d.name, path: d.path, uri: d.uri })),\n null,\n 2,\n ),\n },\n ],\n };\n });\n\n return server;\n}\n";
1
+ export declare const mcpServerTemplate = "import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n listDocs,\n getDoc,\n getAllDocsChunks,\n DOCS_TOOLS,\n} from \"@/services/mcp/tools\";\nimport { getLLMConfig, createEmbeddings } from \"@/services/llm\";\nimport type { DocsChunk } from \"@/services/mcp/types\";\n\n/**\n * In-memory cache for document embeddings.\n * Built once at server startup since docs are static.\n */\nlet docsIndex: {\n ready: boolean;\n building: boolean;\n chunks: (DocsChunk & { embedding: number[] })[];\n} = {\n ready: false,\n building: false,\n chunks: [],\n};\n\n/** Resolves when the initial index build completes */\nlet indexReady: Promise<void> | null = null;\n\n/**\n * Cosine similarity between two vectors\n */\nfunction cosineSim(a: number[], b: number[]): number {\n let dot = 0,\n na = 0,\n nb = 0;\n for (let i = 0; i < a.length; i++) {\n const x = a[i];\n const y = b[i];\n dot += x * y;\n na += x * x;\n nb += y * y;\n }\n if (na === 0 || nb === 0) return 0;\n return dot / (Math.sqrt(na) * Math.sqrt(nb));\n}\n\n/**\n * Build or rebuild the documentation index\n */\nexport async function buildDocsIndex(force = false): Promise<void> {\n if (docsIndex.building) return;\n if (docsIndex.ready && !force) return;\n\n docsIndex.building = true;\n try {\n const chunks = await getAllDocsChunks();\n\n if (chunks.length === 0) {\n docsIndex.chunks = [];\n docsIndex.ready = true;\n return;\n }\n\n const config = getLLMConfig();\n const embeddings = createEmbeddings(config);\n\n // Process embeddings in small batches to avoid exceeding token limits\n const BATCH_SIZE = 10;\n const texts = chunks.map((c) => c.text);\n const vectors: number[][] = [];\n\n for (let i = 0; i < texts.length; i += BATCH_SIZE) {\n const batch = texts.slice(i, i + BATCH_SIZE);\n const batchVectors = await embeddings.embedDocuments(batch);\n vectors.push(...batchVectors);\n }\n\n docsIndex.chunks = chunks.map((c, i) => ({\n ...c,\n embedding: vectors[i],\n }));\n docsIndex.ready = true;\n } catch (error) {\n // Reset so the next call to ensureDocsIndex retries\n indexReady = null;\n throw error;\n } finally {\n docsIndex.building = false;\n }\n}\n\n/**\n * Ensure the docs index is ready.\n * On first call, triggers the build; subsequent calls wait for the same promise.\n */\nexport async function ensureDocsIndex(force = false): Promise<void> {\n if (force) {\n // Wait for any in-flight build before starting a forced rebuild\n if (docsIndex.building && indexReady) {\n await indexReady.catch(() => {});\n }\n docsIndex.ready = false;\n docsIndex.chunks = [];\n indexReady = buildDocsIndex(true);\n return indexReady;\n }\n if (!indexReady) {\n indexReady = buildDocsIndex();\n }\n return indexReady;\n}\n\n// Eagerly start building the index on server startup (docs are static)\nindexReady = buildDocsIndex();\n\n/** Cached embeddings instance for search queries */\nlet cachedEmbeddings: ReturnType<typeof createEmbeddings> | null = null;\n\nfunction getEmbeddings() {\n if (!cachedEmbeddings) {\n cachedEmbeddings = createEmbeddings(getLLMConfig());\n }\n return cachedEmbeddings;\n}\n\n/**\n * Search documents using semantic similarity\n */\nexport async function searchDocs(\n query: string,\n limit = 6,\n): Promise<{ chunk: DocsChunk; score: number }[]> {\n await ensureDocsIndex();\n\n const queryVector = await getEmbeddings().embedQuery(query);\n\n const scored = docsIndex.chunks\n .map((c) => ({\n chunk: { id: c.id, text: c.text, path: c.path, uri: c.uri },\n score: cosineSim(queryVector, c.embedding),\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n\n return scored;\n}\n\n/**\n * Get the current index status\n */\nexport function getIndexStatus(): { ready: boolean; chunkCount: number } {\n return {\n ready: docsIndex.ready,\n chunkCount: docsIndex.chunks.length,\n };\n}\n\n/**\n * Create and configure the MCP server with documentation tools\n */\nexport function createMCPServer(): McpServer {\n const server = new McpServer({\n name: \"docs-server\",\n version: \"1.0.0\",\n });\n\n // Register the search_docs tool\n server.tool(\n \"search_docs\",\n DOCS_TOOLS[0].description,\n {\n query: z\n .string()\n .describe(\"The search query to find relevant documentation\"),\n limit: z\n .number()\n .optional()\n .describe(\"Maximum number of results to return (default: 6)\"),\n },\n async ({ query, limit }) => {\n const results = await searchDocs(query, limit ?? 6);\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n results.map(({ chunk, score }) => ({\n path: chunk.path,\n uri: chunk.uri,\n score: score.toFixed(3),\n text: chunk.text,\n })),\n null,\n 2,\n ),\n },\n ],\n };\n },\n );\n\n // Register the get_doc tool\n server.tool(\n \"get_doc\",\n DOCS_TOOLS[1].description,\n {\n path: z.string().describe(\"The file path to the documentation page\"),\n },\n async ({ path }) => {\n const doc = await getDoc({ path });\n if (!doc) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({ error: \"Document not found\" }),\n },\n ],\n isError: true,\n };\n }\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(doc, null, 2),\n },\n ],\n };\n },\n );\n\n // Register the list_docs tool\n server.tool(\n \"list_docs\",\n DOCS_TOOLS[2].description,\n {\n directory: z\n .string()\n .optional()\n .describe(\"Optional directory to filter results\"),\n },\n async ({ directory }) => {\n const docs = await listDocs({ directory });\n return {\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify(\n docs.map((d) => ({\n name: d.name,\n path: d.path,\n uri: d.uri,\n })),\n null,\n 2,\n ),\n },\n ],\n };\n },\n );\n\n // Register documentation as resources\n server.resource(\"docs://list\", \"docs://list\", async () => {\n const docs = await listDocs();\n return {\n contents: [\n {\n uri: \"docs://list\",\n text: JSON.stringify(\n docs.map((d) => ({ name: d.name, path: d.path, uri: d.uri })),\n null,\n 2,\n ),\n },\n ],\n };\n });\n\n return server;\n}\n";
@@ -95,9 +95,13 @@ export async function buildDocsIndex(force = false): Promise<void> {
95
95
  */
96
96
  export async function ensureDocsIndex(force = false): Promise<void> {
97
97
  if (force) {
98
+ // Wait for any in-flight build before starting a forced rebuild
99
+ if (docsIndex.building && indexReady) {
100
+ await indexReady.catch(() => {});
101
+ }
98
102
  docsIndex.ready = false;
99
103
  docsIndex.chunks = [];
100
- indexReady = buildDocsIndex();
104
+ indexReady = buildDocsIndex(true);
101
105
  return indexReady;
102
106
  }
103
107
  if (!indexReady) {
@@ -1 +1 @@
1
- export declare const tsconfigTemplate: string;
1
+ export declare const tsconfigTemplate = "{\n \"compilerOptions\": {\n \"target\": \"es2020\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\n \"next-env.d.ts\",\n \"**/*.ts\",\n \"**/*.tsx\",\n \"**/*.d.ts\",\n \".next/types/**/*.ts\",\n \".next/dev/types/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\"]\n}\n";
@@ -1,31 +1,32 @@
1
- export const tsconfigTemplate = JSON.stringify({
2
- compilerOptions: {
3
- target: "es2020",
4
- lib: ["dom", "dom.iterable", "esnext"],
5
- allowJs: true,
6
- skipLibCheck: true,
7
- strict: true,
8
- noEmit: true,
9
- esModuleInterop: true,
10
- module: "esnext",
11
- moduleResolution: "bundler",
12
- resolveJsonModule: true,
13
- isolatedModules: true,
14
- jsx: "react-jsx",
15
- incremental: true,
16
- plugins: [{ name: "next" }],
17
- baseUrl: ".",
18
- paths: {
19
- "@/*": ["./*"],
20
- },
21
- },
22
- include: [
23
- "next-env.d.ts",
24
- "**/*.ts",
25
- "**/*.tsx",
26
- "**/*.d.ts",
27
- ".next/types/**/*.ts",
28
- ".next/dev/types/**/*.ts",
29
- ],
30
- exclude: ["node_modules"],
31
- }, null, 2);
1
+ export const tsconfigTemplate = `{
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "@/*": ["./*"]
20
+ }
21
+ },
22
+ "include": [
23
+ "next-env.d.ts",
24
+ "**/*.ts",
25
+ "**/*.tsx",
26
+ "**/*.d.ts",
27
+ ".next/types/**/*.ts",
28
+ ".next/dev/types/**/*.ts"
29
+ ],
30
+ "exclude": ["node_modules"]
31
+ }
32
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
4
4
  "description": "Document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,7 +12,8 @@
12
12
  "dev": "tsc --watch",
13
13
  "start": "node dist/index.js",
14
14
  "prepare": "tsc",
15
- "test": "vitest run"
15
+ "test": "vitest run",
16
+ "format": "prettier --write ."
16
17
  },
17
18
  "keywords": [
18
19
  "doccupine",
@@ -48,6 +49,7 @@
48
49
  "@types/fs-extra": "^11.0.4",
49
50
  "@types/node": "^25.2.3",
50
51
  "@types/prompts": "^2.4.9",
52
+ "prettier": "^3.8.1",
51
53
  "typescript": "^5.9.3",
52
54
  "vitest": "^4.0.18"
53
55
  },