AiCreator.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import React, { useRef, useState } from "react";
  2. import {
  3. CloseOutlined,
  4. SendOutlined,
  5. CaretDownOutlined,
  6. } from "@ant-design/icons";
  7. import { Button, Tooltip, Input, Form, Dropdown, message } from "antd";
  8. import type { DropDownProps } from "antd";
  9. import { useChat } from "@/hooks/useChat";
  10. import {} from "react-markdown";
  11. const items = [
  12. { key: "1", label: "流程图" },
  13. { key: "2", label: "泳道图" },
  14. { key: "3", label: "ER实体图" },
  15. { key: "4", label: "组织图" },
  16. { key: "5", label: "时序图" },
  17. ];
  18. export default function AICreator(props: {
  19. type: "mindmap" | "flow";
  20. onClose?: () => void;
  21. onChange?: (data: any) => void;
  22. onError?: (err: Error) => void;
  23. }) {
  24. const [focused, setFocused] = React.useState(false);
  25. const [input, setInput] = useState("");
  26. const [messageApi, contextHolder] = message.useMessage();
  27. const messageKey = "ailoading";
  28. const msgContent = useRef<string>("");
  29. const { loading, onRequest, cancel } = useChat({
  30. app_name: "system_design",
  31. onUpdate: (msg) => {
  32. setInput("");
  33. msgContent.current += msg.answer;
  34. },
  35. onSuccess: (msg) => {
  36. console.log("加载完毕!", msgContent.current);
  37. messageApi.open({
  38. key: messageKey,
  39. type: "success",
  40. content: "AI创作完成",
  41. duration: 2,
  42. style: {
  43. marginTop: 300,
  44. },
  45. });
  46. handleParse();
  47. },
  48. onError: (err) => {
  49. messageApi.open({
  50. key: messageKey,
  51. type: "error",
  52. content: err.message || "AI创作失败",
  53. duration: 2,
  54. style: {
  55. marginTop: 300,
  56. },
  57. });
  58. },
  59. });
  60. function regexExtractJSON(markdown: string) {
  61. const jsonRegex = /```(?:json)?\n([\s\S]*?)\n```/g;
  62. const matches = [];
  63. let match;
  64. while ((match = jsonRegex.exec(markdown)) !== null) {
  65. try {
  66. const jsonObj = JSON.parse(match[1]);
  67. matches.push(jsonObj);
  68. } catch (e) {
  69. console.warn("无效JSON:", match[0]);
  70. }
  71. }
  72. return matches;
  73. }
  74. const handleParse = () => {
  75. try {
  76. // 根据markdown格式取出json部分数据
  77. const md = msgContent.current;
  78. let json: string;
  79. if (md.includes("```json")) {
  80. json = regexExtractJSON(msgContent.current)?.[0];
  81. } else {
  82. json = JSON.parse(msgContent.current);
  83. }
  84. console.log("解析结果:", json);
  85. props.onChange?.(json);
  86. } catch (error) {
  87. messageApi.open({
  88. key: messageKey,
  89. type: "error",
  90. content: "AI创作失败",
  91. duration: 2,
  92. style: {
  93. marginTop: 300,
  94. },
  95. });
  96. console.error(error);
  97. props.onError?.(new Error("AI创作失败"));
  98. }
  99. };
  100. const [graphType, setGraphType] = useState<string>("流程图");
  101. const dropDownMenu: DropDownProps["menu"] = {
  102. items,
  103. onClick: (info) => {
  104. console.log(info);
  105. const type = items.find((item) => item.key === info.key);
  106. setGraphType(type?.label || "流程图");
  107. },
  108. };
  109. const handleStop = () => {
  110. cancel();
  111. messageApi.open({
  112. key: messageKey,
  113. type: "error",
  114. content: "AI创作已取消",
  115. duration: 2,
  116. style: {
  117. marginTop: 300,
  118. },
  119. });
  120. };
  121. // 处理提交
  122. const onSubmit = () => {
  123. if (input.trim()) {
  124. onRequest(
  125. `设计一个${graphType}, 返回图形json数据, 具体需求描述:${input}`,
  126. undefined,
  127. input
  128. );
  129. messageApi.open({
  130. key: messageKey,
  131. type: "loading",
  132. content: "AI创作中...",
  133. duration: 0,
  134. style: {
  135. marginTop: 300,
  136. },
  137. });
  138. }
  139. };
  140. React.useEffect(() => {
  141. return () => {
  142. // 取消所有进行中的请求
  143. const controller = new AbortController();
  144. controller.abort();
  145. };
  146. }, []);
  147. const handleList = [
  148. {
  149. key: "style",
  150. label: "风格美化",
  151. icon: "icon-yijianmeihua",
  152. color: "#a171f2",
  153. },
  154. {
  155. key: "grammar",
  156. label: "语法修复",
  157. icon: "icon-tubiao_yufajiucuo",
  158. color: "#00c4ad",
  159. },
  160. {
  161. key: "translation_en",
  162. label: "翻译为英文",
  163. icon: "icon-fanyiweiyingwen",
  164. color: "#8c4ff0",
  165. },
  166. {
  167. key: "translation_zh",
  168. label: "翻译为中文",
  169. icon: "icon-fanyiweizhongwen",
  170. color: "#3d72fb",
  171. },
  172. ];
  173. const getCurrentCells = () => {
  174. };
  175. // 风格美化
  176. const handleStyle = () => {
  177. onRequest('生成一个美化风格的配置', {
  178. onUpdate: (data) => {
  179. console.log('style update:', data);
  180. },
  181. onSuccess: (data) => {
  182. console.log(data);
  183. },
  184. onError: (err) => {
  185. console.error(err);
  186. }
  187. })
  188. };
  189. // 语法修复
  190. const handleGrammar = () => {
  191. };
  192. // 翻译
  193. const handleTranslation = (lang: "en" | "zh") => {
  194. };
  195. const handleAiFeature = (feature: string) => {
  196. switch (feature) {
  197. case "style":
  198. handleStyle();
  199. break;
  200. case "grammar":
  201. handleGrammar();
  202. break;
  203. case "translation_en":
  204. handleTranslation("en");
  205. break;
  206. case "translation_zh":
  207. handleTranslation("zh");
  208. break;
  209. default:
  210. break;
  211. }
  212. };
  213. return (
  214. <div className="flex-1 h-full" style={{ backgroundImage: "linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)"}}>
  215. {contextHolder}
  216. <div className="chat-head w-full h-40px px-10px color-#333 flex items-center justify-between">
  217. <span className="text-14px">
  218. <svg className="icon h-32px w-32px" aria-hidden="true">
  219. <use xlinkHref="#icon-AI1"></use>
  220. </svg>
  221. <span>AI创作</span>
  222. </span>
  223. <span>
  224. <Button
  225. type="text"
  226. size="small"
  227. icon={<CloseOutlined />}
  228. onClick={() => props.onClose?.()}
  229. ></Button>
  230. </span>
  231. </div>
  232. <div className="text-14px pl-12px text-#333">绘制图形</div>
  233. <div className="chat-content px-10px overflow-y-auto mt-12px">
  234. <div
  235. style={{
  236. borderColor: focused ? "#1890ff" : "#ddd",
  237. }}
  238. className="chat-foot bg-#fff rounded-10px border border-solid border-1px shadow-sm"
  239. >
  240. <Dropdown menu={dropDownMenu} placement="bottomLeft">
  241. <div className="text-12px pl-10px pt-10px cursor-pointer">
  242. 帮我绘制-{graphType}
  243. <CaretDownOutlined />
  244. </div>
  245. </Dropdown>
  246. <Form onFinish={onSubmit}>
  247. <Input.TextArea
  248. rows={3}
  249. autoSize={{ maxRows: 3, minRows: 3 }}
  250. placeholder="你可以这样问:用户登陆流程图"
  251. variant="borderless"
  252. onFocus={() => setFocused(true)}
  253. onBlur={() => setFocused(false)}
  254. value={input}
  255. onChange={(e) => setInput(e.target.value)}
  256. disabled={loading}
  257. onPressEnter={onSubmit}
  258. />
  259. <div className="text-right p-10px">
  260. {loading ? (
  261. <Tooltip title="停止生成">
  262. <Button
  263. type="primary"
  264. shape="circle"
  265. icon={<i className="iconfont icon-stopcircle" />}
  266. onClick={handleStop}
  267. ></Button>
  268. </Tooltip>
  269. ) : (
  270. <Button
  271. type="text"
  272. icon={<SendOutlined />}
  273. disabled={!input.trim()}
  274. htmlType="submit"
  275. ></Button>
  276. )}
  277. </div>
  278. </Form>
  279. </div>
  280. </div>
  281. <div className="text-14px pl-12px text-#333 mt-32px">图形处理</div>
  282. <div className="flex flex-wrap gap-10px p-10px">
  283. {handleList.map((item) => (
  284. <div
  285. key={item.key}
  286. className="flex-[40%] h-50px bg-#fff rounded-10px shadow-sm flex items-center pl-10px text-12px cursor-pointer"
  287. onClick={() => handleAiFeature(item.key)}
  288. >
  289. <i
  290. className={`iconfont ${item.icon} text-16px`}
  291. style={{ color: item.color }}
  292. ></i>
  293. <span className="ml-10px">{item.label}</span>
  294. </div>
  295. ))}
  296. </div>
  297. </div>
  298. );
  299. }