Browse Source

feat: initial testing version

are 1 year ago
parent
commit
0958241589
3 changed files with 597 additions and 1 deletions
  1. 1 0
      src/components/App.tsx
  2. 589 0
      src/components/TextToExcalidraw/TextToExcalidraw.tsx
  3. 7 1
      src/locales/en.json

+ 1 - 0
src/components/App.tsx

@@ -397,6 +397,7 @@ import { COLOR_PALETTE } from "../colors";
 import { ElementCanvasButton } from "./MagicButton";
 import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
 import { EditorLocalStorage } from "../data/EditorLocalStorage";
+import { TextToExcalidraw } from "./TextToExcalidraw/TextToExcalidraw";
 
 const AppContext = React.createContext<AppClassProperties>(null!);
 const AppPropsContext = React.createContext<AppProps>(null!);

+ 589 - 0
src/components/TextToExcalidraw/TextToExcalidraw.tsx

@@ -0,0 +1,589 @@
+import { useEffect, useRef, useState } from "react";
+import { t } from "../../i18n";
+import { useApp } from "../App";
+import { Dialog } from "../Dialog";
+import { TextField } from "../TextField";
+import Trans from "../Trans";
+import {
+  CloseIcon,
+  RedoIcon,
+  ZoomInIcon,
+  ZoomOutIcon,
+  playerPlayIcon,
+  playerStopFilledIcon,
+} from "../icons";
+import { NonDeletedExcalidrawElement } from "../../element/types";
+import { convertToExcalidrawElements } from "../../data/transform";
+import { exportToCanvas } from "../../packages/utils";
+import { DEFAULT_EXPORT_PADDING } from "../../constants";
+import { canvasToBlob } from "../../data/blob";
+
+const testResponse = `{
+  "error": false,
+  "data": [
+    {
+      "type": "ellipse",
+      "x": 200,
+      "y": 200,
+      "width": 100,
+      "height": 100,
+      "strokeColor": "transparent",
+      "backgroundColor": "yellow",
+      "strokeWidth": 2
+    },
+    {
+      "type": "line",
+      "x": 300,
+      "y": 250,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          70,
+          0
+        ]
+      ],
+      "width": -70,
+      "height": 0,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 293.30127018922195,
+      "y": 275,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          60.62177826491069,
+          35
+        ]
+      ],
+      "width": -60.62177826491069,
+      "height": -35,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 275,
+      "y": 293.30127018922195,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          35,
+          60.62177826491069
+        ]
+      ],
+      "width": -35,
+      "height": -60.62177826491069,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 250,
+      "y": 300,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          0,
+          70
+        ]
+      ],
+      "width": 0,
+      "height": -70,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 225,
+      "y": 293.30127018922195,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -34.99999999999997,
+          60.62177826491069
+        ]
+      ],
+      "width": -34.99999999999997,
+      "height": -60.62177826491069,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 206.69872981077805,
+      "y": 275,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -60.62177826491069,
+          35
+        ]
+      ],
+      "width": -60.62177826491069,
+      "height": -35,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 200,
+      "y": 250,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -70,
+          2.842170943040401e-14
+        ]
+      ],
+      "width": -70,
+      "height": -2.842170943040401e-14,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 206.69872981077805,
+      "y": 225,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -60.62177826491069,
+          -34.99999999999997
+        ]
+      ],
+      "width": -60.62177826491069,
+      "height": -34.99999999999997,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 224.99999999999997,
+      "y": 206.69872981077808,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -35.00000000000003,
+          -60.62177826491069
+        ]
+      ],
+      "width": -35.00000000000003,
+      "height": -60.62177826491069,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 250,
+      "y": 200,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -2.842170943040401e-14,
+          -70
+        ]
+      ],
+      "width": -2.842170943040401e-14,
+      "height": -70,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 275,
+      "y": 206.69872981077808,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          35,
+          -60.621778264910716
+        ]
+      ],
+      "width": -35,
+      "height": -60.621778264910716,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    },
+    {
+      "type": "line",
+      "x": 293.3012701892219,
+      "y": 224.99999999999997,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          60.621778264910745,
+          -35.00000000000003
+        ]
+      ],
+      "width": -60.621778264910745,
+      "height": -35.00000000000003,
+      "strokeColor": "yellow",
+      "backgroundColor": "transparent",
+      "strokeWidth": 5
+    }
+  ]
+}`;
+
+async function fetchData(
+  prompt: string,
+): Promise<readonly NonDeletedExcalidrawElement[]> {
+  const response = await fetch(
+    `http://localhost:3015/v1/ai/text-to-excalidraw/generate`,
+    {
+      method: "POST",
+      headers: {
+        Accept: "application/json",
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify({ prompt }),
+    },
+  );
+
+  const result = await response.json();
+
+  if (result.error) {
+    alert("Oops!");
+    return [];
+  }
+
+  return convertToExcalidrawElements(result.data);
+}
+
+export const TextToExcalidraw = () => {
+  const app = useApp();
+
+  const [prompt, setPrompt] = useState("");
+  const [isPanelOpen, setPanelOpen] = useState(false);
+  const [isLoading, setLoading] = useState(false);
+  const [data, setData] = useState<
+    readonly NonDeletedExcalidrawElement[] | null
+  >(null);
+
+  const [previewCanvas, setPreviewCanvas] = useState<HTMLCanvasElement | null>(
+    null,
+  );
+
+  const containerRef = useRef<HTMLDivElement>(null);
+
+  const onClose = () => {
+    app.setOpenDialog(null);
+  };
+
+  const onSubmit = async () => {
+    setPanelOpen(true);
+    setLoading(true);
+
+    const elements = await fetchData(prompt);
+
+    setData(elements);
+
+    const canvas = await exportToCanvas({
+      elements,
+      files: {},
+      exportPadding: DEFAULT_EXPORT_PADDING,
+    });
+
+    await canvasToBlob(canvas);
+
+    setPreviewCanvas(canvas);
+    setLoading(false);
+  };
+
+  const onInsert = async () => {
+    if (data) {
+      app.addElementsFromPasteOrLibrary({
+        elements: data,
+        files: {},
+        position: "center",
+        fitToContent: true,
+      });
+
+      onClose();
+    }
+  };
+
+  useEffect(() => {
+    if (containerRef.current && previewCanvas) {
+      containerRef.current.replaceChildren(previewCanvas);
+    }
+  }, [previewCanvas]);
+
+  // exportToCanvas([], {}, {}, {});
+  // exportToSvg([], {exportBackground}, {}, {})
+
+  return (
+    <div
+      style={{
+        position: "absolute",
+        top: "6.5rem",
+        pointerEvents: "auto",
+        width: "100%",
+        display: "flex",
+        flexDirection: "column",
+        gap: "0.75rem",
+      }}
+    >
+      <div
+        className="Island"
+        style={{
+          width: "100%",
+          display: "flex",
+          flexDirection: "row",
+          boxSizing: "border-box",
+          gap: "0.75rem",
+          alignItems: "center",
+          height: 48,
+          padding: "0.5rem",
+        }}
+      >
+        <input
+          value={prompt}
+          onChange={(e) => setPrompt(e.target.value)}
+          type="text"
+          style={{
+            flexGrow: 1,
+            height: "100%",
+            boxSizing: "border-box",
+            border: 0,
+            outline: "none",
+          }}
+          placeholder="How can I help you today?"
+        />
+        <button
+          style={{
+            cursor: "pointer",
+            height: "100%",
+            border: "none",
+            background: "white",
+            aspectRatio: "1/1",
+            padding: 0,
+            display: "flex",
+            justifyContent: "center",
+            alignItems: "center",
+          }}
+        >
+          <div
+            style={{ width: "1.25rem", height: "1.25rem", color: "#1B1B1F" }}
+          >
+            {CloseIcon}
+          </div>
+        </button>
+        <div
+          style={{ background: "#D6D6D6", width: 1, height: "1.5rem" }}
+        ></div>
+        <button
+          style={{
+            cursor: "pointer",
+            height: "100%",
+            border: "none",
+            aspectRatio: "1/1",
+            padding: 0,
+            display: "flex",
+            justifyContent: "center",
+            alignItems: "center",
+            backgroundColor: "#6965DB",
+            borderRadius: "0.5rem",
+          }}
+          onClick={onSubmit}
+        >
+          <div style={{ width: "1.25rem", height: "1.25rem", color: "white" }}>
+            {isLoading ? playerStopFilledIcon : playerPlayIcon}
+          </div>
+        </button>
+      </div>
+
+      {isPanelOpen && (
+        <div
+          className="Island"
+          style={{
+            width: "100%",
+            display: "flex",
+            flexDirection: "row",
+            height: 400,
+            boxSizing: "border-box",
+          }}
+        >
+          {isLoading ? (
+            "loading"
+          ) : (
+            <div
+              style={{
+                width: "100%",
+                height: "100%",
+                display: "flex",
+                flexDirection: "column",
+              }}
+            >
+              <div
+                ref={containerRef}
+                style={{
+                  display: "flex",
+                  alignItems: "center",
+                  justifyContent: "center",
+                  width: "100%",
+                  height: "100%",
+                }}
+              />
+              <div
+                style={{
+                  borderTop: "1px solid #F0EFFF",
+                  padding: "0.75rem",
+                  display: "flex",
+                  flexDirection: "row",
+                  gap: "0.75rem",
+                }}
+              >
+                <button
+                  style={{
+                    cursor: "pointer",
+                    width: 32,
+                    height: "100%",
+                    border: "none",
+                    aspectRatio: "1/1",
+                    padding: 0,
+                    display: "flex",
+                    justifyContent: "center",
+                    alignItems: "center",
+                    backgroundColor: "#F5F5F9",
+                    borderRadius: "0.5rem",
+                  }}
+                >
+                  <div
+                    style={{
+                      width: "1.25rem",
+                      height: "1.25rem",
+                      color: "#1B1B1F",
+                    }}
+                  >
+                    {RedoIcon}
+                  </div>
+                </button>
+
+                <div style={{ width: 32, height: "100%", display: "flex" }}>
+                  <button
+                    style={{
+                      cursor: "pointer",
+                      width: 32,
+                      height: "100%",
+                      border: "none",
+                      aspectRatio: "1/1",
+                      padding: 0,
+                      display: "flex",
+                      justifyContent: "center",
+                      alignItems: "center",
+                      backgroundColor: "#F5F5F9",
+                      borderRadius: "0.5rem 0 0 0.5rem",
+                    }}
+                  >
+                    <div
+                      style={{
+                        width: "1.25rem",
+                        height: "1.25rem",
+                        color: "#1B1B1F",
+                      }}
+                    >
+                      {ZoomOutIcon}
+                    </div>
+                  </button>
+                  <button
+                    style={{
+                      cursor: "pointer",
+                      width: 32,
+                      height: "100%",
+                      border: "none",
+                      aspectRatio: "1/1",
+                      padding: 0,
+                      display: "flex",
+                      justifyContent: "center",
+                      alignItems: "center",
+                      backgroundColor: "#F5F5F9",
+                      borderRadius: "0 0.5rem 0.5rem 0",
+                    }}
+                  >
+                    <div
+                      style={{
+                        width: "1.25rem",
+                        height: "1.25rem",
+                        color: "#1B1B1F",
+                      }}
+                    >
+                      {ZoomInIcon}
+                    </div>
+                  </button>
+                </div>
+                <div style={{ flexGrow: 1 }}></div>
+                <button
+                  style={{
+                    cursor: "pointer",
+                    height: "100%",
+                    border: "none",
+                    padding: "0.5rem 1rem",
+                    display: "flex",
+                    justifyContent: "center",
+                    alignItems: "center",
+                    backgroundColor: "#6965DB",
+                    borderRadius: "0.5rem",
+                    color: "white",
+                  }}
+                  onClick={onInsert}
+                >
+                  Insert into scene &gt;
+                </button>
+              </div>
+            </div>
+          )}
+        </div>
+      )}
+    </div>
+  );
+};

+ 7 - 1
src/locales/en.json

@@ -251,7 +251,8 @@
     "hand": "Hand (panning tool)",
     "extraTools": "More tools",
     "mermaidToExcalidraw": "Mermaid to Excalidraw",
-    "magicSettings": "AI settings"
+    "magicSettings": "AI settings",
+    "textToExcalidraw": "Text to Excalidraw"
   },
   "headings": {
     "canvasActions": "Canvas actions",
@@ -517,5 +518,10 @@
     "description": "Currently only <flowchartLink>Flowcharts</flowchartLink> and <sequenceLink>Sequence Diagrams</sequenceLink> are supported. The other types will be rendered as image in Excalidraw.",
     "syntax": "Mermaid Syntax",
     "preview": "Preview"
+  },
+  "textToExcalidraw": {
+    "title": "Text to Excalidraw",
+    "description": "Test",
+    "button": "Insert"
   }
 }