index.tsx 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. import * as React from 'react';
  2. import { useLocation } from 'react-router-dom';
  3. import { observer } from 'mobx-react';
  4. import './style.less';
  5. import {
  6. Button, Input, Form, Divider, Splitter, Select, InputNumber, InputNumberProps,
  7. Radio, Switch, Row, Col, Slider, Space, RadioChangeEvent,
  8. Spin, message, Typography, Tooltip,
  9. Cascader,
  10. Tag, Modal, Table,TablePaginationConfig,Drawer
  11. } from 'antd';
  12. import type { TableProps } from 'antd';
  13. import { PlusCircleOutlined, MinusCircleOutlined, ArrowLeftOutlined, InfoCircleOutlined,CloseCircleOutlined,LinkOutlined } from '@ant-design/icons';
  14. import { apis } from '@/apis';
  15. import router from '@/router';
  16. import LocalStorage from '@/LocalStorage';
  17. import Chat from '@/components/chat';
  18. import store from './store';
  19. import DrawerIndex from '@/pages/deepseek/knowledgeLib/detail/drawerIndex'
  20. const { TextArea } = Input;
  21. const FormItem = Form.Item;
  22. const { Option } = Select;
  23. const MAX_COUNT = 5;
  24. const Index = 1;
  25. const QuestionAnswerInfo: React.FC = () => {
  26. const { state,onChangePagination,onFetchUserListApi } = store;
  27. const { page,sourceData } = state;
  28. const [form] = Form.useForm();
  29. // top_p
  30. const [topPValue, setTopPValue] = React.useState(0.1);
  31. const TopPDecimalStep: React.FC = () => {
  32. const onChange: InputNumberProps['onChange'] = (value) => {
  33. if (Number.isNaN(value)) {
  34. return;
  35. }
  36. setTopPValue(value as number);
  37. };
  38. return (
  39. <Row>
  40. <Col span={12}>
  41. <Slider
  42. min={0}
  43. max={1}
  44. onChange={onChange}
  45. // value={typeof topPValue === 'number' ? topPValue : 0}
  46. value={topPValue}
  47. step={0.1}
  48. />
  49. </Col>
  50. <Col span={4}>
  51. <InputNumber
  52. min={0}
  53. max={1}
  54. className='form-input-number-small'
  55. step={0.01}
  56. value={topPValue}
  57. onChange={onChange}
  58. />
  59. </Col>
  60. </Row>
  61. );
  62. };
  63. const [tempValue, setTempValue] = React.useState(0.01);
  64. // temperature
  65. const TempDecimalStep: React.FC = () => {
  66. const onChange: InputNumberProps['onChange'] = (value) => {
  67. if (Number.isNaN(value)) {
  68. return;
  69. }
  70. setTempValue(value as number);
  71. };
  72. return (
  73. <Row>
  74. <Col span={12}>
  75. <Slider
  76. min={0}
  77. max={1}
  78. onChange={onChange}
  79. // value={typeof tempValue === 'number' ? tempValue : 0}
  80. value={tempValue}
  81. step={0.01}
  82. />
  83. </Col>
  84. <Col span={4}>
  85. <InputNumber
  86. min={0}
  87. max={1}
  88. className='form-input-number-small'
  89. step={0.01}
  90. value={tempValue}
  91. onChange={onChange}
  92. />
  93. </Col>
  94. </Row>
  95. );
  96. };
  97. type ModelList = {
  98. label: string,
  99. value: string,
  100. }[];
  101. type KnowledgeList = {
  102. label: string,
  103. value: string,
  104. createBy: string,
  105. }[];
  106. type AppTypeList = {
  107. label: string,
  108. value: string,
  109. children: {
  110. label: string,
  111. value: string,
  112. }[],
  113. }[];
  114. const tagRender = (props:any) => {
  115. const { label, value, closable, onClose } = props;
  116. return (
  117. <Tag
  118. color="blue"
  119. closable={closable}
  120. onClose={onClose} // 保留原有的删除事件
  121. style={{ marginRight: 4 }}
  122. >
  123. {label}
  124. {/* 在删除按钮后添加自定义图标 */}
  125. <LinkOutlined style={{
  126. marginLeft: 4,
  127. fontSize: 12,
  128. cursor: 'pointer',
  129. }}
  130. onMouseDown={(e) => {
  131. e.stopPropagation();
  132. e.preventDefault();
  133. }}
  134. onMouseUp={(e) => {
  135. e.stopPropagation();
  136. e.preventDefault();
  137. }}
  138. // 自定义图标点击事件
  139. onClick={(e) => {
  140. // 阻止事件冒泡到Tag,避免触发删除
  141. e.stopPropagation();
  142. e.preventDefault();
  143. knowledgeList.forEach((item) => {
  144. if (item.value === value) {
  145. // router.navigate({ pathname: `/deepseek/knowledgeLib/${value}/${item.createBy}`,},);
  146. setDrawerItem(item);
  147. setOpenDrawer(true)
  148. e.stopPropagation();
  149. }
  150. });
  151. // console.log('点击了额外图标,当前选项值:', value,props);
  152. // 这里可以添加你的业务逻辑,如:打开详情、编辑等
  153. }} />
  154. </Tag>
  155. );
  156. };
  157. const [step, setStep] = React.useState(1);
  158. const [pageLoading, setPageLoading] = React.useState(false);
  159. const [modelList, setModelList] = React.useState<ModelList>([]);
  160. const [knowledgeList, setKnowledgeList] = React.useState<KnowledgeList>([]);
  161. const [isVisible, setIsVisible] = React.useState(true);
  162. const [isVisibleCus, setIsVisibleCus] = React.useState(false);
  163. const [isVisibleSlice, setIsVisibleSlice] = React.useState(false);
  164. const [isVisibleRerank, setIsVisibleRerank] = React.useState(true);
  165. const [isDeepThinkVisible, setIsDeepThinkVisible] = React.useState(true);
  166. const [name, setName] = React.useState('');
  167. const [appTypeList, setAppTypeList] = React.useState<AppTypeList>([]);
  168. const [appVisibleList, setAppVisibleList] = React.useState<AppTypeList>([]); // 是否公开
  169. const [visibleFlag, setVisibleFlag] = React.useState<string|number>(0); // 是否公开用来判断是否展示VIP用户
  170. const [updateFlag, setUpdateFlag] = React.useState<boolean>();
  171. const [createFlag, setCreateFlag] = React.useState<boolean>();
  172. const [appProjectList, setAppProjectList] = React.useState<AppTypeList>([]);
  173. const [isAppPro, setIsAppPro] = React.useState<boolean>(false);
  174. const [appId, setAppId] = React.useState<string>('');
  175. const [fetchUserTypeList, setFetchUserTypeList] = React.useState<AppTypeList>([]); // 用户类型
  176. const [userName, setUserName] = React.useState<string>(''); // 用户名
  177. const [userNickName, setUserNickName] = React.useState<string>(''); // 用户昵称
  178. const [userType, setUserType] = React.useState<string>(''); // 用户类型
  179. const [vipList, setVipList] = React.useState<any>([]); // 用户列表
  180. const style: React.CSSProperties = {
  181. display: 'flex',
  182. flexDirection: 'column',
  183. gap: 8,
  184. width: 300,
  185. };
  186. const location = useLocation();
  187. const init = async (id: string) => {
  188. await Promise.all([
  189. api.fetchKnowlegde(),
  190. api.fetchAppType(),
  191. // api.fetchModelList(),
  192. api.fetchAppProType(),
  193. api.fetchAppVisible(id)
  194. ])
  195. if (id) {
  196. await api.fetchDetail(id);
  197. }
  198. onFetchUserListApi('','','');
  199. await api.fetchUserType();
  200. }
  201. React.useEffect(() => {
  202. const id = location?.state?.id;
  203. init(id);
  204. const uFlag = LocalStorage.getStatusFlag('deepseek:application:update');
  205. setUpdateFlag(uFlag);
  206. const cFlag = LocalStorage.getStatusFlag('deepseek:application:create');
  207. setCreateFlag(cFlag);
  208. }, []);
  209. // 定义一个状态来存储输入框数组
  210. const [inputs, setInputs] = React.useState([{ id: 1, value: '' }]);
  211. // 添加新输入框的函数
  212. const addInput = () => {
  213. const newId = inputs.length + 1; // 生成新的唯一ID
  214. setInputs([...inputs, { id: newId, value: '' }]);
  215. };
  216. // 删除输入框(按id删除+最少数量限制)
  217. const delInput = (id: number) => {
  218. if (inputs.length <= 1) {
  219. message.warning("至少保留1个预设问题");
  220. return;
  221. }
  222. setInputs(inputs.filter(input => input.id !== id));
  223. };
  224. // 处理输入变更的函数
  225. const handleChange = (id: number, value: string) => {
  226. setInputs(inputs.map(input => (input.id === id ? { ...input, value } : input)));
  227. };
  228. const handleAppChange = (typeId: number) => {
  229. if (typeId === 41) { // 根据实际值进行判断
  230. setIsAppPro(true);
  231. } else {
  232. setIsAppPro(false);
  233. }
  234. };
  235. const onChangeShow = (checked: boolean) => {
  236. console.log(`switch to ${checked}`);
  237. };
  238. const onChangeModel = (checked: boolean) => {
  239. setIsVisibleRerank(!isVisibleRerank);
  240. };
  241. const onChangeCount = (value: string) => {
  242. if (value === 'fixed') {
  243. setIsVisibleSlice(!isVisibleSlice);
  244. } else {
  245. setIsVisibleSlice(false);
  246. }
  247. };
  248. // 召回方式
  249. const onChangeRecallMethod = (e: RadioChangeEvent) => {
  250. };
  251. // 获取应用详情
  252. const api = {
  253. fetchDetail: async (app_id: string) => {
  254. setPageLoading(true);
  255. try {
  256. const res = await apis.fetchTakaiApplicationDetail(app_id);
  257. const sd = res.data.questionlist.map((item: any, index: number) => {
  258. return {
  259. "id": index + 1,
  260. "value": item.question,
  261. }
  262. });
  263. const info = res.data.detail;
  264. setAppId(info.appId);
  265. setTopPValue(info.topP as number);
  266. setTempValue(info.temperature as number);
  267. setName(info.name);
  268. interface Item2 {
  269. index_type_id: number,
  270. knowledge_id: string
  271. }
  272. interface Item {
  273. show_recall_result: boolean,
  274. recall_method: string,
  275. rerank_status: boolean,
  276. slice_config_type: string,
  277. slice_count: number,
  278. recall_slice_splicing_method: string,
  279. param_desc: string,
  280. rerank_model_name: string,
  281. rerank_index_type_list: [Item2],
  282. recall_index_type_list: [Item2]
  283. }
  284. const data_info: Item = JSON.parse(info.knowledgeInfo);
  285. if (data_info.param_desc === 'custom') {
  286. setIsVisibleCus(!isVisibleCus); //自定义回答风格
  287. }
  288. if (data_info.rerank_status === true) {
  289. setIsVisibleRerank(data_info.rerank_status) //模型
  290. }
  291. //召回切片数量
  292. if (data_info.slice_config_type === 'fixed') {
  293. setIsVisibleSlice(!isVisibleSlice);
  294. } else {
  295. setIsVisibleSlice(false);
  296. }
  297. if (info.typeId === 41) {
  298. setIsAppPro(true);
  299. } else {
  300. setIsAppPro(false);
  301. }
  302. if (info.model === 'Qwen3-30B') {
  303. setIsDeepThinkVisible(true);
  304. } else {
  305. setIsDeepThinkVisible(false);
  306. }
  307. form.setFieldsValue({
  308. id: info.id,
  309. name: info.name, //应用名称
  310. desc: info.desc, //应用描述
  311. prompt: info.prompt, //应用提示语
  312. top_p: info.topP as number, //topP
  313. temperature: info.temperature as number, //温度
  314. knowledge_ids: info.knowledgeIds,
  315. model: info.model,
  316. isDeepThink: info.isDeepThink,
  317. icon_color: info.icon_color,
  318. icon_type: info.icon_type,
  319. questionList: sd, //问题列表
  320. max_token: info.maxToken, //应用最大token
  321. updateDate: info.updateDate, // 更新时间
  322. appProId: info.appProId?.join('-'),// 项目
  323. typeId: info.typeId, //应用类型
  324. visible: info.visible || '0', //是否公开
  325. sort: info.sort || null, //显示顺序
  326. param_desc: data_info.param_desc, //回答风格
  327. show_recall_result: data_info.show_recall_result, //是否展示召回结果
  328. recall_method: data_info.recall_method, //召回方式
  329. rerank_status: data_info.rerank_status, //开启rerank
  330. rerank_model_name: 'rerank', //模型名称
  331. slice_config_type: data_info.slice_config_type, // 召回切片数量
  332. slice_count: data_info.slice_count, // 切片数量
  333. recall_slice_splicing_method: data_info.recall_slice_splicing_method, // 切片内容
  334. // rerank_status = 1 rerank_index_type_list
  335. // recall_method = 'embedding' || 'mixed' recall_index_type_list
  336. //recall_index_type_list: info.recall_index_type_list, //知识库id
  337. //rerank_index_type_list: info.rerank_index_type_list, //知识库id
  338. })
  339. setVisibleFlag(info.visible || '0')
  340. if(info.vipList&&info.vipList.length>0){
  341. setVipList(info.vipList);
  342. }
  343. if (sd.length > 0) {
  344. setInputs(sd);
  345. }
  346. } catch (error) {
  347. console.error(error);
  348. } finally {
  349. setPageLoading(false);
  350. }
  351. },
  352. //获取知识库列表
  353. fetchKnowlegde: async () => {
  354. try {
  355. const res = await apis.fetchTakaiKnowledgeList();
  356. const list = res.data.map((item: any) => {
  357. return {
  358. label: item.name,
  359. value: item.knowledgeId,
  360. createBy: item.createBy,
  361. }
  362. });
  363. setKnowledgeList(list);
  364. } catch (error: any) {
  365. console.error(error);
  366. }
  367. },
  368. // 获取应用类型
  369. fetchAppType: async () => {
  370. try {
  371. const res = await apis.fetchTakaiAppTypeList('app_type');
  372. const list = res.data.map((item: any) => {
  373. return {
  374. label: item.dictLabel,
  375. value: item.dictCode,
  376. }
  377. });
  378. setAppTypeList(list);
  379. } catch (error: any) {
  380. console.error(error);
  381. }
  382. },
  383. // 获取是否公开类型
  384. fetchAppVisible: async (id: string) => {
  385. try {
  386. const res = await apis.fetchTakaiAppTypeList('app_visible');
  387. const list = res.data.map((item: any) => {
  388. return {
  389. label: item.dictLabel,
  390. value: item.dictValue,
  391. }
  392. });
  393. setAppVisibleList(list);
  394. if (!id) {
  395. form.setFieldsValue({
  396. visible: list[0]?.value
  397. });
  398. setVisibleFlag(list[0]?.value);
  399. }
  400. } catch (error: any) {
  401. console.error(error);
  402. }
  403. },
  404. // 获取用户类型
  405. fetchUserType: async () => {
  406. try {
  407. const res = await apis.fetchTakaiAppTypeList('sys_user_type');
  408. const list = res.data.map((item: any) => {
  409. return {
  410. label: item.dictLabel,
  411. value: item.dictValue,
  412. }
  413. });
  414. setFetchUserTypeList(list);
  415. } catch (error: any) {
  416. console.error(error);
  417. }
  418. },
  419. // 获取模型列表
  420. fetchModelList: async () => {
  421. try {
  422. const res = await apis.fetchModelList();
  423. const list = res.data.data.map((item: any) => {
  424. return {
  425. label: item.modelName,
  426. value: item.modelCode,
  427. }
  428. });
  429. setModelList(list);
  430. } catch (error: any) {
  431. console.error(error);
  432. }
  433. },
  434. // 项目级应用下的类型
  435. fetchAppProType: async () => {
  436. try {
  437. const res = await apis.fetchTakaiAppTypeList('projectTree');
  438. console.log('res.data',res.data)
  439. const list: AppTypeList = res.data?.reduce((acc:any,item: any) => {
  440. if(item.children.length>0){
  441. item.children.forEach((val:any)=>{
  442. acc.push({
  443. label: val.label,
  444. value: `${item.value}-${val.value}`,
  445. })
  446. })
  447. }
  448. return acc;
  449. },[]);
  450. setAppProjectList(list);
  451. } catch (error: any) {
  452. console.error(error);
  453. }
  454. },
  455. // 获取用户列表信息
  456. fetchUserListApi: async () => {
  457. try {
  458. const res = await apis.fetchUserListApi({
  459. pageNum:page.pageNumber,
  460. pageSize: page.pageSize,
  461. userName:userName,
  462. nickName:userNickName,
  463. userType:userType
  464. });
  465. // setSourceData(res.rows)
  466. } catch (error) {
  467. console.error(error);
  468. }
  469. }
  470. }
  471. const handleRedioClick = (value: string) => {
  472. setIsVisibleCus(false);
  473. if (value === 'strict') {
  474. setTopPValue(0.5);
  475. setTempValue(0.01);
  476. } else if (value === 'moderate') {
  477. setTopPValue(0.7);
  478. setTempValue(0.50);
  479. } else if (value === 'flexib') {
  480. setTopPValue(0.9);
  481. setTempValue(0.90);
  482. }
  483. }
  484. const saveConfig = (type: 'SAVE' | 'SUBMIT') => {
  485. form.validateFields().then(async (values) => {
  486. const data = values;
  487. console.log(values, 'values');
  488. // 问题列表
  489. const question: string[] = [];
  490. if (inputs) {
  491. inputs.map((item, index) => {
  492. question.push(item.value);
  493. });
  494. }
  495. console.log(question, 'question');
  496. interface Item {
  497. index_type_id: number,
  498. knowledge_id: string
  499. }
  500. const indexTypeList: Item[] = [];
  501. if (values.knowledge_ids && values.knowledge_ids.length > 0) {
  502. console.log("knowledge_ids", values.knowledge_ids);
  503. const index_type: Item = {
  504. index_type_id: 0,
  505. knowledge_id: values.knowledge_ids
  506. };
  507. indexTypeList.push(index_type);
  508. }
  509. const userInfo = LocalStorage.getUserInfo();
  510. const userId = (userInfo?.id ?? '').toString();
  511. const data_info = {
  512. param_desc: values.param_desc, //回答风格
  513. show_recall_result: values.show_recall_result, //是否展示召回结果
  514. recall_method: values.recall_method, //召回方式
  515. rerank_status: values.rerank_status, //开启rerank
  516. rerank_model_name: values.rerank_model_name, //模型名称
  517. slice_config_type: values.slice_config_type, // 召回切片数量
  518. slice_count: values.slice_count, // 切片数量
  519. recall_slice_splicing_method: values.recall_slice_splicing_method, // 切片内容
  520. rerank_index_type_list: values.rerank_status === true ? indexTypeList : [], //知识库id
  521. recall_index_type_list: values.recall_method === 'embedding' || 'mixed' ? indexTypeList : []
  522. // rerank_status = 1 rerank_index_type_list
  523. // recall_method = 'embedding' || 'embedding' recall_index_type_list
  524. };
  525. // const knowledgeIds: string[] = [];
  526. // knowledgeIds.push(values.knowledge_ids);
  527. const info = {
  528. id: values.id,
  529. name: values.name, //应用名称
  530. desc: values.desc, //应用描述
  531. prompt: values.prompt, //应用提示语
  532. top_p: topPValue.toString(), //topP
  533. temperature: tempValue.toString(), //温度
  534. knowledge_ids: values.knowledge_ids,
  535. slice_count: values.slice_count,
  536. model: values.model,
  537. isDeepThink: values.isDeepThink,
  538. icon_color: values.icon_color,
  539. icon_type: values.icon_type,
  540. questionList: question,
  541. knowledge_info: JSON.stringify(data_info),
  542. max_token: values.max_token, //应用最大token
  543. typeId: values.typeId, // 应用类型
  544. visible: values.visible, // 是否公开
  545. sort: values.sort||null, // 显示顺序
  546. vipList: vipList, // vip用户列表
  547. appProId: values?.appProId?.[0]?.split('-') ?? null,// 项目
  548. userId: userId, // 用户id
  549. };
  550. console.log(info, 'info data');
  551. const id = location?.state?.id;
  552. let res = null;
  553. if (id) {
  554. // 编辑应用
  555. res = await apis.modifyTakaiApplicationApi(id, info);
  556. } else {
  557. // 创建应用
  558. res = await await apis.createTakaiApplicationApi(info);
  559. }
  560. console.log(res, 'create or modify');
  561. if (res.data === 9) {
  562. message.error('没有配置审核人,请联系管理员');
  563. } else if (res.data === 1) {
  564. message.success('操作成功')
  565. } else {
  566. message.error('操作失败');
  567. }
  568. if (type === 'SUBMIT') {
  569. router.navigate({ pathname: '/deepseek/questionAnswer' });
  570. }
  571. }).catch((error) => {
  572. console.error(error);
  573. error.errorFields && error.errorFields.map((item: any) => {
  574. console.log(item, 'item');
  575. message.error(`字段 ${item.name} ${item.errors[0]}`);
  576. });
  577. });
  578. }
  579. /*
  580. 选择VIP用户弹窗start
  581. */
  582. const [isModalOpen, setIsModalOpen] = React.useState(false);
  583. let falgVipList:any = [];
  584. const handleOk = () => {
  585. setIsModalOpen(false);
  586. let vipListFalg:any = [...vipList];
  587. const vipIds = new Set(vipListFalg.map((vip:any) => vip.userId));
  588. const merged = [...vipListFalg];
  589. falgVipList.forEach((item:any) => {
  590. if (!vipIds.has(item.userId)) {
  591. merged.push(item);
  592. }
  593. });
  594. setVipList([...merged]);
  595. };
  596. const handleCancel = async () => {
  597. setIsModalOpen(false);
  598. };
  599. interface DataType {
  600. name: string;
  601. key: string;
  602. nickName: string;
  603. userName: string;
  604. deptName: string;
  605. userTypeName: string;
  606. }
  607. // const [sourceData, setSourceData] = React.useState<DataType[]>([]);
  608. const paginationConfig: TablePaginationConfig = {
  609. // 显示数据总量
  610. showTotal: (total: number) => {
  611. return `共 ${total} 条`;
  612. },
  613. // 展示分页条数切换
  614. showSizeChanger: false,
  615. // 指定每页显示条数
  616. // 快速跳转至某页
  617. showQuickJumper: false,
  618. current: page.pageNumber,
  619. pageSize: page.pageSize,
  620. total: page.total,
  621. onChange: async (page, pageSize) => {
  622. await onChangePagination(page, pageSize,userName,userNickName,userType);
  623. },
  624. };
  625. const vipModal = () => {
  626. const columns: TableProps<DataType>['columns'] = [
  627. {
  628. title: '昵称',
  629. dataIndex: 'nickName',
  630. render: (text) => <p>{text}</p>,
  631. },
  632. {
  633. title: '用户名称',
  634. dataIndex: 'userName',
  635. render: (text) => <p>{text}</p>,
  636. },
  637. {
  638. title: '部门',
  639. dataIndex: 'deptName',
  640. },
  641. {
  642. title: '用户类型',
  643. dataIndex: 'userTypeName',
  644. },
  645. ];
  646. const rowSelection: TableProps<DataType>['rowSelection'] = {
  647. type: 'checkbox',
  648. onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
  649. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  650. falgVipList = selectedRows
  651. },
  652. getCheckboxProps: (record: DataType) => ({
  653. disabled: record.name === 'Disabled User', // Column configuration not to be checked
  654. name: record.name,
  655. }),
  656. };
  657. return (
  658. <>
  659. <Modal
  660. title="请选择指定用户"
  661. open={isModalOpen}
  662. onOk={handleOk}
  663. onCancel={handleCancel}
  664. width='80%'
  665. >
  666. <div className='modal_top'>
  667. <Input placeholder="请输入用户昵称" allowClear onChange={(e)=>{
  668. setUserNickName(e.target.value)
  669. }} />
  670. <Input placeholder="请输入用户名称" allowClear onChange={(e)=>{
  671. setUserName(e.target.value)
  672. }} />
  673. <Select
  674. placeholder='请选择用户类型'
  675. style={{ width: 150 }}
  676. onChange={(e) => {
  677. if(e === undefined){
  678. setUserType('')
  679. return
  680. }
  681. setUserType(e)
  682. }}
  683. allowClear={true}
  684. >
  685. {
  686. fetchUserTypeList.map((item, index) => {
  687. return <Option value={item.value} key={index}>
  688. {item.label}
  689. </Option>
  690. })
  691. }
  692. </Select>
  693. <Button value="large" style={{
  694. background: 'transparent',
  695. border: '1px solid #1677ff',
  696. color: '#1677ff'
  697. }}
  698. onClick={() => { onFetchUserListApi(userName,userNickName,userType) }}
  699. > 搜索 </Button>
  700. {/* <Button value="large"
  701. onClick={() => { api.fetchUserListApi() }}
  702. > 重置 </Button> */}
  703. </div>
  704. <Table<DataType> pagination={paginationConfig} rowKey="userName" rowSelection={rowSelection} columns={columns} dataSource={sourceData} />
  705. </Modal>
  706. </>
  707. )
  708. }
  709. /*
  710. 选择VIP用户弹窗end
  711. */
  712. /*
  713. 查看引用知识库抽屉start
  714. */
  715. const [openDrawer, setOpenDrawer] = React.useState(false);
  716. const [drawerItem,setDrawerItem] = React.useState<any>({});
  717. const onCloseDrawer = () => {
  718. setOpenDrawer(false);
  719. }
  720. const DrawerDetail = ()=>{
  721. return (
  722. <Drawer
  723. title={drawerItem?.label}
  724. width={'80%'}
  725. closable={{ 'aria-label': 'Close Button' }}
  726. onClose={onCloseDrawer}
  727. open={openDrawer}
  728. style={{zIndex:11111}}
  729. >
  730. <DrawerIndex drawerItem={drawerItem}></DrawerIndex>
  731. </Drawer>
  732. )
  733. }
  734. /*
  735. 查看引用知识库抽屉end
  736. */
  737. return (
  738. <>
  739. <div className='questionAnswerInfo'>
  740. <Spin spinning={pageLoading}>
  741. <Form
  742. form={form}
  743. layout='vertical'
  744. initialValues={{
  745. isDeepThink: 'N',
  746. max_token: 4096,
  747. model:'Qwen3-30B',
  748. show_recall_result: true,
  749. rerank_model_name:'rerank',
  750. slice_config_type:'customized',
  751. rerank_status: true,
  752. param_desc:'strict',
  753. recall_method:'mixed'
  754. }}
  755. >
  756. <div style={{ display: step === 1 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
  757. <FormItem
  758. label='问答应用名称'
  759. name='name'
  760. rules={[{ required: true, message: '问答应用名称不能为空' }]}
  761. >
  762. <Input placeholder="请输入问答应用名称" className='form-element-standard' style={{ height: '48px'}}/>
  763. </FormItem>
  764. <FormItem
  765. label='应用类型'
  766. name='typeId'
  767. >
  768. <Select
  769. className='form-element-select'
  770. style={{ height: '48px'}}
  771. placeholder='请选择问答应用类型'
  772. onChange={handleAppChange}
  773. allowClear={true}
  774. >
  775. {
  776. appTypeList.map((item, index) => {
  777. return <Option value={item.value} key={index}>
  778. {item.label}
  779. </Option>
  780. })
  781. }
  782. </Select>
  783. </FormItem>
  784. {
  785. isAppPro &&
  786. <>
  787. <FormItem
  788. label='项目'
  789. name='appProId'
  790. rules={[{ required: true, message: '项目不能为空' }]}
  791. >
  792. <Cascader
  793. options={appProjectList}
  794. placeholder="请选择项目"
  795. showSearch
  796. className="form-element-select"
  797. style={{ height: '48px'}}
  798. />
  799. </FormItem>
  800. </>
  801. }
  802. <FormItem
  803. label='是否公开'
  804. name='visible'
  805. >
  806. <Select
  807. className='form-element-select'
  808. style={{ height: '48px'}}
  809. placeholder='请选择是否公开'
  810. allowClear={true}
  811. onChange={(e) => {
  812. setVisibleFlag(e)
  813. }}
  814. >
  815. {
  816. appVisibleList.map((item, index) => {
  817. return <Option value={item.value} key={index}>
  818. {item.label}
  819. </Option>
  820. })
  821. }
  822. </Select>
  823. </FormItem>
  824. <FormItem
  825. label='显示顺序'
  826. name='sort'
  827. >
  828. <InputNumber placeholder="请输入显示顺序" value={''} className='form-element-standard' style={{ height: '48px',lineHeight:'48px'}}/>
  829. </FormItem>
  830. {/* VIP用户 */}
  831. {visibleFlag==1&&<FormItem
  832. label='指定用户'
  833. >
  834. <div className='tags-info'>
  835. <p className='tags-list'>
  836. {vipList.map((item: any) =>
  837. (<Tag key={item.userId} color="blue" closeIcon onClose={(e)=>{
  838. const newVipList = vipList.filter((vip:any) => vip.userId !== item.userId);
  839. setVipList(newVipList);
  840. e.preventDefault();
  841. }}>
  842. {item.userName}
  843. </Tag>)
  844. )}
  845. </p>
  846. <p>
  847. {vipList.length>0&&<CloseCircleOutlined className='cup' onClick={()=>{
  848. setVipList([]);
  849. }} />}
  850. <Button style={{
  851. background: 'transparent',
  852. border: '1px solid #1677ff',
  853. color: '#1677ff'
  854. }} type="primary" variant="outlined" onClick={() => { setIsModalOpen(true) }}>选择</Button>
  855. </p>
  856. </div>
  857. </FormItem>}
  858. <FormItem
  859. label='问答应用描述'
  860. name='desc'
  861. rules={[{ required: true, message: '问答应用描述不能为空' }]}
  862. >
  863. <TextArea
  864. showCount
  865. maxLength={500}
  866. placeholder="请输入当前应用的描述"
  867. className='form-textarea-large'
  868. />
  869. </FormItem>
  870. <div className='preset-questions'>
  871. <h4>添加引导问题</h4>
  872. <div>
  873. {
  874. inputs.map(input => (
  875. <div key={input.id} className='question-item'>
  876. <label>引导问题 {input.id}</label>
  877. <Input
  878. className='question-input'
  879. type="text"
  880. value={input.value}
  881. onChange={e => handleChange(input.id, e.target.value)}
  882. />
  883. <div className='question-actions'>
  884. <PlusCircleOutlined className='question-icon' onClick={addInput} />
  885. <MinusCircleOutlined className='question-icon' onClick={() => delInput(input.id)} />
  886. </div>
  887. </div>
  888. ))}
  889. </div>
  890. </div>
  891. <div style={{ display: 'flex', gap: '12px', marginTop: '24px', paddingTop: '24px', borderTop: '1px solid #f0f0f0' }}>
  892. <Button
  893. className='btn-cancel'
  894. onClick={() => {
  895. router.navigate({ pathname: '/deepseek/questionAnswer' });
  896. }}
  897. >
  898. 返回
  899. </Button>
  900. <Button
  901. type='primary'
  902. onClick={() => {
  903. form.validateFields(['name', 'desc', 'appProId']).then(async (values) => {
  904. setStep(2);
  905. setInputs(inputs);
  906. }).catch((error) => {
  907. console.error(error);
  908. });
  909. }}
  910. >
  911. 下一步
  912. </Button>
  913. </div>
  914. </div>
  915. <div style={{ display: step === 2 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
  916. <div className='flex-between padding-bottom-16'>
  917. <div>
  918. <Button
  919. className='btn-back'
  920. icon={<ArrowLeftOutlined />}
  921. onClick={() => {
  922. setStep(1);
  923. }}
  924. >
  925. 上一步
  926. </Button>
  927. </div>
  928. <div style={{ display: 'flex', gap: '12px' }}>
  929. <Button
  930. className='btn-cancel'
  931. onClick={() => {
  932. router.navigate({ pathname: '/deepseek/questionAnswer' });
  933. }}
  934. >
  935. 取消
  936. </Button>
  937. {
  938. appId && (<Button
  939. type='primary'
  940. className='btn-cancel'
  941. onClick={() => {
  942. saveConfig('SAVE');
  943. }}
  944. >
  945. 保存
  946. </Button>)
  947. }
  948. {createFlag && (
  949. <Button
  950. type='primary'
  951. onClick={() => {
  952. saveConfig('SUBMIT');
  953. }}
  954. >
  955. 提交应用
  956. </Button>
  957. )}
  958. </div>
  959. </div>
  960. <div className='section-title'>
  961. Prompt编写与参数配置
  962. <Tooltip
  963. title="Prompt用于对大模型的回复做出一些列指令和约束。这段Prompt不会被用户看到。"
  964. placement="right"
  965. overlayStyle={{ fontSize: '12px' }}
  966. >
  967. <InfoCircleOutlined style={{ marginLeft: '8px', color: '#999', fontSize: '14px' }} />
  968. </Tooltip>
  969. </div>
  970. <Splitter style={{ border: '1px solid #f0f0f0', borderRadius: '6px', height: 550 }}>
  971. <Splitter.Panel defaultSize="35%">
  972. <div className='prompt'>
  973. <div className='prompt-info'>
  974. <div className='prompt-info-text'>
  975. <Typography.Paragraph style={{ fontSize: '12px', lineHeight: '1.6', color: '#999', margin: 0 }}>
  976. 编写Prompt过程中可以引入2项变量:
  977. <span className='variable-highlight'>{'{{知识}}'}</span>
  978. 代表知识库中检索到的知识内容,
  979. <span className='variable-highlight'>{'{{用户}}'}</span>
  980. 代表用户输入的内容。您可以在编写Prompt过程中将变量拼接在合适的位置。
  981. {/* <br />
  982. 插入:
  983. <span className='variable-highlight'>{'{{知识}}'}</span>,
  984. 插入:
  985. <span className='variable-highlight'>{'{{用户}}'}</span>。 */}
  986. </Typography.Paragraph>
  987. </div>
  988. </div>
  989. {/* 移除 Divider,使用 CSS 边框替代 */}
  990. <div className='prompt-editor-area'>
  991. <FormItem name='prompt'
  992. initialValue={
  993. `你是一位知识检索助手,你必须并且只能从我发送的众多知识片段中寻找能够解决用户输入问题的最优答案,并且在执行任务的过程中严格执行规定的要求。 \n
  994. 知识片段如下:
  995. {{知识}}
  996. 规定要求:
  997. - 找到答案就仅使用知识片段中的原文回答用户的提问;- 找不到答案就用自身知识并且告诉用户该信息不是来自文档;
  998. - 所引用的文本片段中所包含的示意图占位符必须进行返回,占位符格式参考:【示意图序号_编号】
  999. - 严禁输出任何知识片段中不存在的示意图占位符;
  1000. - 输出的内容必须删除其中包含的任何图注、序号等信息。例如:“进入登录页面(图1.1)”需要从文字中删除图序,回复效果为:“进入登录页面”;“如图所示1.1”,回复效果为:“如图所示”;
  1001. 格式规范:
  1002. - 文档中会出现包含表格的情况,表格是以图片标识符的形式呈现,表格中缺失数据时候返回空单元格;
  1003. - 如果需要用到表格中的数据,以代码块语法输出表格中的数据;
  1004. - 避免使用代码块语法回复信息;
  1005. - 回复的开头语不要输出诸如:“我想”,“我认为”,“think”等相关语义的文本。
  1006. 严格执行规定要求,不要复述问题,直接开始回答。
  1007. 用户输入问题:
  1008. {{用户}}`
  1009. }
  1010. rules={[{ required: true, message: '提示词不能为空' }]}>
  1011. <TextArea
  1012. placeholder="提示词"
  1013. rows={50}
  1014. />
  1015. </FormItem>
  1016. </div>
  1017. </div>
  1018. </Splitter.Panel>
  1019. <Splitter.Panel defaultSize="30%">
  1020. <div className='flex-center-container'>
  1021. <div className='half-width'>
  1022. <div className='flex-center-top'>
  1023. 欢迎使用 {name || '问答应用'}
  1024. </div>
  1025. <div className='flex-start pl-20 padding-top-20'>
  1026. <FormItem
  1027. label='引用知识库'
  1028. name='knowledge_ids'
  1029. rules={[{ required: true, message: '知识库不能为空' }]}>
  1030. <Select
  1031. mode='multiple'
  1032. maxCount={MAX_COUNT}
  1033. showSearch={true}
  1034. className='form-element-select'
  1035. placeholder='请选择需要引用的知识库'
  1036. tagRender={tagRender}
  1037. >
  1038. {
  1039. knowledgeList.map((item, index) => {
  1040. return <Option value={item.value} key={index}>
  1041. {item.label}
  1042. </Option>
  1043. })
  1044. }
  1045. </Select>
  1046. </FormItem>
  1047. </div>
  1048. <div className='flex-start pl-20'>
  1049. <FormItem
  1050. label='调用模型'
  1051. name="model"
  1052. rules={[{ required: true, message: '模型不能为空' }]}>
  1053. <Select
  1054. placeholder='请选择模型'
  1055. allowClear={true}
  1056. className='form-element-select'
  1057. onChange={(value) => {
  1058. if (value === 'Qwen3-30B') {
  1059. setIsDeepThinkVisible(true);
  1060. } else {
  1061. setIsDeepThinkVisible(false);
  1062. }
  1063. }}
  1064. >
  1065. <Option value='Qwen3-30B'>Qwen3-30B</Option>
  1066. <Option value='Qwen2-72B'>Qwen2-72B</Option>
  1067. </Select>
  1068. </FormItem>
  1069. </div>
  1070. <div className='flex-start pl-20' style={{
  1071. display: isDeepThinkVisible ? 'flex' : 'none'
  1072. }}>
  1073. <FormItem
  1074. label='深度思考'
  1075. name='isDeepThink'
  1076. rules={[{ required: true, message: '请选择是否深度思考' }]}>
  1077. <Radio.Group buttonStyle="solid" className='form-element-button-group'>
  1078. <Radio.Button value='Y'>是</Radio.Button>
  1079. <Radio.Button value='N'>否</Radio.Button>
  1080. </Radio.Group>
  1081. </FormItem>
  1082. </div>
  1083. <div className='flex-start pl-20'>
  1084. <FormItem
  1085. label='max token'
  1086. name='max_token'
  1087. rules={[{ required: true, message: 'max token不能为空' }]}>
  1088. <InputNumber
  1089. className='form-element-input-number'
  1090. />
  1091. </FormItem>
  1092. </div>
  1093. {
  1094. !isVisible &&
  1095. <div className='flex-start pl-20'>
  1096. <a onClick={() => {
  1097. setIsVisible(!isVisible);
  1098. }} className='link-more-settings'>
  1099. 更多设置
  1100. </a>
  1101. </div>
  1102. }
  1103. {/* {isVisible && */}
  1104. <div style={{ display: isVisible ? 'block' : 'none', paddingTop: '20px' }}>
  1105. {isVisibleCus &&
  1106. <Space style={{ width: '100%' }} direction="vertical">
  1107. <div className='flex-start pl-20'>
  1108. <FormItem
  1109. label='Top-p'
  1110. name='topP'
  1111. className='form-element-standard'
  1112. >
  1113. <TopPDecimalStep />
  1114. </FormItem>
  1115. </div>
  1116. <div className='flex-start pl-20'>
  1117. <FormItem
  1118. label='Temperature'
  1119. name='temperature'
  1120. className='form-element-standard'
  1121. >
  1122. <TempDecimalStep />
  1123. </FormItem>
  1124. </div>
  1125. </Space>
  1126. }
  1127. <div style={{
  1128. display: 'flex',
  1129. justifyContent: 'flex-start',
  1130. alignItems: 'center'
  1131. }} className='pl-20'>
  1132. <FormItem
  1133. label='回答风格'
  1134. name='param_desc'
  1135. rules={[{ required: true, message: '回答风格不能为空' }]}>
  1136. <Radio.Group buttonStyle="solid"
  1137. className='form-element-button-group'>
  1138. <Radio.Button onClick={() => {
  1139. handleRedioClick('strict')
  1140. }} value='strict'>严谨</Radio.Button>
  1141. <Radio.Button onClick={() => {
  1142. handleRedioClick('moderate')
  1143. }} value='moderate'>适中</Radio.Button>
  1144. <Radio.Button onClick={() => {
  1145. handleRedioClick('flexib')
  1146. }} value='flexib'>发散</Radio.Button>
  1147. <Radio.Button value='custom'
  1148. onClick={() => {
  1149. setIsVisibleCus(!isVisibleCus);
  1150. setTopPValue(0.1);
  1151. setTempValue(0.01);
  1152. }}
  1153. >自定义
  1154. </Radio.Button>
  1155. </Radio.Group>
  1156. </FormItem>
  1157. </div>
  1158. <div style={{
  1159. display: 'flex',
  1160. justifyContent: 'flex-start',
  1161. alignItems: 'center'
  1162. }} className='pl-20'>
  1163. <FormItem
  1164. label='展示引用知识'
  1165. name='show_recall_result'
  1166. className='form-element-standard'>
  1167. <Switch onChange={onChangeShow} />
  1168. </FormItem>
  1169. </div>
  1170. <div style={{
  1171. display: 'flex',
  1172. justifyContent: 'flex-start',
  1173. alignItems: 'center'
  1174. }} className='pl-20'>
  1175. <FormItem
  1176. label='召回方式'
  1177. name='recall_method'
  1178. rules={[{ required: true, message: '召回方式不能为空' }]}>
  1179. <Radio.Group
  1180. style={style}
  1181. onChange={onChangeRecallMethod}
  1182. options={[
  1183. { value: 'embedding', label: '向量化检索' },
  1184. { value: 'keyword', label: '关键词检索' },
  1185. { value: 'mixed', label: '混合检索' },
  1186. ]}
  1187. />
  1188. </FormItem>
  1189. </div>
  1190. <div style={{
  1191. display: 'flex',
  1192. justifyContent: 'flex-start',
  1193. alignItems: 'center'
  1194. }} className='pl-20'>
  1195. <div className='section-title'>重排方式</div>
  1196. </div>
  1197. <div style={{
  1198. display: 'flex',
  1199. justifyContent: 'flex-start',
  1200. alignItems: 'center'
  1201. }} className='pl-20'>
  1202. <FormItem
  1203. label='Rerank模型'
  1204. name='rerank_status'
  1205. valuePropName='checked'
  1206. className='form-control-width'
  1207. >
  1208. <Switch onChange={onChangeModel} />
  1209. </FormItem>
  1210. </div>
  1211. {isVisibleRerank &&
  1212. <div style={{
  1213. display: 'flex',
  1214. justifyContent: 'flex-start',
  1215. alignItems: 'center'
  1216. }} className='pl-20'>
  1217. <FormItem
  1218. label='模型选择'
  1219. name='rerank_model_name'
  1220. >
  1221. <Select
  1222. className='questionAnswerInfo-content-title'
  1223. placeholder='请选择模型'
  1224. // defaultValue={'rerank'}
  1225. >
  1226. <Option value='rerank'>默认Rerank模型</Option>
  1227. </Select>
  1228. </FormItem>
  1229. </div>
  1230. }
  1231. <div style={{
  1232. display: 'flex',
  1233. justifyContent: 'flex-start',
  1234. alignItems: 'center'
  1235. }} className='pl-20'>
  1236. <FormItem
  1237. label='召回切片数量'
  1238. name='slice_config_type'
  1239. rules={[{ required: true, message: '召回方式不能为空' }]}>
  1240. <Select
  1241. // className='questionAnswerInfo-content-title'
  1242. className='form-element-select'
  1243. placeholder='请选择'
  1244. onChange={onChangeCount}>
  1245. <Option value="fixed">手动设置</Option>
  1246. <Option value="customized">自动设置</Option>
  1247. </Select>
  1248. </FormItem>
  1249. </div>
  1250. {isVisibleSlice &&
  1251. <div style={{
  1252. display: 'flex',
  1253. justifyContent: 'flex-start',
  1254. alignItems: 'center'
  1255. }} className='pl-20'>
  1256. <FormItem
  1257. label='召回切片数'
  1258. name='slice_count'
  1259. rules={[{ required: true, message: '切片数不能为空' }]}>
  1260. <InputNumber max={1024} changeOnWheel
  1261. // className='questionAnswerInfo-content-title'
  1262. className='form-element-standard'
  1263. />
  1264. </FormItem>
  1265. </div>
  1266. }
  1267. <div style={{
  1268. display: 'flex',
  1269. justifyContent: 'flex-start',
  1270. alignItems: 'center'
  1271. }} className='pl-20'>
  1272. <FormItem
  1273. label='召回切片拼接方式'
  1274. name='recall_slice_splicing_method'
  1275. >
  1276. <TextArea
  1277. rows={4}
  1278. className='form-textarea-large'
  1279. placeholder="请输入内容"
  1280. />
  1281. </FormItem>
  1282. </div>
  1283. </div>
  1284. {/* } */}
  1285. </div>
  1286. </div>
  1287. </Splitter.Panel>
  1288. {appId && (<Splitter.Panel defaultSize="35%">
  1289. <Chat appId={appId} />
  1290. </Splitter.Panel>)}
  1291. </Splitter>
  1292. </div>
  1293. </Form>
  1294. </Spin>
  1295. </div >
  1296. {isModalOpen && vipModal()}
  1297. {DrawerDetail()}
  1298. </>
  1299. );
  1300. };
  1301. export default observer(QuestionAnswerInfo);