TaskBuilderChapter.tsx 13 KB

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