TaskBuilderChapter.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import {
  2. Button,
  3. Divider,
  4. Input,
  5. message,
  6. Modal,
  7. Space,
  8. Steps,
  9. Typography,
  10. } from "antd";
  11. import { useState } from "react";
  12. import Workflow from "./Workflow";
  13. import {
  14. IProjectTreeData,
  15. IProjectTreeInsertRequest,
  16. IProjectTreeResponse,
  17. ITaskData,
  18. ITaskGroupInsertData,
  19. ITaskGroupInsertRequest,
  20. ITaskGroupResponse,
  21. } from "../api/task";
  22. import ChapterToc from "../article/ChapterToc";
  23. import { IChapterToc } from "../api/Corpus";
  24. import { post } from "../../request";
  25. import TaskBuilderProp, { IParam, IProp } from "./TaskBuilderProp";
  26. import {
  27. IPayload,
  28. ITokenCreate,
  29. ITokenCreateResponse,
  30. ITokenData,
  31. TPower,
  32. } from "../api/token";
  33. import ProjectWithTasks from "./ProjectWithTasks";
  34. const { Text, Paragraph } = Typography;
  35. interface IModal {
  36. studioName?: string;
  37. channels?: string[];
  38. book?: number;
  39. para?: number;
  40. open?: boolean;
  41. onClose?: () => void;
  42. }
  43. export const TaskBuilderChapterModal = ({
  44. studioName,
  45. channels,
  46. book,
  47. para,
  48. open = false,
  49. onClose,
  50. }: IModal) => {
  51. return (
  52. <>
  53. <Modal
  54. destroyOnClose={true}
  55. width={1400}
  56. style={{ top: 10 }}
  57. title={""}
  58. footer={false}
  59. open={open}
  60. onOk={onClose}
  61. onCancel={onClose}
  62. >
  63. <TaskBuilderChapter
  64. style={{ marginTop: 20 }}
  65. studioName={studioName}
  66. channels={channels}
  67. book={book}
  68. para={para}
  69. />
  70. </Modal>
  71. </>
  72. );
  73. };
  74. interface IWidget {
  75. studioName?: string;
  76. channels?: string[];
  77. book?: number;
  78. para?: number;
  79. style?: React.CSSProperties;
  80. }
  81. const TaskBuilderChapter = ({
  82. studioName,
  83. book,
  84. para,
  85. style,
  86. channels,
  87. }: IWidget) => {
  88. const [current, setCurrent] = useState(0);
  89. const [workflow, setWorkflow] = useState<ITaskData[]>();
  90. const [chapter, setChapter] = useState<IChapterToc[]>();
  91. const [tokens, setTokens] = useState<ITokenData[]>();
  92. const [messages, setMessages] = useState<string[]>([]);
  93. const [prop, setProp] = useState<IProp[]>();
  94. const [title, setTitle] = useState<string>();
  95. const [loading, setLoading] = useState(false);
  96. const [projects, setProjects] = useState<IProjectTreeData[]>();
  97. const [done, setDone] = useState(false);
  98. const steps = [
  99. {
  100. title: "选择章节",
  101. content: (
  102. <div style={{ padding: 8 }}>
  103. <Space key={1}>
  104. <Text type="secondary">{"任务组标题"}</Text>
  105. <Input
  106. value={title}
  107. onChange={(e) => {
  108. setTitle(e.target.value);
  109. }}
  110. />
  111. </Space>
  112. <ChapterToc
  113. key={2}
  114. book={book}
  115. para={para}
  116. maxLevel={7}
  117. onData={(data: IChapterToc[]) => {
  118. setChapter(data);
  119. if (data.length > 0) {
  120. if (!title && data[0].text) {
  121. setTitle(data[0].text);
  122. }
  123. }
  124. //获取channel token
  125. let payload: IPayload[] = [];
  126. channels?.forEach((channel) => {
  127. data.forEach((chapter) => {
  128. const power: TPower[] = ["readonly", "edit"];
  129. payload = payload.concat(
  130. power.map((item) => {
  131. return {
  132. res_id: channel,
  133. res_type: "channel",
  134. book: chapter.book,
  135. para_start: chapter.paragraph,
  136. para_end: chapter.paragraph + chapter.chapter_len,
  137. power: item,
  138. };
  139. })
  140. );
  141. });
  142. });
  143. const url = "/v2/access-token";
  144. const values = { payload: payload };
  145. console.info("api request", url, values);
  146. post<ITokenCreate, ITokenCreateResponse>(url, values).then(
  147. (json) => {
  148. console.info("api response", json);
  149. setTokens(json.data.rows);
  150. }
  151. );
  152. }}
  153. />
  154. </div>
  155. ),
  156. },
  157. {
  158. title: "选择工作流",
  159. content: (
  160. <Workflow
  161. studioName={studioName}
  162. onData={(data) => setWorkflow(data)}
  163. />
  164. ),
  165. },
  166. {
  167. title: "参数设置",
  168. content: (
  169. <div>
  170. <TaskBuilderProp
  171. workflow={workflow}
  172. channelsId={channels}
  173. onChange={(data: IProp[] | undefined) => {
  174. console.info("prop value", data);
  175. setProp(data);
  176. }}
  177. />
  178. </div>
  179. ),
  180. },
  181. {
  182. title: "生成任务",
  183. content: (
  184. <div style={{ padding: 8 }}>
  185. <div>
  186. <Space>
  187. <Text type="secondary">title</Text>
  188. <Text>{title}</Text>
  189. </Space>
  190. </div>
  191. <div>
  192. <Space>
  193. <Text type="secondary">新增任务组</Text>
  194. <Text>{chapter?.length}</Text>
  195. </Space>
  196. </div>
  197. <div>
  198. <Space>
  199. <Text type="secondary">每个任务组任务数量</Text>
  200. <Text>{workflow?.length}</Text>
  201. </Space>
  202. </div>
  203. <div>
  204. <Paragraph>点击生成按钮生成</Paragraph>
  205. </div>
  206. <div>
  207. {messages?.map((item, id) => {
  208. return <div key={id}>{item}</div>;
  209. })}
  210. </div>
  211. </div>
  212. ),
  213. },
  214. {
  215. title: "完成",
  216. content: projects ? (
  217. <ProjectWithTasks projectId={projects[0].id} />
  218. ) : (
  219. <></>
  220. ),
  221. },
  222. ];
  223. const next = () => {
  224. setCurrent(current + 1);
  225. };
  226. const prev = () => {
  227. setCurrent(current - 1);
  228. };
  229. const items = steps.map((item) => ({ key: item.title, title: item.title }));
  230. const DoButton = () => (
  231. <Button
  232. loading={loading}
  233. disabled={loading}
  234. type="primary"
  235. onClick={async () => {
  236. if (!studioName || !chapter) {
  237. console.error("缺少参数", studioName, chapter);
  238. return;
  239. }
  240. setLoading(true);
  241. //生成projects
  242. setMessages((origin) => [...origin, "正在生成任务组……"]);
  243. const url = "/v2/project-tree";
  244. const values: IProjectTreeInsertRequest = {
  245. studio_name: studioName,
  246. data: chapter.map((item, id) => {
  247. return {
  248. id: item.paragraph.toString(),
  249. title: id === 0 && title ? title : item.text ?? "",
  250. type: "instance",
  251. weight: item.chapter_strlen,
  252. parent_id: item.parent.toString(),
  253. res_id: `${item.book}-${item.paragraph}`,
  254. };
  255. }),
  256. };
  257. console.info("api request", url, values);
  258. const res = await post<IProjectTreeInsertRequest, IProjectTreeResponse>(
  259. url,
  260. values
  261. );
  262. console.info("api response", res);
  263. if (!res.ok) {
  264. setMessages((origin) => [...origin, "正在生成任务组失败"]);
  265. return;
  266. } else {
  267. setProjects(res.data.rows);
  268. setMessages((origin) => [...origin, "生成任务组成功"]);
  269. }
  270. //生成tasks
  271. setMessages((origin) => [...origin, "正在生成任务……"]);
  272. const taskUrl = "/v2/task-group";
  273. if (!workflow) {
  274. return;
  275. }
  276. let taskData: ITaskGroupInsertData[] = res.data.rows
  277. .filter((value) => value.isLeaf)
  278. .map((project, pId) => {
  279. return {
  280. project_id: project.id,
  281. tasks: workflow.map((task, tId) => {
  282. let newContent = task.description;
  283. prop
  284. ?.find((pValue) => pValue.taskId === task.id)
  285. ?.param?.forEach((value: IParam) => {
  286. //替换数字参数
  287. if (value.type === "number") {
  288. const searchValue = `${value.key}=${value.value}`;
  289. const replaceValue =
  290. `${value.key}=` +
  291. (value.initValue + value.step * pId).toString();
  292. newContent = newContent?.replace(
  293. searchValue,
  294. replaceValue
  295. );
  296. } else {
  297. //替换book
  298. if (project.resId) {
  299. const [book, paragraph] = project.resId.split("-");
  300. newContent = newContent?.replace(
  301. "book=#",
  302. `book=${book}`
  303. );
  304. newContent = newContent?.replace(
  305. "paragraphs=#",
  306. `paragraphs=${paragraph}`
  307. );
  308. //替换channel
  309. //查找toke
  310. const [channel, power] = value.value.split("@");
  311. const mToken = tokens?.find(
  312. (token) =>
  313. token.payload.book?.toString() === book &&
  314. token.payload.para_start?.toString() ===
  315. paragraph &&
  316. token.payload.res_id === channel &&
  317. (power && power.length > 0
  318. ? token.payload.power === power
  319. : true)
  320. );
  321. newContent = newContent?.replace(
  322. value.key,
  323. channel + (mToken ? "@" + mToken?.token : "")
  324. );
  325. }
  326. }
  327. });
  328. console.debug("description", newContent);
  329. return {
  330. ...task,
  331. type: "instance",
  332. description: newContent,
  333. };
  334. }),
  335. };
  336. });
  337. console.info("api request", taskUrl, taskData);
  338. const taskRes = await post<ITaskGroupInsertRequest, ITaskGroupResponse>(
  339. taskUrl,
  340. { data: taskData }
  341. );
  342. if (taskRes.ok) {
  343. message.success("ok");
  344. setMessages((origin) => [...origin, "生成任务成功"]);
  345. setMessages((origin) => [
  346. ...origin,
  347. "生成任务" + taskRes.data.taskCount,
  348. ]);
  349. setMessages((origin) => [
  350. ...origin,
  351. "生成任务关联" + taskRes.data.taskRelationCount,
  352. ]);
  353. setMessages((origin) => [
  354. ...origin,
  355. "打开译经楼-我的任务查看已经生成的任务",
  356. ]);
  357. setDone(true);
  358. }
  359. setLoading(false);
  360. }}
  361. >
  362. Done
  363. </Button>
  364. );
  365. return (
  366. <div style={style}>
  367. <Steps current={current} items={items} />
  368. <div className="steps-content" style={{ minHeight: 400 }}>
  369. {steps[current].content}
  370. </div>
  371. <Divider></Divider>
  372. <div style={{ display: "flex", justifyContent: "space-between" }}>
  373. {current < steps.length - 1 ? (
  374. <Button
  375. style={{ margin: "0 8px" }}
  376. disabled={current === 0}
  377. onClick={() => prev()}
  378. >
  379. Previous
  380. </Button>
  381. ) : (
  382. <></>
  383. )}
  384. {current < steps.length - 2 && (
  385. <Button type="primary" onClick={() => next()}>
  386. Next
  387. </Button>
  388. )}
  389. {current === steps.length - 2 && (
  390. <>
  391. {done ? (
  392. <Button type="primary" onClick={() => next()}>
  393. 完成
  394. </Button>
  395. ) : (
  396. <DoButton />
  397. )}
  398. </>
  399. )}
  400. </div>
  401. </div>
  402. );
  403. };
  404. export default TaskBuilderChapter;