AI.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import {
  2. DiagramToCodePlugin,
  3. exportToBlob,
  4. getTextFromElements,
  5. MIME_TYPES,
  6. TTDDialog,
  7. } from "@excalidraw/excalidraw";
  8. import { getDataURL } from "@excalidraw/excalidraw/data/blob";
  9. import { safelyParseJSON } from "@excalidraw/common";
  10. import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
  11. export const AIComponents = ({
  12. excalidrawAPI,
  13. }: {
  14. excalidrawAPI: ExcalidrawImperativeAPI;
  15. }) => {
  16. return (
  17. <>
  18. <DiagramToCodePlugin
  19. generate={async ({ frame, children }) => {
  20. const appState = excalidrawAPI.getAppState();
  21. const blob = await exportToBlob({
  22. elements: children,
  23. appState: {
  24. ...appState,
  25. exportBackground: true,
  26. viewBackgroundColor: appState.viewBackgroundColor,
  27. },
  28. exportingFrame: frame,
  29. files: excalidrawAPI.getFiles(),
  30. mimeType: MIME_TYPES.jpg,
  31. });
  32. const dataURL = await getDataURL(blob);
  33. const textFromFrameChildren = getTextFromElements(children);
  34. const response = await fetch(
  35. `${
  36. import.meta.env.VITE_APP_AI_BACKEND
  37. }/v1/ai/diagram-to-code/generate`,
  38. {
  39. method: "POST",
  40. headers: {
  41. Accept: "application/json",
  42. "Content-Type": "application/json",
  43. },
  44. body: JSON.stringify({
  45. texts: textFromFrameChildren,
  46. image: dataURL,
  47. theme: appState.theme,
  48. }),
  49. },
  50. );
  51. if (!response.ok) {
  52. const text = await response.text();
  53. const errorJSON = safelyParseJSON(text);
  54. if (!errorJSON) {
  55. throw new Error(text);
  56. }
  57. if (errorJSON.statusCode === 429) {
  58. return {
  59. html: `<html>
  60. <body style="margin: 0; text-align: center">
  61. <div style="display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100vh; padding: 0 60px">
  62. <div style="color:red">Too many requests today,</br>please try again tomorrow!</div>
  63. </br>
  64. </br>
  65. <div>You can also try <a href="${
  66. import.meta.env.VITE_APP_PLUS_LP
  67. }/plus?utm_source=excalidraw&utm_medium=app&utm_content=d2c" target="_blank" rel="noopener">Excalidraw+</a> to get more requests.</div>
  68. </div>
  69. </body>
  70. </html>`,
  71. };
  72. }
  73. throw new Error(errorJSON.message || text);
  74. }
  75. try {
  76. const { html } = await response.json();
  77. if (!html) {
  78. throw new Error("Generation failed (invalid response)");
  79. }
  80. return {
  81. html,
  82. };
  83. } catch (error: any) {
  84. throw new Error("Generation failed (invalid response)");
  85. }
  86. }}
  87. />
  88. <TTDDialog
  89. onTextSubmit={async (input) => {
  90. try {
  91. const response = await fetch(
  92. `${
  93. import.meta.env.VITE_APP_AI_BACKEND
  94. }/v1/ai/text-to-diagram/generate`,
  95. {
  96. method: "POST",
  97. headers: {
  98. Accept: "application/json",
  99. "Content-Type": "application/json",
  100. },
  101. body: JSON.stringify({ prompt: input }),
  102. },
  103. );
  104. const rateLimit = response.headers.has("X-Ratelimit-Limit")
  105. ? parseInt(response.headers.get("X-Ratelimit-Limit") || "0", 10)
  106. : undefined;
  107. const rateLimitRemaining = response.headers.has(
  108. "X-Ratelimit-Remaining",
  109. )
  110. ? parseInt(
  111. response.headers.get("X-Ratelimit-Remaining") || "0",
  112. 10,
  113. )
  114. : undefined;
  115. const json = await response.json();
  116. if (!response.ok) {
  117. if (response.status === 429) {
  118. return {
  119. rateLimit,
  120. rateLimitRemaining,
  121. error: new Error(
  122. "Too many requests today, please try again tomorrow!",
  123. ),
  124. };
  125. }
  126. throw new Error(json.message || "Generation failed...");
  127. }
  128. const generatedResponse = json.generatedResponse;
  129. if (!generatedResponse) {
  130. throw new Error("Generation failed...");
  131. }
  132. return { generatedResponse, rateLimit, rateLimitRemaining };
  133. } catch (err: any) {
  134. throw new Error("Request failed");
  135. }
  136. }}
  137. />
  138. </>
  139. );
  140. };