summaryrefslogtreecommitdiffstats
path: root/excalidraw-app/components/AI.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'excalidraw-app/components/AI.tsx')
-rw-r--r--excalidraw-app/components/AI.tsx159
1 files changed, 159 insertions, 0 deletions
diff --git a/excalidraw-app/components/AI.tsx b/excalidraw-app/components/AI.tsx
new file mode 100644
index 0000000..ba13849
--- /dev/null
+++ b/excalidraw-app/components/AI.tsx
@@ -0,0 +1,159 @@
+import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
+import {
+ DiagramToCodePlugin,
+ exportToBlob,
+ getTextFromElements,
+ MIME_TYPES,
+ TTDDialog,
+} from "@excalidraw/excalidraw";
+import { getDataURL } from "@excalidraw/excalidraw/data/blob";
+import { safelyParseJSON } from "@excalidraw/excalidraw/utils";
+
+export const AIComponents = ({
+ excalidrawAPI,
+}: {
+ excalidrawAPI: ExcalidrawImperativeAPI;
+}) => {
+ return (
+ <>
+ <DiagramToCodePlugin
+ generate={async ({ frame, children }) => {
+ const appState = excalidrawAPI.getAppState();
+
+ const blob = await exportToBlob({
+ elements: children,
+ appState: {
+ ...appState,
+ exportBackground: true,
+ viewBackgroundColor: appState.viewBackgroundColor,
+ },
+ exportingFrame: frame,
+ files: excalidrawAPI.getFiles(),
+ mimeType: MIME_TYPES.jpg,
+ });
+
+ const dataURL = await getDataURL(blob);
+
+ const textFromFrameChildren = getTextFromElements(children);
+
+ const response = await fetch(
+ `${
+ import.meta.env.VITE_APP_AI_BACKEND
+ }/v1/ai/diagram-to-code/generate`,
+ {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ texts: textFromFrameChildren,
+ image: dataURL,
+ theme: appState.theme,
+ }),
+ },
+ );
+
+ if (!response.ok) {
+ const text = await response.text();
+ const errorJSON = safelyParseJSON(text);
+
+ if (!errorJSON) {
+ throw new Error(text);
+ }
+
+ if (errorJSON.statusCode === 429) {
+ return {
+ html: `<html>
+ <body style="margin: 0; text-align: center">
+ <div style="display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100vh; padding: 0 60px">
+ <div style="color:red">Too many requests today,</br>please try again tomorrow!</div>
+ </br>
+ </br>
+ <div>You can also try <a href="${
+ import.meta.env.VITE_APP_PLUS_LP
+ }/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noreferrer noopener">kj-diagramming cloud</a> to get more requests.</div>
+ </div>
+ </body>
+ </html>`,
+ };
+ }
+
+ throw new Error(errorJSON.message || text);
+ }
+
+ try {
+ const { html } = await response.json();
+
+ if (!html) {
+ throw new Error("Generation failed (invalid response)");
+ }
+ return {
+ html,
+ };
+ } catch (error: any) {
+ throw new Error("Generation failed (invalid response)");
+ }
+ }}
+ />
+
+ <TTDDialog
+ onTextSubmit={async (input) => {
+ try {
+ const response = await fetch(
+ `${
+ import.meta.env.VITE_APP_AI_BACKEND
+ }/v1/ai/text-to-diagram/generate`,
+ {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ prompt: input }),
+ },
+ );
+
+ const rateLimit = response.headers.has("X-Ratelimit-Limit")
+ ? parseInt(response.headers.get("X-Ratelimit-Limit") || "0", 10)
+ : undefined;
+
+ const rateLimitRemaining = response.headers.has(
+ "X-Ratelimit-Remaining",
+ )
+ ? parseInt(
+ response.headers.get("X-Ratelimit-Remaining") || "0",
+ 10,
+ )
+ : undefined;
+
+ const json = await response.json();
+
+ if (!response.ok) {
+ if (response.status === 429) {
+ return {
+ rateLimit,
+ rateLimitRemaining,
+ error: new Error(
+ "Too many requests today, please try again tomorrow!",
+ ),
+ };
+ }
+
+ throw new Error(json.message || "Generation failed...");
+ }
+
+ const generatedResponse = json.generatedResponse;
+ if (!generatedResponse) {
+ throw new Error("Generation failed...");
+ }
+
+ return { generatedResponse, rateLimit, rateLimitRemaining };
+ } catch (err: any) {
+ throw new Error("Request failed");
+ }
+ }}
+ />
+ </>
+ );
+};