From 2008b49207362b8391fc7a5bfec6f33db9d63981 Mon Sep 17 00:00:00 2001 From: Peterl561 Date: Fri, 7 Mar 2025 00:57:27 +0800 Subject: [PATCH 1/4] feat: open in chat button in doc examples --- apps/docs/.env.example | 7 +- apps/docs/actions/open-in-chat.ts | 62 ++++++++++++++ .../docs/components/code-demo/code-demo.tsx | 82 +++++++++++++++---- 3 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 apps/docs/actions/open-in-chat.ts diff --git a/apps/docs/.env.example b/apps/docs/.env.example index 3889456f1c..41fa308744 100644 --- a/apps/docs/.env.example +++ b/apps/docs/.env.example @@ -21,4 +21,9 @@ NEXT_PUBLIC_FB_FEEDBACK_URL= # PostHog NEXT_PUBLIC_POSTHOG_KEY=your-posthog-key -NEXT_PUBLIC_POSTHOG_HOST=your-posthog-host \ No newline at end of file +NEXT_PUBLIC_POSTHOG_HOST=your-posthog-host + +# Chat +IMPORT_API_KEY=your-import-api-key +CHAT_API_URL= +CHAT_URL= diff --git a/apps/docs/actions/open-in-chat.ts b/apps/docs/actions/open-in-chat.ts new file mode 100644 index 0000000000..27e663aa42 --- /dev/null +++ b/apps/docs/actions/open-in-chat.ts @@ -0,0 +1,62 @@ +"use server"; + +import {SandpackFiles} from "@codesandbox/sandpack-react/types"; + +const importReact = 'import React from "react";'; + +export const openInChat = async ({ + title, + files, + dependencies, +}: { + title?: string; + files: SandpackFiles; + dependencies?: {name: string; version: string}[]; +}) => { + try { + // assumes one file for now + let content = files["/App.jsx"]; + + if (!content || typeof content !== "string") { + return { + error: "Content is not a string", + data: null, + }; + } + + // Check if the file content includes 'React' import statements, if not, add it + if ( + content.includes("React.") && + !content.includes("from 'react'") && + !content.includes('from "react"') + ) { + content = `${importReact}\n${content}\n`; + } + + const response = await fetch(`${process.env.CHAT_API_URL}/import`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.IMPORT_API_KEY}`, + }, + body: JSON.stringify({ + title, + content, + dependencies, + }), + }); + + const result = await response.json(); + + if (result.error || !result.path) { + return { + error: result.error ?? "Unknown error", + data: null, + }; + } + + return {error: null, data: `${process.env.CHAT_URL}${result.path}`}; + } catch (error) { + return {error: error, data: null}; + } +}; diff --git a/apps/docs/components/docs/components/code-demo/code-demo.tsx b/apps/docs/components/docs/components/code-demo/code-demo.tsx index 65b5f9e3e8..45f0ceec99 100644 --- a/apps/docs/components/docs/components/code-demo/code-demo.tsx +++ b/apps/docs/components/docs/components/code-demo/code-demo.tsx @@ -1,14 +1,16 @@ "use client"; -import React, {useCallback, useMemo, useRef} from "react"; +import React, {useCallback, useMemo, useRef, useState} from "react"; import dynamic from "next/dynamic"; -import {Skeleton, Tab, Tabs} from "@heroui/react"; +import {Button, Skeleton, Spinner, Tab, Tabs} from "@heroui/react"; import {useInView} from "framer-motion"; import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo"; import WindowResizer, {WindowResizerProps} from "./window-resizer"; import {GradientBoxProps} from "@/components/gradient-box"; +import {openInChat} from "@/actions/open-in-chat"; +import {SmallLogo} from "@/components/heroui-logo"; const DynamicReactLiveDemo = dynamic( () => import("./react-live-demo").then((m) => m.ReactLiveDemo), @@ -75,6 +77,8 @@ export const CodeDemo: React.FC = ({ margin: "600px", }); + const [isLoading, setIsLoading] = useState(false); + const {noInline, code} = useCodeDemo({ files, }); @@ -166,24 +170,66 @@ export const CodeDemo: React.FC = ({ return true; }, [showTabs, showPreview, showEditor]); + const handleOpenInChat = async () => { + setIsLoading(true); + const {data, error} = await openInChat({title, files, dependencies: []}); + + setIsLoading(false); + + if (error || !data) { + // TODO: toast conflicts with docs toast provider + // addToast({ + // title: "Error", + // description: error ?? "Unknown error", + // }); + + alert(error ?? "Unknown error"); + + return; + } + + window.open(data, "_blank"); + }; + return ( -
+
{shouldRenderTabs ? ( - - - {previewContent} - - - {editorContent} - - + <> + + + {previewContent} + + + {editorContent} + + + + ) : ( <> {previewContent} From 1bec998b8d764755af3937c2a104340d00a244ca Mon Sep 17 00:00:00 2001 From: Peterl561 Date: Fri, 7 Mar 2025 22:59:28 +0800 Subject: [PATCH 2/4] feat: pass dependencies in open in chat --- apps/docs/actions/open-in-chat.ts | 14 ++---- .../docs/components/code-demo/code-demo.tsx | 50 +++++++++++-------- .../code-demo/parse-dependencies.ts | 33 ++++++++++++ 3 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 apps/docs/components/docs/components/code-demo/parse-dependencies.ts diff --git a/apps/docs/actions/open-in-chat.ts b/apps/docs/actions/open-in-chat.ts index 27e663aa42..b4374dec6c 100644 --- a/apps/docs/actions/open-in-chat.ts +++ b/apps/docs/actions/open-in-chat.ts @@ -2,17 +2,11 @@ import {SandpackFiles} from "@codesandbox/sandpack-react/types"; +import {parseDependencies} from "@/components/docs/components/code-demo/parse-dependencies"; + const importReact = 'import React from "react";'; -export const openInChat = async ({ - title, - files, - dependencies, -}: { - title?: string; - files: SandpackFiles; - dependencies?: {name: string; version: string}[]; -}) => { +export const openInChat = async ({title, files}: {title?: string; files: SandpackFiles}) => { try { // assumes one file for now let content = files["/App.jsx"]; @@ -33,6 +27,8 @@ export const openInChat = async ({ content = `${importReact}\n${content}\n`; } + const dependencies = parseDependencies(content); + const response = await fetch(`${process.env.CHAT_API_URL}/import`, { method: "POST", headers: { diff --git a/apps/docs/components/docs/components/code-demo/code-demo.tsx b/apps/docs/components/docs/components/code-demo/code-demo.tsx index 45f0ceec99..9faec7d125 100644 --- a/apps/docs/components/docs/components/code-demo/code-demo.tsx +++ b/apps/docs/components/docs/components/code-demo/code-demo.tsx @@ -9,8 +9,8 @@ import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo"; import WindowResizer, {WindowResizerProps} from "./window-resizer"; import {GradientBoxProps} from "@/components/gradient-box"; -import {openInChat} from "@/actions/open-in-chat"; import {SmallLogo} from "@/components/heroui-logo"; +import {openInChat} from "@/actions/open-in-chat"; const DynamicReactLiveDemo = dynamic( () => import("./react-live-demo").then((m) => m.ReactLiveDemo), @@ -170,12 +170,16 @@ export const CodeDemo: React.FC = ({ return true; }, [showTabs, showPreview, showEditor]); + const isComponentsPage = window ? window.location.pathname.includes("/components/") : false; + const handleOpenInChat = async () => { setIsLoading(true); - const {data, error} = await openInChat({title, files, dependencies: []}); - setIsLoading(false); + const path = window.location.pathname.split("/components/")[1]; + const capitalizedPath = path.charAt(0).toUpperCase() + path.slice(1); + const {data, error} = await openInChat({title: `${capitalizedPath} - ${title}`, files}); + setIsLoading(false); if (error || !data) { // TODO: toast conflicts with docs toast provider // addToast({ @@ -210,25 +214,27 @@ export const CodeDemo: React.FC = ({ {editorContent} - + {isComponentsPage && ( + + )} ) : ( <> diff --git a/apps/docs/components/docs/components/code-demo/parse-dependencies.ts b/apps/docs/components/docs/components/code-demo/parse-dependencies.ts new file mode 100644 index 0000000000..cfcb10b5a3 --- /dev/null +++ b/apps/docs/components/docs/components/code-demo/parse-dependencies.ts @@ -0,0 +1,33 @@ +const packageRegex = /(?:from|import)\s+(?:.*\s+from\s+)?['"]([^'"]+)['"]/g; + +export const parseDependencies = (content: string) => { + const dependencies: {name: string; version: string}[] = []; + + content.match(packageRegex)?.forEach((match) => { + if (match.includes("@heroui")) { + return; + } + + if (match.includes("./") || match.includes("../")) { + return; + } + + const packageName = match.match(/['"]([^'"]+)['"]/)?.[1]; + + if (!packageName) { + return; + } + + dependencies.push({ + name: packageName, + version: fixedVersions[packageName] || "latest", + }); + }); + + return dependencies; +}; + +const fixedVersions = { + "@internationalized/date": "3.7.0", + "@react-aria/i18n": "3.12.5", +}; From e96348064531393960270afce3d5ea64ef81cc33 Mon Sep 17 00:00:00 2001 From: Peterl561 Date: Sat, 8 Mar 2025 00:58:52 +0800 Subject: [PATCH 3/4] fix: open in chat error handling --- apps/docs/app/docs/[[...slug]]/page.tsx | 4 +- .../docs/components/code-demo/code-demo.tsx | 40 +++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx index da649aba04..0d8aa28f70 100644 --- a/apps/docs/app/docs/[[...slug]]/page.tsx +++ b/apps/docs/app/docs/[[...slug]]/page.tsx @@ -2,7 +2,7 @@ import type {Metadata} from "next"; import {notFound} from "next/navigation"; import {allDocs} from "contentlayer2/generated"; -import {Link} from "@heroui/react"; +import {Link, ToastProvider} from "@heroui/react"; import {MDXContent} from "@/components/mdx-content"; import {siteConfig} from "@/config/site"; @@ -104,6 +104,8 @@ export default async function DocPage({params}: DocPageProps) {
)} + {/* toast page has its own provider*/} + {doc.title !== "Toast" && } ); } diff --git a/apps/docs/components/docs/components/code-demo/code-demo.tsx b/apps/docs/components/docs/components/code-demo/code-demo.tsx index 9faec7d125..2d372fd555 100644 --- a/apps/docs/components/docs/components/code-demo/code-demo.tsx +++ b/apps/docs/components/docs/components/code-demo/code-demo.tsx @@ -2,8 +2,10 @@ import React, {useCallback, useMemo, useRef, useState} from "react"; import dynamic from "next/dynamic"; -import {Button, Skeleton, Spinner, Tab, Tabs} from "@heroui/react"; +import {addToast, Button, Skeleton, Spinner, Tab, Tabs} from "@heroui/react"; import {useInView} from "framer-motion"; +import {usePostHog} from "posthog-js/react"; +import {usePathname} from "next/navigation"; import {useCodeDemo, UseCodeDemoProps} from "./use-code-demo"; import WindowResizer, {WindowResizerProps} from "./window-resizer"; @@ -77,6 +79,9 @@ export const CodeDemo: React.FC = ({ margin: "600px", }); + const pathname = usePathname(); + const posthog = usePostHog(); + const [isLoading, setIsLoading] = useState(false); const {noInline, code} = useCodeDemo({ @@ -170,30 +175,41 @@ export const CodeDemo: React.FC = ({ return true; }, [showTabs, showPreview, showEditor]); - const isComponentsPage = window ? window.location.pathname.includes("/components/") : false; + const isComponentsPage = pathname.includes("/components/"); - const handleOpenInChat = async () => { + const handleOpenInChat = useCallback(async () => { setIsLoading(true); - const path = window.location.pathname.split("/components/")[1]; - const capitalizedPath = path.charAt(0).toUpperCase() + path.slice(1); + const component = pathname.split("/components/")[1]; + + posthog.capture("CodeDemo - Open in Chat", { + component, + demo: title, + }); + + const capitalizedPath = component.charAt(0).toUpperCase() + component.slice(1); const {data, error} = await openInChat({title: `${capitalizedPath} - ${title}`, files}); setIsLoading(false); + if (error || !data) { - // TODO: toast conflicts with docs toast provider - // addToast({ - // title: "Error", - // description: error ?? "Unknown error", - // }); + posthog.capture("CodeDemo - Open in Chat Error", { + component, + demo: title, + error: error ?? "Unknown error", + }); - alert(error ?? "Unknown error"); + addToast({ + title: "Error", + description: error ?? "Unknown error", + color: "danger", + }); return; } window.open(data, "_blank"); - }; + }, [pathname, title, files, posthog]); return (
From 869ccb1c3745edd0befad1b10b6fba66c90b9f9d Mon Sep 17 00:00:00 2001 From: Junior Garcia Date: Mon, 10 Mar 2025 11:43:45 -0300 Subject: [PATCH 4/4] chore: small adjustment --- apps/docs/components/docs/components/code-demo/code-demo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/components/docs/components/code-demo/code-demo.tsx b/apps/docs/components/docs/components/code-demo/code-demo.tsx index 2d372fd555..d18d4fa991 100644 --- a/apps/docs/components/docs/components/code-demo/code-demo.tsx +++ b/apps/docs/components/docs/components/code-demo/code-demo.tsx @@ -232,7 +232,7 @@ export const CodeDemo: React.FC = ({ {isComponentsPage && (