index.tsx 61 KB

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