commit e26ef731e9f15ed9ea87ef5c243f2d6927e1a740 Author: dayuan.jiang Date: Wed Mar 19 06:04:06 2025 +0000 initialize project with Next.js, Tailwind CSS, and essential configurations diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts new file mode 100644 index 0000000..c66a71d --- /dev/null +++ b/app/api/chat/route.ts @@ -0,0 +1,13 @@ +import { google } from "@ai-sdk/google"; +import { streamText } from "ai"; + +export const maxDuration = 30; + +export async function POST(req: Request) { + const { messages } = await req.json(); + const result = streamText({ + model: google("gemini-2.0-flash"), + messages, + }); + return result.toDataStreamResponse(); +} \ No newline at end of file diff --git a/app/assistant.tsx b/app/assistant.tsx new file mode 100644 index 0000000..c229d29 --- /dev/null +++ b/app/assistant.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { AssistantRuntimeProvider } from "@assistant-ui/react"; +import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; +import { Thread } from "@/components/assistant-ui/thread"; +import { ThreadList } from "@/components/assistant-ui/thread-list"; + +export const Assistant = () => { + const runtime = useChatRuntime({ + api: "/api/chat", + }); + + return ( + +
+ + +
+
+ ); +}; diff --git a/app/extract_xml.ts b/app/extract_xml.ts new file mode 100644 index 0000000..e5181d8 --- /dev/null +++ b/app/extract_xml.ts @@ -0,0 +1,69 @@ +import * as pako from 'pako'; + +export async function extractDiagramXML(xml_svg_string: string): Promise { + try { + // 1. Parse the SVG string (using built-in DOMParser in a browser-like environment) + const svgString = atob(xml_svg_string.slice(26)) + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(svgString, "image/svg+xml"); + const svgElement = svgDoc.querySelector('svg'); + + if (!svgElement) { + throw new Error("No SVG element found in the input string."); + } + console.log("svgElement", svgElement); + // 2. Extract the 'content' attribute + const encodedContent = svgElement.getAttribute('content'); + + if (!encodedContent) { + throw new Error("SVG element does not have a 'content' attribute."); + } + + // 3. Decode HTML entities (using a minimal function) + function decodeHtmlEntities(str: string) { + const textarea = document.createElement('textarea'); // Use built-in element + textarea.innerHTML = str; + return textarea.value; + } + const xmlContent = decodeHtmlEntities(encodedContent); + + // 4. Parse the XML content + const xmlDoc = parser.parseFromString(xmlContent, "text/xml"); + const diagramElement = xmlDoc.querySelector('diagram'); + + if (!diagramElement) { + throw new Error("No diagram element found"); + } + console.log("diagramElement", diagramElement); + // 5. Extract base64 encoded data + const base64EncodedData = diagramElement.textContent; + console.log("base64EncodedData", base64EncodedData); + + if (!base64EncodedData) { + throw new Error("No encoded data found in the diagram element"); + } + + // 6. Decode base64 data + const binaryString = atob(base64EncodedData); + + // 7. Convert binary string to Uint8Array + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + // 8. Decompress data using pako (equivalent to zlib.decompress with wbits=-15) + const decompressedData = pako.inflate(bytes, { windowBits: -15 }); + + // 9. Convert the decompressed data to a string + const decoder = new TextDecoder('utf-8'); + const decodedString = decoder.decode(decompressedData); + + return decodedString; + + } catch (error) { + console.error("Error extracting diagram XML:", error); + throw error; // Re-throw for caller handling + } +} \ No newline at end of file diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..b70a101 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,123 @@ +@import "tailwindcss"; + +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..f7fa87e --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..0eedd02 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,83 @@ +"use client"; +import { AssistantRuntimeProvider } from "@assistant-ui/react"; +import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; +import { DrawIoEmbed, DrawIoEmbedRef } from "react-drawio"; + +import { Thread } from "@/components/assistant-ui/thread"; +import { useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { extractDiagramXML } from "./extract_xml" +export default function Home() { + const runtime = useChatRuntime({ + api: "/api/chat", + }); + + const drawioRef = useRef(null); + const [imgData, setImgData] = useState(null); + const [diagram, setDiagram] = useState(""); + // const handleExport = () => {}; + const handleExport = () => { + if (drawioRef.current) { + drawioRef.current.exportDiagram({ + format: "xmlsvg", + }); + } + }; + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (drawioRef.current) { + drawioRef.current.load({ + xml: diagram, + }); + } + }; + console.log("imgData", imgData); + return ( + +
+ setImgData(data.data)} + urlParameters={{ + // ui: "kennedy", + spin: true, + libraries: false, + saveAndExit: false, + noExitBtn: true, + }} + /> + {/* */} +
+
+
+ +