l2Edit.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import { ArrowLeft, ChevronUp } from "lucide-react";
  2. import { useContext, useEffect, useMemo, useRef, useState } from "react";
  3. import { useTranslation } from "react-i18next";
  4. import { useNavigate, useParams } from "react-router-dom";
  5. import L2ParameterComponent from "../../CustomNodes/GenericNode/components/parameterComponent/l2Index";
  6. import ShadTooltip from "../../components/ShadTooltipComponent";
  7. import { Button } from "../../components/ui/button";
  8. import { Input } from "../../components/ui/input";
  9. import { Label } from "../../components/ui/label";
  10. import { Textarea } from "../../components/ui/textarea";
  11. import { alertContext } from "../../contexts/alertContext";
  12. import { TabsContext } from "../../contexts/tabsContext";
  13. import { userContext } from "../../contexts/userContext";
  14. import { createCustomFlowApi, getFlowApi } from "../../controllers/API/flow";
  15. import { useHasForm } from "../../util/hook";
  16. import FormSet from "./components/FormSet";
  17. import { captureAndAlertRequestErrorHoc } from "../../controllers/request";
  18. import shangchuan from "../../assets/npc/shangchuan.png";
  19. import shou from "../../assets/npc/shou.png";
  20. import huifumoren from "../../assets/npc/huifumoren.png";
  21. import nengliIcon from "../../assets/npc/nengliIcon.png";
  22. import robot from "../../assets/robot.png";
  23. import { uploadFileWithProgress, uploadNpcHeaderLibFileWithProgress } from "../../modals/UploadModal/upload";
  24. import { TitleIconBg, gradients } from "@/components/bs-comp/cardComponent";
  25. export default function l2Edit() {
  26. const { t } = useTranslation()
  27. const { id } = useParams()
  28. const { flow: nextFlow, setFlow, saveFlow } = useContext(TabsContext);
  29. const { setErrorData, setSuccessData } = useContext(alertContext);
  30. const flow = useMemo(() => {
  31. return id ? nextFlow : null
  32. }, [nextFlow])
  33. console.log(flow,'flow')
  34. const [isL2, setIsL2] = useState(false)
  35. const [loading, setLoading] = useState(false)
  36. const nameRef = useRef(null)
  37. const descRef = useRef(null)
  38. const guideRef = useRef(null)
  39. const [logo, setLogo] = useState("")
  40. const randomNum = Math.floor(Math.random()*(4-0+1)+0);
  41. useEffect(() => {
  42. // 无id不再请求
  43. if (!id) return
  44. // 已有flow 数据时,不再请求
  45. if (flow?.id === id) {
  46. setIsL2(true)
  47. nameRef.current.value = flow.name
  48. descRef.current.value = flow.description
  49. guideRef.current.value = flow.guide_word
  50. setLogo(flow.logo)
  51. return
  52. }
  53. // 无flow从db获取
  54. getFlowApi(id).then(_flow => {
  55. // 回填flow
  56. setFlow('l2 flow init', _flow)
  57. setIsL2(true)
  58. nameRef.current.value = _flow.name
  59. descRef.current.value = _flow.description
  60. guideRef.current.value = _flow.guide_word
  61. setLogo(_flow.logo)
  62. })
  63. }, [id])
  64. // 校验
  65. const { user } = useContext(userContext);
  66. const [error, setError] = useState({ name: false, desc: false }) // 表单error信息展示
  67. const isParamError = (name, desc, showErrorConfirm = false) => {
  68. const errorlist = [];
  69. if (!name) errorlist.push(t('skills.skillNameRequired'));
  70. if (name.length > 30) errorlist.push(t('skills.skillNameTooLong'));
  71. // Duplicate name validation
  72. const nameErrors = errorlist.length;
  73. if (!desc) errorlist.push(t('skills.skillDescRequired'));
  74. if (desc.length > 200) errorlist.push(t('skills.skillDescTooLong'));
  75. if (errorlist.length && showErrorConfirm) setErrorData({
  76. title: t('skills.errorTitle'),
  77. list: errorlist,
  78. });
  79. setError({ name: !!nameErrors, desc: errorlist.length > nameErrors });
  80. return !!errorlist.length;
  81. }
  82. const navigate = useNavigate()
  83. // 创建新技能
  84. const handleCreateNewSkill = async () => {
  85. const name = nameRef.current.value
  86. const guideWords = guideRef.current.value
  87. const description = descRef.current.value
  88. const avatar_img = logo
  89. const avatar_color = gradients[randomNum]
  90. if (isParamError(name, description, true)) return
  91. setLoading(true)
  92. await captureAndAlertRequestErrorHoc(createCustomFlowApi({
  93. name,
  94. description,
  95. guide_word: guideWords,
  96. avatar_img,
  97. avatar_color
  98. }, user.user_name).then(newFlow => {
  99. setFlow('l2 create flow', newFlow)
  100. navigate("/flow/" + newFlow.id, { replace: true }); // l3
  101. }))
  102. setLoading(false)
  103. }
  104. const formRef = useRef(null)
  105. // 编辑回填参数
  106. const handleJumpFlow = async () => {
  107. const name = nameRef.current.value
  108. const description = descRef.current.value
  109. const guideWords = guideRef.current.value
  110. // 高级配置信息有误直接跳转L3
  111. if (isParamError(name, description)) return navigate('/flow/' + id, { replace: true })
  112. // 保存在跳
  113. setLoading(true)
  114. formRef.current?.save()
  115. await saveFlow({...flow, name, description, guide_word: guideWords}, true)
  116. setLoading(false)
  117. navigate('/flow/' + id, { replace: true })
  118. }
  119. const handleSave = async () => {
  120. const name = nameRef.current.value
  121. const description = descRef.current.value
  122. const guideWords = guideRef.current.value
  123. const avatar_img = logo
  124. const avatar_color = gradients[randomNum]
  125. // const logo = flow.logo
  126. if (isParamError(name, description)) return
  127. setLoading(true)
  128. formRef.current?.save()
  129. console.log(flow,name,description,guideWords,logo)
  130. await saveFlow({...flow, name, description, guide_word: guideWords, avatar_img, avatar_color}, true)
  131. setLoading(false)
  132. setSuccessData({ title: t('success') });
  133. setTimeout(() => /^\/skill\/[\w\d-]+/.test(location.pathname) && navigate(-1), 2000);
  134. }
  135. // 表单收缩
  136. const showContent = (e) => {
  137. console.log(e,e.target.tagName)
  138. const target = e.target.tagName === 'IMG' ? e.target.parentNode : e.target
  139. const contentDom = target.nextSibling
  140. console.log(target)
  141. target.children[1].style.transform = contentDom.clientHeight ? 'rotate(180deg)' : 'rotate(0deg)'
  142. contentDom.style.display = contentDom.clientHeight ? 'none' : 'block'
  143. }
  144. // isForm
  145. const isForm = useHasForm(flow)
  146. const handleButtonClick = () => {
  147. // Create a file input element
  148. const input = document.createElement("input");
  149. input.type = "file";
  150. input.accept = "image/*";
  151. input.style.display = "none"; // Hidden from view
  152. input.multiple = false; // Allow only one file selection
  153. input.onchange = (e: Event) => {
  154. setLoading(true);
  155. // Get the selected file
  156. const file = (e.target as HTMLInputElement).files?.[0];
  157. // Check if the file type is correct
  158. // if (file && checkFileType(file.name)) {
  159. // Upload the file
  160. uploadNpcHeaderLibFileWithProgress(file, (progress) => { }).then(res => {
  161. // isSSO ? uploadFileWithProgress(file, (progress) => { }).then(res => {
  162. setLoading(false);
  163. if (typeof res === 'string') return setErrorData({ title: "Error", list: [res] })
  164. const { file_path } = res;
  165. setLogo(file_path);
  166. // logo = file_path;
  167. // setMyValue(file.name);
  168. // onChange(file.name);
  169. // sets the value that goes to the backend
  170. // onFileChange(file_path);
  171. })
  172. // uploadFile(file, flow.id)
  173. // .then((data) => {
  174. // console.log("File uploaded successfully");
  175. // // Get the file name from the response
  176. // const { file_path } = data;
  177. // // Update the state and callback with the name of the file
  178. // // sets the value to the user
  179. // setMyValue(file.name);
  180. // onChange(file.name);
  181. // // sets the value that goes to the backend
  182. // onFileChange(file_path);
  183. // setLoading(false);
  184. // })
  185. // .catch(() => {
  186. // console.error("Error occurred while uploading file");
  187. // setLoading(false);
  188. // });
  189. // } else {
  190. // // Show an error if the file type is not allowed
  191. // setErrorData({
  192. // title:
  193. // "请选择有效文件。只允许使用这些文件类型:",
  194. // list: fileTypes,
  195. // });
  196. // setLoading(false);
  197. // }
  198. };
  199. // Trigger the file selection dialog
  200. input.click();
  201. };
  202. return <div className="relative box-border">
  203. <div className="p-6 pb-48 h-screen overflow-y-auto">
  204. <div className="flex justify-between w-full">
  205. <ShadTooltip content={t('back')} side="right">
  206. <button className="extra-side-bar-buttons w-[36px]" onClick={() => window.history.length < 3 ? navigate('/skills') : navigate(-1)}>
  207. <ArrowLeft strokeWidth={1.5} className="side-bar-button-size" />
  208. </button>
  209. </ShadTooltip>
  210. {/* <ShadTooltip content="接口信息" side="left">
  211. <button className="extra-side-bar-buttons w-[36px]" onClick={() => openPopUp(<ApiModal flow={flows.find((f) => f.id === tabId)} />)} >
  212. <TerminalSquare strokeWidth={1.5} className="side-bar-button-size " ></TerminalSquare>
  213. </button>
  214. </ShadTooltip> */}
  215. </div>
  216. {/* form */}
  217. <div className="pt-6 skillSettings">
  218. {/* <p className="text-center text-2xl">{t('skills.skillSettings')}</p> */}
  219. <div className="skillSettingsTitle" onClick={showContent}>
  220. <p>基础信息</p>
  221. <img src={shou} alt="" />
  222. </div>
  223. <div className="skillSettingsDiv">
  224. <div className="pt-[20px] pr-[14px] pl-[14px]">
  225. <p>能力头像</p>
  226. <div className="flex items-center ml-[7px] mt-[10px]">
  227. {!logo ? <TitleIconBg className="w-[41px] h-[41px] min-w-[41px]" id={randomNum} ><img onClick={handleButtonClick} src={nengliIcon} alt="" /></TitleIconBg> : <img src={logo} className="w-[41px] h-[41px]" onClick={handleButtonClick} alt="" />}
  228. <div className="flex items-center justify-center ml-[20px] w-[95px] h-[27px] bg-[#333333] cursor-pointer" style={{borderRadius:"14px"}} onClick={() => setLogo(robot)}>
  229. <img src={huifumoren} className="w-[12px] h-[11px]" alt="" />
  230. <span className="ml-[5px] text-[#999999] text-[12px] mt-[1px]">恢复默认</span>
  231. </div>
  232. </div>
  233. </div>
  234. <div className="pt-[20px] pr-[14px] pl-[14px]">
  235. <p>能力名称</p>
  236. <Input ref={nameRef} placeholder={t('skills.skillName')} className={`mt-2 ${error.name && 'border-red-400'}`} />
  237. </div>
  238. <div className="pt-[20px] pr-[14px] pl-[14px]">
  239. <p>描述</p>
  240. <Textarea ref={descRef} id="name" placeholder={t('skills.description')} className={`mt-2 ${error.desc && 'border-red-400'}`} />
  241. </div>
  242. <div className="pt-[20px] pr-[14px] pl-[14px] pb-[20px]">
  243. <p>引导词</p>
  244. <Textarea ref={guideRef} maxLength={1000} id="name" placeholder={t('skills.guideWords')} className={`mt-2 ${error.desc && 'border-red-400'}`} />
  245. </div>
  246. </div>
  247. {isL2 && <div>
  248. <div className="skillSettingsTitle mt-[20px]" onClick={showContent}>
  249. <p>参数信息</p>
  250. <img src={shou} alt="" />
  251. </div>
  252. <div className="skillSettingsDiv skillSettingsInput">
  253. {flow.data.nodes.map(({ data, id }) => (
  254. <div key={id} className="w-full">
  255. <div className="only:hidden mt-6">
  256. <span className="jiedian">
  257. {data.node ? (data.node.l2_name || data.node.display_name) : ''}
  258. </span>
  259. </div>
  260. {
  261. // 自定义组件
  262. data.node && Object.keys(data.node.template).map(k => (
  263. data.node.template[k].l2 && <div className="w-full mt-4 px-1" key={k}>
  264. <Label htmlFor="name" className="text-right ml-[14px]">
  265. {data.node.template[k].l2_name || data.node.template[k].name}
  266. </Label>
  267. <L2ParameterComponent data={data} type={data.node.template[k].type} name={k} />
  268. </div>
  269. ))
  270. }
  271. </div>
  272. ))}
  273. </div>
  274. </div>}
  275. {isForm && <FormSet ref={formRef} id={id}></FormSet>}
  276. {/* <div className="w-[50%] max-w-2xl mx-auto">
  277. {
  278. // L2 form
  279. isL2 && <div className="w-full mt-8">
  280. <p className="text-center text-gray-400 cursor-pointer flex justify-center" onClick={showContent}>
  281. {t('skills.parameterInfo')}
  282. <ChevronUp />
  283. </p>
  284. <div className="w-full overflow-hidden transition-all px-1">
  285. {flow.data.nodes.map(({ data }) => (
  286. <div key={data.id} className="w-full">
  287. <div className="only:hidden mt-6">
  288. <span className="p-2 font-bold text-gray-400 text-base">
  289. {data.node ? (data.node.l2_name || data.node.display_name) : ''}
  290. </span>
  291. </div>
  292. {
  293. // 自定义组件
  294. data.node && Object.keys(data.node.template).map(k => (
  295. data.node.template[k].l2 && <div className="w-full mt-4 px-1" key={k}>
  296. <Label htmlFor="name" className="text-right">
  297. {data.node.template[k].l2_name || data.node.template[k].name}
  298. </Label>
  299. <L2ParameterComponent data={data} type={data.node.template[k].type} name={k} />
  300. </div>
  301. ))
  302. }
  303. </div>
  304. ))}
  305. </div>
  306. </div>
  307. } */}
  308. {/* 表单设置 */}
  309. {/* {isForm && <FormSet ref={formRef} id={id}></FormSet>}
  310. </div> */}
  311. </div>
  312. {/* footer */}
  313. <div className="skillSettingsBtn">
  314. {/* <div className="absolute flex bottom-0 w-full py-8 justify-center bg-[#fff] border-t dark:bg-gray-900"> */}
  315. {
  316. isL2 ?
  317. <div className="flex gap-4">
  318. <Button disabled={loading} className="skillSettingsBtn1 w-[200px] h-[27px] rounded-full" onClick={handleSave}>
  319. {t('save')}
  320. </Button>
  321. <Button disabled={loading} className="skillSettingsBtn2 w-[200px] h-[27px] rounded-full ml-[40px]" variant="outline" onClick={() => handleJumpFlow()}>
  322. {t('skills.advancedConfiguration')}
  323. </Button>
  324. </div>
  325. :
  326. <div className="flex justify-center">
  327. <Button disabled={loading} className="skillSettingsBtn1 extra-side-bar-save-disable w-[200px] h-[27px] rounded-full" onClick={handleCreateNewSkill}>
  328. {t('skills.nextStep')}
  329. </Button>
  330. </div>
  331. }
  332. </div>
  333. </div>
  334. </div>
  335. };