|
@@ -2,58 +2,20 @@ import { Dialog } from "../Dialog";
|
|
import { useApp } from "../App";
|
|
import { useApp } from "../App";
|
|
import MermaidToExcalidraw from "./MermaidToExcalidraw";
|
|
import MermaidToExcalidraw from "./MermaidToExcalidraw";
|
|
import TTDDialogTabs from "./TTDDialogTabs";
|
|
import TTDDialogTabs from "./TTDDialogTabs";
|
|
-import { ChangeEventHandler, useEffect, useRef, useState } from "react";
|
|
|
|
|
|
+import { useEffect, useState } from "react";
|
|
import { useUIAppState } from "../../context/ui-appState";
|
|
import { useUIAppState } from "../../context/ui-appState";
|
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
|
import { TTDDialogTabTriggers } from "./TTDDialogTabTriggers";
|
|
import { TTDDialogTabTriggers } from "./TTDDialogTabTriggers";
|
|
import { TTDDialogTabTrigger } from "./TTDDialogTabTrigger";
|
|
import { TTDDialogTabTrigger } from "./TTDDialogTabTrigger";
|
|
import { TTDDialogTab } from "./TTDDialogTab";
|
|
import { TTDDialogTab } from "./TTDDialogTab";
|
|
import { t } from "../../i18n";
|
|
import { t } from "../../i18n";
|
|
-import { TTDDialogInput } from "./TTDDialogInput";
|
|
|
|
-import { TTDDialogOutput } from "./TTDDialogOutput";
|
|
|
|
-import { TTDDialogPanel } from "./TTDDialogPanel";
|
|
|
|
-import { TTDDialogPanels } from "./TTDDialogPanels";
|
|
|
|
-import {
|
|
|
|
- MermaidToExcalidrawLibProps,
|
|
|
|
- convertMermaidToExcalidraw,
|
|
|
|
- insertToEditor,
|
|
|
|
- saveMermaidDataToStorage,
|
|
|
|
-} from "./common";
|
|
|
|
-import { NonDeletedExcalidrawElement } from "../../element/types";
|
|
|
|
-import { BinaryFiles } from "../../types";
|
|
|
|
-import { ArrowRightIcon } from "../icons";
|
|
|
|
|
|
+import { CommonDialogProps, MermaidToExcalidrawLibProps } from "./common";
|
|
|
|
|
|
import "./TTDDialog.scss";
|
|
import "./TTDDialog.scss";
|
|
-import { isFiniteNumber } from "../../utils";
|
|
|
|
-import { atom, useAtom } from "jotai";
|
|
|
|
-import { trackEvent } from "../../analytics";
|
|
|
|
|
|
+import { TextToDiagram } from "./TextToDiagram";
|
|
|
|
+import { TextToDrawing } from "./TextToDrawing";
|
|
|
|
|
|
-const MIN_PROMPT_LENGTH = 3;
|
|
|
|
-const MAX_PROMPT_LENGTH = 1000;
|
|
|
|
-
|
|
|
|
-const rateLimitsAtom = atom<{
|
|
|
|
- rateLimit: number;
|
|
|
|
- rateLimitRemaining: number;
|
|
|
|
-} | null>(null);
|
|
|
|
-
|
|
|
|
-type OnTestSubmitRetValue = {
|
|
|
|
- rateLimit?: number | null;
|
|
|
|
- rateLimitRemaining?: number | null;
|
|
|
|
-} & (
|
|
|
|
- | { generatedResponse: string | undefined; error?: null | undefined }
|
|
|
|
- | {
|
|
|
|
- error: Error;
|
|
|
|
- generatedResponse?: null | undefined;
|
|
|
|
- }
|
|
|
|
-);
|
|
|
|
-
|
|
|
|
-export const TTDDialog = (
|
|
|
|
- props:
|
|
|
|
- | {
|
|
|
|
- onTextSubmit(value: string): Promise<OnTestSubmitRetValue>;
|
|
|
|
- }
|
|
|
|
- | { __fallback: true },
|
|
|
|
-) => {
|
|
|
|
|
|
+export const TTDDialog = (props: CommonDialogProps | { __fallback: true }) => {
|
|
const appState = useUIAppState();
|
|
const appState = useUIAppState();
|
|
|
|
|
|
if (appState.openDialog?.name !== "ttd") {
|
|
if (appState.openDialog?.name !== "ttd") {
|
|
@@ -72,118 +34,10 @@ export const TTDDialogBase = withInternalFallback(
|
|
tab,
|
|
tab,
|
|
...rest
|
|
...rest
|
|
}: {
|
|
}: {
|
|
- tab: "text-to-diagram" | "mermaid";
|
|
|
|
- } & (
|
|
|
|
- | {
|
|
|
|
- onTextSubmit(value: string): Promise<OnTestSubmitRetValue>;
|
|
|
|
- }
|
|
|
|
- | { __fallback: true }
|
|
|
|
- )) => {
|
|
|
|
|
|
+ tab: "text-to-diagram" | "mermaid" | "text-to-drawing";
|
|
|
|
+ } & (CommonDialogProps | { __fallback: true })) => {
|
|
const app = useApp();
|
|
const app = useApp();
|
|
|
|
|
|
- const someRandomDivRef = useRef<HTMLDivElement>(null);
|
|
|
|
-
|
|
|
|
- const [text, setText] = useState("");
|
|
|
|
-
|
|
|
|
- const prompt = text.trim();
|
|
|
|
-
|
|
|
|
- const handleTextChange: ChangeEventHandler<HTMLTextAreaElement> = (
|
|
|
|
- event,
|
|
|
|
- ) => {
|
|
|
|
- setText(event.target.value);
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const [onTextSubmitInProgess, setOnTextSubmitInProgess] = useState(false);
|
|
|
|
- const [rateLimits, setRateLimits] = useAtom(rateLimitsAtom);
|
|
|
|
-
|
|
|
|
- const onGenerate = async () => {
|
|
|
|
- if (
|
|
|
|
- prompt.length > MAX_PROMPT_LENGTH ||
|
|
|
|
- prompt.length < MIN_PROMPT_LENGTH ||
|
|
|
|
- onTextSubmitInProgess ||
|
|
|
|
- rateLimits?.rateLimitRemaining === 0 ||
|
|
|
|
- // means this is not a text-to-diagram dialog (needed for TS only)
|
|
|
|
- "__fallback" in rest
|
|
|
|
- ) {
|
|
|
|
- if (prompt.length < MIN_PROMPT_LENGTH) {
|
|
|
|
- setError(
|
|
|
|
- new Error(
|
|
|
|
- `Prompt is too short (min ${MIN_PROMPT_LENGTH} characters)`,
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
|
|
- setError(
|
|
|
|
- new Error(
|
|
|
|
- `Prompt is too long (max ${MAX_PROMPT_LENGTH} characters)`,
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- try {
|
|
|
|
- setOnTextSubmitInProgess(true);
|
|
|
|
-
|
|
|
|
- trackEvent("ai", "generate", "ttd");
|
|
|
|
-
|
|
|
|
- const { generatedResponse, error, rateLimit, rateLimitRemaining } =
|
|
|
|
- await rest.onTextSubmit(prompt);
|
|
|
|
-
|
|
|
|
- if (isFiniteNumber(rateLimit) && isFiniteNumber(rateLimitRemaining)) {
|
|
|
|
- setRateLimits({ rateLimit, rateLimitRemaining });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (error) {
|
|
|
|
- setError(error);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (!generatedResponse) {
|
|
|
|
- setError(new Error("Generation failed"));
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- try {
|
|
|
|
- await convertMermaidToExcalidraw({
|
|
|
|
- canvasRef: someRandomDivRef,
|
|
|
|
- data,
|
|
|
|
- mermaidToExcalidrawLib,
|
|
|
|
- setError,
|
|
|
|
- mermaidDefinition: generatedResponse,
|
|
|
|
- });
|
|
|
|
- trackEvent("ai", "mermaid parse success", "ttd");
|
|
|
|
- saveMermaidDataToStorage(generatedResponse);
|
|
|
|
- } catch (error: any) {
|
|
|
|
- console.info(
|
|
|
|
- `%cTTD mermaid render errror: ${error.message}`,
|
|
|
|
- "color: red",
|
|
|
|
- );
|
|
|
|
- console.info(
|
|
|
|
- `>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\nTTD mermaid definition render errror: ${error.message}`,
|
|
|
|
- "color: yellow",
|
|
|
|
- );
|
|
|
|
- trackEvent("ai", "mermaid parse failed", "ttd");
|
|
|
|
- setError(
|
|
|
|
- new Error(
|
|
|
|
- "Generated an invalid diagram :(. You may also try a different prompt.",
|
|
|
|
- ),
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- } catch (error: any) {
|
|
|
|
- let message: string | undefined = error.message;
|
|
|
|
- if (!message || message === "Failed to fetch") {
|
|
|
|
- message = "Request failed";
|
|
|
|
- }
|
|
|
|
- setError(new Error(message));
|
|
|
|
- } finally {
|
|
|
|
- setOnTextSubmitInProgess(false);
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const refOnGenerate = useRef(onGenerate);
|
|
|
|
- refOnGenerate.current = onGenerate;
|
|
|
|
-
|
|
|
|
const [mermaidToExcalidrawLib, setMermaidToExcalidrawLib] =
|
|
const [mermaidToExcalidrawLib, setMermaidToExcalidrawLib] =
|
|
useState<MermaidToExcalidrawLibProps>({
|
|
useState<MermaidToExcalidrawLibProps>({
|
|
loaded: false,
|
|
loaded: false,
|
|
@@ -200,13 +54,6 @@ export const TTDDialogBase = withInternalFallback(
|
|
fn();
|
|
fn();
|
|
}, [mermaidToExcalidrawLib.api]);
|
|
}, [mermaidToExcalidrawLib.api]);
|
|
|
|
|
|
- const data = useRef<{
|
|
|
|
- elements: readonly NonDeletedExcalidrawElement[];
|
|
|
|
- files: BinaryFiles | null;
|
|
|
|
- }>({ elements: [], files: null });
|
|
|
|
-
|
|
|
|
- const [error, setError] = useState<Error | null>(null);
|
|
|
|
-
|
|
|
|
return (
|
|
return (
|
|
<Dialog
|
|
<Dialog
|
|
className="ttd-dialog"
|
|
className="ttd-dialog"
|
|
@@ -243,6 +90,9 @@ export const TTDDialogBase = withInternalFallback(
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</TTDDialogTabTrigger>
|
|
</TTDDialogTabTrigger>
|
|
|
|
+ <TTDDialogTabTrigger tab="text-to-drawing">
|
|
|
|
+ {t("labels.textToDrawing")}
|
|
|
|
+ </TTDDialogTabTrigger>
|
|
<TTDDialogTabTrigger tab="mermaid">Mermaid</TTDDialogTabTrigger>
|
|
<TTDDialogTabTrigger tab="mermaid">Mermaid</TTDDialogTabTrigger>
|
|
</TTDDialogTabTriggers>
|
|
</TTDDialogTabTriggers>
|
|
)}
|
|
)}
|
|
@@ -254,93 +104,15 @@ export const TTDDialogBase = withInternalFallback(
|
|
</TTDDialogTab>
|
|
</TTDDialogTab>
|
|
{!("__fallback" in rest) && (
|
|
{!("__fallback" in rest) && (
|
|
<TTDDialogTab className="ttd-dialog-content" tab="text-to-diagram">
|
|
<TTDDialogTab className="ttd-dialog-content" tab="text-to-diagram">
|
|
- <div className="ttd-dialog-desc">
|
|
|
|
- Currently we use Mermaid as a middle step, so you'll get best
|
|
|
|
- results if you describe a diagram, workflow, flow chart, and
|
|
|
|
- similar.
|
|
|
|
- </div>
|
|
|
|
- <TTDDialogPanels>
|
|
|
|
- <TTDDialogPanel
|
|
|
|
- label={t("labels.prompt")}
|
|
|
|
- panelAction={{
|
|
|
|
- action: onGenerate,
|
|
|
|
- label: "Generate",
|
|
|
|
- icon: ArrowRightIcon,
|
|
|
|
- }}
|
|
|
|
- onTextSubmitInProgess={onTextSubmitInProgess}
|
|
|
|
- panelActionDisabled={
|
|
|
|
- prompt.length > MAX_PROMPT_LENGTH ||
|
|
|
|
- rateLimits?.rateLimitRemaining === 0
|
|
|
|
- }
|
|
|
|
- renderTopRight={() => {
|
|
|
|
- if (!rateLimits) {
|
|
|
|
- return null;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return (
|
|
|
|
- <div
|
|
|
|
- className="ttd-dialog-rate-limit"
|
|
|
|
- style={{
|
|
|
|
- fontSize: 12,
|
|
|
|
- marginLeft: "auto",
|
|
|
|
- color:
|
|
|
|
- rateLimits.rateLimitRemaining === 0
|
|
|
|
- ? "var(--color-danger)"
|
|
|
|
- : undefined,
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- {rateLimits.rateLimitRemaining} requests left today
|
|
|
|
- </div>
|
|
|
|
- );
|
|
|
|
- }}
|
|
|
|
- renderBottomRight={() => {
|
|
|
|
- const ratio = prompt.length / MAX_PROMPT_LENGTH;
|
|
|
|
- if (ratio > 0.8) {
|
|
|
|
- return (
|
|
|
|
- <div
|
|
|
|
- style={{
|
|
|
|
- marginLeft: "auto",
|
|
|
|
- fontSize: 12,
|
|
|
|
- fontFamily: "monospace",
|
|
|
|
- color:
|
|
|
|
- ratio > 1 ? "var(--color-danger)" : undefined,
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- Length: {prompt.length}/{MAX_PROMPT_LENGTH}
|
|
|
|
- </div>
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return null;
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- <TTDDialogInput
|
|
|
|
- onChange={handleTextChange}
|
|
|
|
- input={text}
|
|
|
|
- placeholder={"Describe what you want to see..."}
|
|
|
|
- onKeyboardSubmit={() => {
|
|
|
|
- refOnGenerate.current();
|
|
|
|
- }}
|
|
|
|
- />
|
|
|
|
- </TTDDialogPanel>
|
|
|
|
- <TTDDialogPanel
|
|
|
|
- label="Preview"
|
|
|
|
- panelAction={{
|
|
|
|
- action: () => {
|
|
|
|
- console.info("Panel action clicked");
|
|
|
|
- insertToEditor({ app, data });
|
|
|
|
- },
|
|
|
|
- label: "Insert",
|
|
|
|
- icon: ArrowRightIcon,
|
|
|
|
- }}
|
|
|
|
- >
|
|
|
|
- <TTDDialogOutput
|
|
|
|
- canvasRef={someRandomDivRef}
|
|
|
|
- error={error}
|
|
|
|
- loaded={mermaidToExcalidrawLib.loaded}
|
|
|
|
- />
|
|
|
|
- </TTDDialogPanel>
|
|
|
|
- </TTDDialogPanels>
|
|
|
|
|
|
+ <TextToDiagram
|
|
|
|
+ onTextSubmit={rest.onTextSubmit}
|
|
|
|
+ mermaidToExcalidrawLib={mermaidToExcalidrawLib}
|
|
|
|
+ />
|
|
|
|
+ </TTDDialogTab>
|
|
|
|
+ )}
|
|
|
|
+ {!("__fallback" in rest) && (
|
|
|
|
+ <TTDDialogTab className="ttd-dialog-content" tab="text-to-drawing">
|
|
|
|
+ <TextToDrawing onTextSubmit={rest.onTextSubmit} />
|
|
</TTDDialogTab>
|
|
</TTDDialogTab>
|
|
)}
|
|
)}
|
|
</TTDDialogTabs>
|
|
</TTDDialogTabs>
|