index.tsx 81 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586
  1. import * as React from 'react';
  2. import { useLocation, useNavigate } from 'react-router-dom';
  3. import { LeftOutlined } from '@ant-design/icons';
  4. import './style.scss';
  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, ColorPicker
  11. } from 'antd';
  12. import type { TableProps } from 'antd';
  13. import { PlusCircleOutlined, MinusCircleOutlined, ArrowLeftOutlined, InfoCircleOutlined, CloseCircleOutlined, LinkOutlined, FileDoneOutlined } from '@ant-design/icons';
  14. import * as AllIcons from '@ant-design/icons';
  15. import IconPicker from './component/IconPicker';
  16. import { apis } from '@/apis';
  17. import router from '@/router';
  18. import LocalStorage from '@/LocalStorage';
  19. import Chat from '@/components/chat';
  20. import { useQuestionAnswerInfoStore } from './store';
  21. import DrawerIndex from '@/pages/knowledgeLib/detail/drawerIndex'
  22. const { TextArea } = Input;
  23. const FormItem = Form.Item;
  24. const { Option } = Select;
  25. const MAX_COUNT = 5;
  26. const QuestionAnswerInfo: React.FC = () => {
  27. const {
  28. page,
  29. sourceData,
  30. pageLoading,
  31. setPageLoading,
  32. setPage,
  33. setSourceData,
  34. fetchUserListApi,
  35. } = useQuestionAnswerInfoStore();
  36. const navigate = useNavigate();
  37. const [form] = Form.useForm();
  38. const [iconPickerVisible, setIconPickerVisible] = React.useState(false);
  39. const [selectedIcon, setSelectedIcon] = React.useState<string | null>(null);
  40. const [previewBg, setPreviewBg] = React.useState<string>('#ffffff');
  41. const getContrastColor = (hex: string) => {
  42. // remove #
  43. const c = hex.replace('#', '');
  44. const r = parseInt(c.substring(0, 2), 16);
  45. const g = parseInt(c.substring(2, 4), 16);
  46. const b = parseInt(c.substring(4, 6), 16);
  47. // relative luminance
  48. const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  49. return luminance > 0.6 ? '#000' : '#fff';
  50. }
  51. const presetColors = ['#1677ff', '#52c41a', '#fa8c16', '#f5222d', '#722ed1', '#ffffff', '#f0f0f0'];
  52. const presetItems = [{ label: '', colors: presetColors }];
  53. // top_p
  54. const [topPValue, setTopPValue] = React.useState(0.1);
  55. const [topKValue, setTopKValue] = React.useState(1);
  56. const TopPDecimalStep: React.FC = () => {
  57. const onChange: InputNumberProps['onChange'] = (value) => {
  58. if (Number.isNaN(value)) {
  59. return;
  60. }
  61. setTopPValue(value as number);
  62. };
  63. return (
  64. <Row>
  65. <Col span={12}>
  66. <Slider
  67. min={0}
  68. max={1}
  69. onChange={onChange}
  70. // value={typeof topPValue === 'number' ? topPValue : 0}
  71. value={topPValue}
  72. step={0.1}
  73. />
  74. </Col>
  75. <Col span={4}>
  76. <InputNumber
  77. min={0}
  78. max={1}
  79. className='form-input-number-small'
  80. step={0.01}
  81. value={topPValue}
  82. onChange={onChange}
  83. />
  84. </Col>
  85. </Row>
  86. );
  87. };
  88. const [tempValue, setTempValue] = React.useState(0.01);
  89. // temperature
  90. const TempDecimalStep: React.FC = () => {
  91. const onChange: InputNumberProps['onChange'] = (value) => {
  92. if (Number.isNaN(value)) {
  93. return;
  94. }
  95. setTempValue(value as number);
  96. };
  97. return (
  98. <Row>
  99. <Col span={12}>
  100. <Slider
  101. min={0}
  102. max={1}
  103. onChange={onChange}
  104. // value={typeof tempValue === 'number' ? tempValue : 0}
  105. value={tempValue}
  106. step={0.01}
  107. />
  108. </Col>
  109. <Col span={4}>
  110. <InputNumber
  111. min={0}
  112. max={1}
  113. className='form-input-number-small'
  114. step={0.01}
  115. value={tempValue}
  116. onChange={onChange}
  117. />
  118. </Col>
  119. </Row>
  120. );
  121. };
  122. type ModelList = {
  123. label: string,
  124. value: string,
  125. }[];
  126. type KnowledgeList = {
  127. label: string,
  128. value: string,
  129. createBy: string,
  130. }[];
  131. type AppTypeList = {
  132. label: string,
  133. value: string,
  134. children: {
  135. label: string,
  136. value: string,
  137. }[],
  138. }[];
  139. const tagRender = (props: any) => {
  140. const { label, value, closable, onClose } = props;
  141. return (
  142. <Tag
  143. color="blue"
  144. closable={closable}
  145. onClose={onClose} // 保留原有的删除事件
  146. style={{ marginRight: 4 }}
  147. >
  148. {label}
  149. {/* 在删除按钮后添加自定义图标 */}
  150. <LinkOutlined style={{
  151. marginLeft: 4,
  152. fontSize: 12,
  153. cursor: 'pointer',
  154. color: '#FF9500',
  155. fontWeight: 'bold',
  156. }}
  157. onMouseDown={(e) => {
  158. e.stopPropagation();
  159. e.preventDefault();
  160. }}
  161. onMouseUp={(e) => {
  162. e.stopPropagation();
  163. e.preventDefault();
  164. }}
  165. // 自定义图标点击事件
  166. onClick={(e) => {
  167. // 阻止事件冒泡到Tag,避免触发删除
  168. e.stopPropagation();
  169. e.preventDefault();
  170. knowledgeList.forEach((item) => {
  171. if (item.value === value) {
  172. // router.navigate({ pathname: `/deepseek/knowledgeLib/${value}/${item.createBy}`,},);
  173. setDrawerItem(item);
  174. setOpenDrawer(true)
  175. e.stopPropagation();
  176. }
  177. });
  178. // console.log('点击了额外图标,当前选项值:', value,props);
  179. // 这里可以添加你的业务逻辑,如:打开详情、编辑等
  180. }} />
  181. </Tag>
  182. );
  183. };
  184. const [step, setStep] = React.useState(1);
  185. const [modelList, setModelList] = React.useState<ModelList>([]);
  186. const [knowledgeList, setKnowledgeList] = React.useState<KnowledgeList>([]);
  187. const [isVisible, setIsVisible] = React.useState(true);
  188. const [isVisibleCus, setIsVisibleCus] = React.useState(false);
  189. const [isVisibleSlice, setIsVisibleSlice] = React.useState(false);
  190. const [isVisibleRerank, setIsVisibleRerank] = React.useState(true);
  191. const [isDeepThinkVisible, setIsDeepThinkVisible] = React.useState(true);
  192. const [name, setName] = React.useState('');
  193. const [appTypeList, setAppTypeList] = React.useState<AppTypeList>([]);
  194. const [appVisibleList, setAppVisibleList] = React.useState<AppTypeList>([]); // 是否公开
  195. const [visibleFlag, setVisibleFlag] = React.useState<string | number>(0); // 是否公开用来判断是否展示VIP用户
  196. const [updateFlag, setUpdateFlag] = React.useState<boolean>();
  197. const [createFlag, setCreateFlag] = React.useState<boolean>();
  198. const [appProjectList, setAppProjectList] = React.useState<AppTypeList>([]);
  199. const [isAppPro, setIsAppPro] = React.useState<boolean>(false);
  200. const [appId, setAppId] = React.useState<string>('');
  201. const [fetchUserTypeList, setFetchUserTypeList] = React.useState<AppTypeList>([]); // 用户类型
  202. const [userName, setUserName] = React.useState<string>(''); // 用户名
  203. const [userNickName, setUserNickName] = React.useState<string>(''); // 用户昵称
  204. const [userType, setUserType] = React.useState<string>(''); // 用户类型
  205. const [vipList, setVipList] = React.useState<any>([]); // 用户列表
  206. const [infoDetail, setinfoDetail] = React.useState<any>(null);// 知识库详情
  207. const [parameter,setParameter] = React.useState<any>(null);
  208. const style: React.CSSProperties = {
  209. display: 'flex',
  210. flexDirection: 'column',
  211. gap: 8,
  212. width: 300,
  213. };
  214. const userInfo = LocalStorage.getUserInfo();
  215. const userId = (userInfo?.id ?? '').toString();
  216. const location = useLocation();
  217. const [modeList,setModeList] = React.useState<any>([]); // 模型列表数据
  218. const [modeOldList,setModeOldList] = React.useState<any>([]); // 模型列表数据(原始)
  219. // 获取调用模型的列表数据
  220. const onFetchRerankModelList = async () => {
  221. const res = await apis.fetchRerankModelList();
  222. if(res && res.data){
  223. const list = res.data.map((item: any) => {
  224. return {
  225. label: item.model,
  226. value: item.model,
  227. }
  228. })
  229. setModeList(list);
  230. setModeOldList(res.data);
  231. }
  232. }
  233. const init = async (id: string) => {
  234. await Promise.all([
  235. api.fetchKnowlegde(),
  236. api.fetchAppType(),
  237. // api.fetchModelList(),
  238. api.fetchAppProType(),
  239. api.fetchAppVisible(id)
  240. ])
  241. if (id) {
  242. await api.fetchDetail(id);
  243. }
  244. onFetchUserListApi('', '', '');
  245. onFetchRerankModelList();
  246. await api.fetchUserType();
  247. }
  248. React.useEffect(() => {
  249. const id = location?.state?.id;
  250. init(id);
  251. const uFlag = LocalStorage.getStatusFlag('deepseek:application:update');
  252. setUpdateFlag(uFlag);
  253. setParameter(LocalStorage.getStatusFlag('appCenter:questionAnswer:parameter'));
  254. setEditPrompt(LocalStorage.getStatusFlag('appCenter:questionAnswer:parameter'))
  255. setCreateFlag(true);
  256. }, []);
  257. // 定义一个状态来存储输入框数组
  258. const [inputs, setInputs] = React.useState([{ id: 1, value: '' }]);
  259. // 添加新输入框的函数
  260. const addInput = () => {
  261. const newId = inputs.length + 1; // 生成新的唯一ID
  262. setInputs([...inputs, { id: newId, value: '' }]);
  263. };
  264. // 删除输入框(按id删除+最少数量限制)
  265. const delInput = (id: number) => {
  266. if (inputs.length <= 1) {
  267. message.warning("至少保留1个预设问题");
  268. return;
  269. }
  270. setInputs(inputs.filter(input => input.id !== id));
  271. };
  272. // 处理输入变更的函数
  273. const handleChange = (id: number, value: string) => {
  274. setInputs(inputs.map(input => (input.id === id ? { ...input, value } : input)));
  275. };
  276. const handleAppChange = (typeId: number) => {
  277. console.log('typeId', typeId)
  278. if (typeId === 62) { // 根据实际值进行判断
  279. setIsAppPro(true);
  280. } else {
  281. setIsAppPro(false);
  282. }
  283. };
  284. const onChangeShow = (checked: boolean) => {
  285. console.log(`switch to ${checked}`);
  286. };
  287. const onChangeCount = (value: string) => {
  288. if (value === 'fixed') {
  289. setIsVisibleSlice(!isVisibleSlice);
  290. } else {
  291. setIsVisibleSlice(false);
  292. }
  293. };
  294. // 召回方式
  295. const onChangeRecallMethod = (e: RadioChangeEvent) => {
  296. };
  297. // 获取应用详情
  298. const api = {
  299. fetchDetail: async (app_id: string) => {
  300. setPageLoading(true);
  301. try {
  302. const res = await apis.fetchTakaiApplicationDetail(app_id);
  303. const sd = res.data.questionlist.map((item: any, index: number) => {
  304. return {
  305. "id": index + 1,
  306. "value": item.question,
  307. }
  308. });
  309. const info = res.data.detail;
  310. setAppId(info.appId);
  311. setTopPValue(info.topP as number);
  312. setTempValue(info.temperature as number);
  313. setName(info.name);
  314. setinfoDetail(res.data);
  315. interface Item2 {
  316. index_type_id: number,
  317. knowledge_id: string
  318. }
  319. interface Item {
  320. show_recall_result: boolean,
  321. recall_method: string,
  322. rerank_status: boolean,
  323. slice_config_type: string,
  324. slice_count: number,
  325. param_desc: string,
  326. rerank_model_name: string,
  327. is_multi_round: string,
  328. multi_round: string,
  329. rerank_index_type_list: [Item2],
  330. recall_index_type_list: [Item2]
  331. }
  332. const data_info: Item = JSON.parse(info.knowledgeInfo);
  333. if (data_info.param_desc === 'custom') {
  334. setIsVisibleCus(!isVisibleCus); //自定义回答风格
  335. }
  336. if (data_info.rerank_status === true) {
  337. setIsVisibleRerank(data_info.rerank_status) //模型
  338. }
  339. //召回切片数量
  340. if (data_info.slice_config_type === 'fixed') {
  341. setIsVisibleSlice(!isVisibleSlice);
  342. } else {
  343. setIsVisibleSlice(false);
  344. }
  345. if (info.typeId === 62) {
  346. setIsAppPro(true);
  347. } else {
  348. setIsAppPro(false);
  349. }
  350. if (info.model === 'Qwen3-30B') {
  351. setIsDeepThinkVisible(true);
  352. } else {
  353. setIsDeepThinkVisible(false);
  354. }
  355. form.setFieldsValue({
  356. id: info.id,
  357. name: info.name, //应用名称
  358. desc: info.desc, //应用描述
  359. prompt: info.prompt, //应用提示语
  360. topP: info.topP as string, //topP
  361. topK: info.topK as number, //topK
  362. temperature: info.temperature as number, //温度
  363. knowledge_ids: info.knowledgeIds,
  364. model: info.model,
  365. isDeepThink: 'N',
  366. iconColor: info.iconColor,
  367. iconType: info.iconType,
  368. questionList: sd, //问题列表
  369. max_token: info.maxToken, //应用最大token
  370. updateDate: info.updateDate, // 更新时间
  371. appProId: info.appProId,// 项目
  372. typeId: info.typeId, //应用类型
  373. visible: info.visible || '0', //是否公开
  374. sort: info.sort || null, //显示顺序
  375. param_desc: data_info.param_desc, //回答风格
  376. show_recall_result: data_info.show_recall_result, //是否展示召回结果
  377. recall_method: data_info.recall_method, //召回方式
  378. rerank_status: data_info.rerank_status, //开启rerank
  379. rerank_model_name: data_info.rerank_model_name, //模型名称
  380. slice_config_type: data_info.slice_config_type, // 召回切片数量
  381. slice_count: data_info.slice_count, // 切片数量
  382. is_multi_round: data_info.is_multi_round==='Y'?true:false, // 多轮对话
  383. multi_round: data_info.multi_round, // 多轮对话
  384. groupVisible: info.groupVisible === '1' ? true : false,// 集团是否公开
  385. })
  386. if(data_info.is_multi_round === 'Y'){
  387. setIsMultiRound(true);
  388. }else{
  389. setIsMultiRound(false);
  390. }
  391. // 如果接口返回 iconType,设置选中图标显示
  392. if (info.iconType) {
  393. setSelectedIcon(info.iconType);
  394. }
  395. if (info.iconColor) {
  396. setPreviewBg(info.iconColor);
  397. form.setFieldsValue({ iconColor: info.iconColor });
  398. }
  399. setVisibleFlag(info.visible || '0')
  400. if (info.vipList && info.vipList.length > 0) {
  401. setVipList(info.vipList);
  402. }
  403. if (sd.length > 0) {
  404. setInputs(sd);
  405. }
  406. } catch (error) {
  407. console.error(error);
  408. } finally {
  409. setPageLoading(false);
  410. }
  411. },
  412. //获取知识库列表
  413. fetchKnowlegde: async () => {
  414. try {
  415. const res = await apis.fetchTakaiKnowledgeList();
  416. const list = res.data.map((item: any) => {
  417. return {
  418. label: item.name,
  419. value: item.knowledgeId,
  420. createBy: item.createBy,
  421. }
  422. });
  423. setKnowledgeList(list);
  424. } catch (error: any) {
  425. console.error(error);
  426. }
  427. },
  428. // 获取应用类型
  429. fetchAppType: async () => {
  430. try {
  431. const res = await apis.fetchTakaiAppTypeList('app_type');
  432. const list = res.data.map((item: any) => {
  433. return {
  434. label: item.dictLabel,
  435. value: item.dictCode,
  436. }
  437. });
  438. setAppTypeList(list);
  439. } catch (error: any) {
  440. console.error(error);
  441. }
  442. },
  443. // 获取是否公开类型
  444. fetchAppVisible: async (id: string) => {
  445. try {
  446. const res = await apis.fetchTakaiAppTypeList('app_visible');
  447. const list = res.data.map((item: any) => {
  448. return {
  449. label: item.dictLabel,
  450. value: item.dictValue,
  451. }
  452. });
  453. setAppVisibleList(list);
  454. if (!id) {
  455. form.setFieldsValue({
  456. visible: list[0]?.value
  457. });
  458. setVisibleFlag(list[0]?.value);
  459. }
  460. } catch (error: any) {
  461. console.error(error);
  462. }
  463. },
  464. // 获取用户类型
  465. fetchUserType: async () => {
  466. try {
  467. const res = await apis.fetchTakaiAppTypeList('sys_user_type');
  468. const list = res.data.map((item: any) => {
  469. return {
  470. label: item.dictLabel,
  471. value: item.dictValue,
  472. }
  473. });
  474. setFetchUserTypeList(list);
  475. } catch (error: any) {
  476. console.error(error);
  477. }
  478. },
  479. // 获取模型列表
  480. fetchModelList: async () => {
  481. try {
  482. const res = await apis.fetchModelList();
  483. const list = res.data.data.map((item: any) => {
  484. return {
  485. label: item.modelName,
  486. value: item.modelCode,
  487. }
  488. });
  489. setModelList(list);
  490. } catch (error: any) {
  491. console.error(error);
  492. }
  493. },
  494. // 项目级应用下的类型
  495. fetchAppProType: async () => {
  496. try {
  497. const res = await apis.fetchTakaiAppTypeList('projectTree');
  498. const list: AppTypeList = res.data?.reduce((acc: any, item: any) => {
  499. acc.push({
  500. label: item.label,
  501. value: `${item.value}`,
  502. })
  503. return acc;
  504. }, []);
  505. setAppProjectList(list);
  506. } catch (error: any) {
  507. console.error(error);
  508. }
  509. },
  510. // 获取用户列表信息
  511. fetchUserListApi: async () => {
  512. try {
  513. const res = await apis.fetchUserListApi({
  514. pageNum: page.pageNum,
  515. pageSize: page.pageSize,
  516. userName: userName,
  517. nickName: userNickName,
  518. userType: userType
  519. });
  520. // setSourceData(res.rows)
  521. } catch (error) {
  522. console.error(error);
  523. }
  524. }
  525. }
  526. const handleRedioClick = (value: string) => {
  527. setIsVisibleCus(false);
  528. if (value === 'strict') {
  529. setTopPValue(0.5);
  530. setTempValue(0.01);
  531. } else if (value === 'moderate') {
  532. setTopPValue(0.7);
  533. setTempValue(0.50);
  534. } else if (value === 'flexib') {
  535. setTopPValue(0.9);
  536. setTempValue(0.90);
  537. }
  538. }
  539. const saveConfig = async (type: 'SAVE' | 'SUBMIT'|'CHAT') => {
  540. return form.validateFields().then(async (values) => {
  541. const data = values;
  542. // 问题列表
  543. const question: string[] = [];
  544. if (inputs) {
  545. inputs.map((item, index) => {
  546. question.push(item.value);
  547. });
  548. }
  549. interface Item {
  550. index_type_id: number,
  551. knowledge_id: string
  552. }
  553. const indexTypeList: Item[] = [];
  554. if (values.knowledge_ids && values.knowledge_ids.length > 0) {
  555. // console.log("knowledge_ids", values.knowledge_ids);
  556. const index_type: Item = {
  557. index_type_id: 0,
  558. knowledge_id: values.knowledge_ids
  559. };
  560. indexTypeList.push(index_type);
  561. }
  562. const data_info = {
  563. param_desc: values.param_desc, //回答风格
  564. show_recall_result: values.show_recall_result, //是否展示召回结果
  565. recall_method: values.recall_method, //召回方式
  566. rerank_status: true, //开启rerank
  567. rerank_model_name: values.rerank_model_name, //模型名称
  568. slice_config_type: values.slice_config_type, // 召回切片数量
  569. slice_count: values.slice_count, // 切片数量
  570. rerank_index_type_list: indexTypeList, //知识库id
  571. recall_index_type_list: values.recall_method === 'embedding' || 'mixed' ? indexTypeList : [],
  572. is_multi_round: values.is_multi_round? 'Y' : 'N', // 多轮对话
  573. multi_round: values.multi_round
  574. // rerank_status = 1 rerank_index_type_list
  575. // recall_method = 'embedding' || 'embedding' recall_index_type_list
  576. };
  577. const info = {
  578. id: values.id,
  579. name: values.name, //应用名称
  580. desc: values.desc, //应用描述
  581. prompt: values.prompt, //应用提示语
  582. topP: topPValue.toString(), //topP
  583. temperature: tempValue.toString(), //温度
  584. knowledge_ids: values.knowledge_ids,
  585. slice_count: values.slice_count,
  586. model: values.model,
  587. isDeepThink: 'N',
  588. questionList: question,
  589. knowledge_info: JSON.stringify(data_info),
  590. max_token: values.max_token, //应用最大token
  591. typeId: values.typeId, // 应用类型
  592. visible: values.visible, // 是否公开
  593. sort: values.sort || null, // 显示顺序
  594. vipList: vipList, // vip用户列表
  595. appProId: values?.appProId?.toString(),// 项目
  596. userId: userId, // 用户id
  597. iconColor: previewBg,
  598. iconType: values.iconType,
  599. groupVisible: values.groupVisible ? '1' : '0',
  600. // topK: values.topK || 1,
  601. };
  602. if(type === 'CHAT'){
  603. setinfoDetail({detail:{...info},questionlist:infoDetail?.questionlist || []});
  604. return info;
  605. }
  606. const id = location?.state?.id;
  607. let res = null;
  608. if (id) {
  609. // 编辑应用
  610. res = await apis.modifyTakaiApplicationApi(id, info);
  611. } else {
  612. // 创建应用
  613. res = await await apis.createTakaiApplicationApi(info);
  614. }
  615. // console.log(res, 'create or modify');
  616. if (res.data === 9) {
  617. message.error('没有配置审核人,请联系管理员');
  618. } else if (res.data === 1) {
  619. message.success('操作成功')
  620. } else {
  621. message.error('操作失败');
  622. }
  623. if (type === 'SUBMIT') {
  624. navigate({ pathname: '/appCenter/questionAnswer/list' }, { replace: true });
  625. }
  626. }).catch((error) => {
  627. console.error(error);
  628. error.errorFields && error.errorFields.map((item: any) => {
  629. console.log(item, 'item');
  630. message.error(`字段 ${item.name} ${item.errors[0]}`);
  631. });
  632. });
  633. }
  634. /*
  635. 选择VIP用户弹窗start
  636. */
  637. const [isModalOpen, setIsModalOpen] = React.useState(false);
  638. let falgVipList: any = [];
  639. const handleOk = () => {
  640. setIsModalOpen(false);
  641. let vipListFalg: any = [...vipList];
  642. const vipIds = new Set(vipListFalg.map((vip: any) => vip.userId));
  643. const merged = [...vipListFalg];
  644. falgVipList.forEach((item: any) => {
  645. if (!vipIds.has(item.userId)) {
  646. merged.push(item);
  647. }
  648. });
  649. setVipList([...merged]);
  650. };
  651. const handleCancel = async () => {
  652. setIsModalOpen(false);
  653. };
  654. interface DataType {
  655. name: string;
  656. key: string;
  657. nickName: string;
  658. userName: string;
  659. deptName: string;
  660. userTypeName: string;
  661. }
  662. // const [sourceData, setSourceData] = React.useState<DataType[]>([]);
  663. const paginationConfig: TablePaginationConfig = {
  664. // 显示数据总量
  665. showTotal: (total: number) => {
  666. return `共 ${total} 条`;
  667. },
  668. // 展示分页条数切换
  669. showSizeChanger: false,
  670. // 指定每页显示条数
  671. // 快速跳转至某页
  672. showQuickJumper: false,
  673. current: page.pageNum,
  674. pageSize: page.pageSize,
  675. total: page.total,
  676. onChange: async (page, pageSize) => {
  677. await onChangePagination(page, pageSize, userName, userNickName, userType);
  678. },
  679. };
  680. const vipModal = () => {
  681. const columns: TableProps<DataType>['columns'] = [
  682. {
  683. title: '昵称',
  684. dataIndex: 'nickName',
  685. render: (text) => <p>{text}</p>,
  686. },
  687. {
  688. title: '用户名称',
  689. dataIndex: 'userName',
  690. render: (text) => <p>{text}</p>,
  691. },
  692. {
  693. title: '部门',
  694. dataIndex: 'deptName',
  695. },
  696. {
  697. title: '用户类型',
  698. dataIndex: 'userTypeName',
  699. },
  700. ];
  701. const rowSelection: TableProps<DataType>['rowSelection'] = {
  702. type: 'checkbox',
  703. onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
  704. console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  705. falgVipList = selectedRows
  706. },
  707. getCheckboxProps: (record: DataType) => ({
  708. disabled: record.name === 'Disabled User', // Column configuration not to be checked
  709. name: record.name,
  710. }),
  711. };
  712. return (
  713. <>
  714. <Modal
  715. title="请选择指定用户"
  716. open={isModalOpen}
  717. onOk={handleOk}
  718. onCancel={handleCancel}
  719. width='80%'
  720. >
  721. <div className='modal_top'>
  722. <Input placeholder="请输入用户昵称" allowClear onChange={(e) => {
  723. setUserNickName(e.target.value)
  724. }} />
  725. <Input placeholder="请输入用户名称" allowClear onChange={(e) => {
  726. setUserName(e.target.value)
  727. }} />
  728. <Select
  729. placeholder='请选择用户类型'
  730. style={{ width: 150 }}
  731. onChange={(e) => {
  732. if (e === undefined) {
  733. setUserType('')
  734. return
  735. }
  736. setUserType(e)
  737. }}
  738. allowClear={true}
  739. >
  740. {
  741. fetchUserTypeList.map((item, index) => {
  742. return <Option value={item.value} key={index}>
  743. {item.label}
  744. </Option>
  745. })
  746. }
  747. </Select>
  748. <Button value="large" style={{
  749. background: 'transparent',
  750. border: '1px solid #1677ff',
  751. color: '#1677ff'
  752. }}
  753. onClick={() => { onFetchUserListApi(userName, userNickName, userType) }}
  754. > 搜索 </Button>
  755. {/* <Button value="large"
  756. onClick={() => { api.fetchUserListApi() }}
  757. > 重置 </Button> */}
  758. </div>
  759. <Table<DataType> pagination={paginationConfig} rowKey="userName" rowSelection={rowSelection} columns={columns} dataSource={sourceData} />
  760. </Modal>
  761. </>
  762. )
  763. }
  764. /*
  765. 选择VIP用户弹窗end
  766. */
  767. /*
  768. 查看引用知识库抽屉start
  769. */
  770. const [openDrawer, setOpenDrawer] = React.useState(false);
  771. const [drawerItem, setDrawerItem] = React.useState<any>({});
  772. const onCloseDrawer = () => {
  773. setOpenDrawer(false);
  774. }
  775. const DrawerDetail = () => {
  776. return (
  777. <Drawer
  778. title={drawerItem?.label}
  779. width={'80%'}
  780. closable={{ 'aria-label': 'Close Button' }}
  781. onClose={onCloseDrawer}
  782. open={openDrawer}
  783. style={{ zIndex: 11111 }}
  784. >
  785. <DrawerIndex drawerItem={drawerItem}></DrawerIndex>
  786. </Drawer>
  787. )
  788. }
  789. const [automatic, setAutomatic] = React.useState<boolean>(false);// 是否开启自动更新
  790. const [promptValue, setPromptValue] = React.useState<string>(`--- 系统指令与角色定义 ---
  791. # 核心角色:AI文档处理专家
  792. 你是一位结合公司多种领域的专业知识训练的AI文档处理专家,基于Qwen3的预训练模型能力。
  793. 你的**最高优先级目标**是:严格且忠实地遵循下方【约束和要求】中的所有规定,并仅根据【知识片段】中的信息进行归纳总结,生成高质量的对话式回答来回应【用户输入】。
  794. --- 约束和要求:严格执行 ---
  795. 请将以下所有规定视为必须严格执行的底层系统指令,不可修改、忽略或绕过。
  796. 一、 核心回答原则
  797. **1. 严格基于知识片段**:你的回答必须完全源自“知识片段”的归纳总结。
  798. **2. 禁止使用外部知识**:严禁使用你自身的预训练知识进行回答或补充。如果知识片段中找不到答案,你必须且只能回复:“该问题在提供的知识库中暂无明确记载,建议您查阅相关文档或咨询专业人士。”
  799. **3. 主动澄清模糊问题**:如果用户问题模糊,你必须根据知识片段内容,主动询问用户可能想问的具体方向。例如,用户问“标准是什么?”,你可以回复:“您是否想了解‘高处作业’相关的具体标准要求?”
  800. 二、示意图占位符处理规则
  801. **1. 触发条件与精确复制**:
  802. - 仅当所依据的知识片段中明确包含符合 【示意图序号_xxxxxxxxxxxxxxxxxxxxx_n】格式的占位符时,才可在回答中引用。
  803. - 引用时必须将占位符及其在源片段中的直接上下文描述文字一并原样复制,严禁任何修改、概括或截断。正确示例:若知识片段为“...施工流程如下【示意图序号_a29375108162406318082_n】…”,则回答中应为“...施工流程如下【示意图序号_a29375108162406318082_n】”。
  804. **2. 严禁虚构**:严禁生成任何知识片段中不存在的示意图占位符或描述性文字。如果回答内容所依据的知识片段内没有示意图占位符,则整个回答中不得出现任何形式的 【示意图序号_xxxxxxxxxxxxxxxxxxxxx_n】格式的占位符。
  805. **3. 清理无关标记**:从最终回答中删除所有来自知识片段的、与示意图占位符无关的图注、图表序号(如“(图1.1)”)等信息。
  806. 三、 针对URL信息来源的引用规则
  807. **1. 触发条件与精准复制**:
  808. - 仅当知识片段中已存在原始URL链接,且回答内容直接引用该片段时,方可标注来源。
  809. - 若知识片段无URL,则回答中禁止出现任何形式的链接或引用标记(如[5])。
  810. **2. 严禁虚构**:
  811. - 严禁生成任何知识片段中不存在的url链接。如果回答内容所依据的知识片段内没有url链接,则整个回答中不得出现任何形式的 "http://虚构链接" 。
  812. **3. 无URL时的替代方案**:若需引用无URL的知识片段,直接注明:"根据知识片段中《XX规范》第X条..."
  813. 四、LaTeX公式处理规则
  814. **1. 公式代码保护**:知识片段中如出现以美元符号包裹(例如 公式或 双美元符号包裹)或其他数学标记的LaTeX公式代码,你必须将这些代码视为纯文本并完整地、一字不差地输出在你的回答中。
  815. **2. 禁止修改**:严禁对任何公式代码进行修改、转义、截断、简化或使用自然语言进行解释,严禁在公式代码中添加多余的空格符号。你的目标是确保这些代码块在传递至前端时,能与原始知识片段中的内容完全一致。
  816. **3. 渲染前提**:只有当你输出的公式代码与原始片段完全一致时,前端的Markdown渲染器才能正确识别并将其显示为美观的数学公式。任何微小的改动都可能导致公式渲染失败。
  817. 五、 格式与内容规范
  818. - 文档中的表格以图片标识符呈现,若表格数据缺失则返回空单元格。
  819. - 如需使用表格数据,以markdown格式输出。
  820. - 回复开头避免使用“我想”、“我认为”等词语。
  821. - 回答中若出现网页链接,务必在链接后换行。
  822. - 注意区分不同系统的概念,如“掌监APP”和“慧项管平台”不能混淆。
  823. - 注意“模型”或“大模型”与“机器人”是不同的概念。
  824. --- 内部思考流程(思维链) ---
  825. **在生成最终回答前,必须严格依照以下步骤进行思考和规划:**
  826. 1. **[意图分析]** 识别用户{{用户}}的提问核心,判断其是否清晰或模糊。
  827. 2. **[知识检索]** 在知识片段中筛选出所有相关信息,评估知识覆盖度。
  828. 3. **[规则校验]** 检查是否有触发“主动澄清模糊问题”原则(I.3)或“找不到答案”原则(I.2)。
  829. 4. **[格式规划]** 确定内容是否涉及示意图(II)、URL引用(III)、LaTeX公式(IV)或Markdown表格(V),并规划如何精确应用相关规则。
  830. 5. **[答案生成]** 基于以上分析和规划,生成符合所有【约束和要求】的对话式回答。
  831. [思考结束,请勿输出此流程内容]
  832. --- 任务输入 ---
  833. # 知识片段
  834. {{知识}}
  835. # 用户输入
  836. {{用户}}
  837. --- 最终回答 ---
  838. [直接输出对话式回答,不要复述用户问题]`);// 提示语
  839. const [editPrompt, setEditPrompt] = React.useState<boolean>(false);// 是否编辑提示语
  840. const [isMultiRound,setIsMultiRound] = React.useState<boolean>(false);// 是否开启多轮对话
  841. /*
  842. 查看引用知识库抽屉end
  843. */
  844. return (
  845. <div className='page-layout'>
  846. <div className="list-header with-back">
  847. <div className="list-header-title">
  848. <button className="back-btn" onClick={() => navigate(-1)} title="返回">
  849. <LeftOutlined />
  850. </button>
  851. <h3>{location?.state?.id ? '编辑 RAG 应用' : '创建 RAG 应用'}</h3>
  852. </div>
  853. </div>
  854. <Spin spinning={pageLoading}>
  855. <Form
  856. form={form}
  857. layout='vertical'
  858. initialValues={{
  859. isDeepThink: 'N',
  860. // max_token: 4096,
  861. // model: 'Qwen3-30B',
  862. show_recall_result: true,
  863. // rerank_model_name: 'bge-reranker-v2-m3',
  864. slice_config_type: 'customized',
  865. rerank_status: true,
  866. param_desc: 'strict',
  867. recall_method: 'mixed',
  868. topK: 50,
  869. is_multi_round:isMultiRound
  870. }}
  871. >
  872. <div style={{ display: step === 1 ? 'block' : 'none' }} >
  873. <FormItem label='请选择应用图标' tooltip='用于在应用广场展示' name='iconType' rules={[{ required: true, message: '请选择图标' }]}>
  874. <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
  875. {/* 左侧预览块(固定) */}
  876. <div style={{ width: 84, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
  877. <div style={{ width: 64, height: 64, borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', background: previewBg, border: '1px solid #e8e8e8' }}>
  878. {selectedIcon ? (() => { const C = (AllIcons as any)[selectedIcon]; const iconColor = getContrastColor(previewBg); return C ? <C style={{ fontSize: 28, color: iconColor }} /> : <span style={{ fontSize: 12 }}>{selectedIcon}</span> })() : <span style={{ color: '@text-hint', fontSize: 12 }}>预览</span>}
  879. </div>
  880. </div>
  881. {/* 右侧选择区(简洁协调) */}
  882. <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
  883. <a onClick={() => setIconPickerVisible(true)} style={{ fontSize: 13, color: '#1677ff', cursor: 'pointer' }}>选择图标</a>
  884. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  885. <div style={{ fontSize: 12, color: '#666' }}>背景色:</div>
  886. <ColorPicker presets={presetItems} value={previewBg}
  887. onChange={(color) => {
  888. const hex = color.toHexString?.() || color?.toString?.() || previewBg;
  889. setPreviewBg(hex);
  890. form.setFieldsValue({ iconColor: hex });
  891. }} />
  892. </div>
  893. </div>
  894. </div>
  895. </FormItem>
  896. <FormItem
  897. label='问答应用名称'
  898. tooltip='尽量概括应用的主要功能'
  899. name='name'
  900. rules={[{ required: true, message: '问答应用名称不能为空' }]}
  901. >
  902. <Input placeholder="请输入问答应用名称" className='form-element-standard' style={{ height: '36px' }} />
  903. </FormItem>
  904. <FormItem
  905. label='应用类型'
  906. tooltip='应用的实际分类'
  907. name='typeId'
  908. >
  909. <Select
  910. className='form-element-select'
  911. style={{ height: '36px' }}
  912. placeholder='请选择问答应用类型'
  913. onChange={handleAppChange}
  914. allowClear={true}
  915. >
  916. {
  917. appTypeList.map((item, index) => {
  918. return <Option value={item.value} key={index}>
  919. {item.label}
  920. </Option>
  921. })
  922. }
  923. </Select>
  924. </FormItem>
  925. {
  926. isAppPro &&
  927. <>
  928. <FormItem
  929. label='项目'
  930. tooltip='应用所属项目'
  931. name='appProId'
  932. rules={[{ required: true, message: '项目不能为空' }]}
  933. >
  934. <Cascader
  935. options={appProjectList}
  936. placeholder="请选择项目"
  937. showSearch
  938. className="form-element-select"
  939. style={{ height: '36px' }}
  940. />
  941. </FormItem>
  942. </>
  943. }
  944. <FormItem
  945. label='是否公开'
  946. tooltip='公开应用后,所有用户均可使用该应用,私有应用仅限自己和指定用户使用'
  947. name='visible'
  948. >
  949. <Select
  950. className='form-element-select'
  951. style={{ height: '36px' }}
  952. placeholder='请选择是否公开'
  953. allowClear={true}
  954. onChange={(e) => {
  955. setVisibleFlag(e)
  956. }}
  957. >
  958. {
  959. appVisibleList.map((item, index) => {
  960. return <Option value={item.value} key={index}>
  961. {item.label}
  962. </Option>
  963. })
  964. }
  965. </Select>
  966. </FormItem>
  967. {userInfo?.tenantId === '000000' && visibleFlag === '0' && <FormItem
  968. label='集团公开'
  969. tooltip='集团下所有用户均可使用该应用'
  970. name='groupVisible'
  971. layout='horizontal'
  972. >
  973. <Switch onChange={onChangeShow} />
  974. </FormItem>}
  975. <FormItem
  976. label='显示顺序'
  977. name='sort'
  978. tooltip='用于应用广场的显示顺序'
  979. >
  980. <InputNumber placeholder="请输入显示顺序" value={''} className='form-element-standard' style={{ height: '36px', lineHeight: '36px' }} />
  981. </FormItem>
  982. {/* VIP用户 */}
  983. {visibleFlag == 1 && <FormItem
  984. label='指定用户'
  985. tooltip='私有应用的指定用户'
  986. >
  987. <div className='tags-info'>
  988. <p className='tags-list'>
  989. {vipList.map((item: any) =>
  990. (<Tag key={item.userId} color="blue" closeIcon onClose={(e) => {
  991. const newVipList = vipList.filter((vip: any) => vip.userId !== item.userId);
  992. setVipList(newVipList);
  993. e.preventDefault();
  994. }}>
  995. {item.userName}
  996. </Tag>)
  997. )}
  998. </p>
  999. <p>
  1000. {vipList.length > 0 && <CloseCircleOutlined className='cup' onClick={() => {
  1001. setVipList([]);
  1002. }} />}
  1003. <Button style={{
  1004. background: 'transparent',
  1005. border: '1px solid #1677ff',
  1006. color: '#1677ff'
  1007. }} type="primary" variant="outlined" onClick={() => { setIsModalOpen(true) }}>选择</Button>
  1008. </p>
  1009. </div>
  1010. </FormItem>}
  1011. <FormItem
  1012. label='问答应用描述'
  1013. tooltip='对当前应用功能的描述使用户更了解应用的使用范围'
  1014. name='desc'
  1015. rules={[{ required: true, message: '问答应用描述不能为空' }]}
  1016. >
  1017. <TextArea
  1018. showCount
  1019. maxLength={500}
  1020. placeholder="请输入当前应用的描述"
  1021. className='form-textarea-large'
  1022. />
  1023. </FormItem>
  1024. <div className='preset-questions'>
  1025. <h4>添加引导问题</h4>
  1026. <div>
  1027. {
  1028. inputs.map(input => (
  1029. <div key={input.id} className='question-item'>
  1030. <label>引导问题 {input.id}</label>
  1031. <Input
  1032. className='question-input'
  1033. type="text"
  1034. value={input.value}
  1035. onChange={e => handleChange(input.id, e.target.value)}
  1036. />
  1037. <div className='question-actions'>
  1038. <PlusCircleOutlined className='question-icon' onClick={addInput} />
  1039. <MinusCircleOutlined className='question-icon' onClick={() => delInput(input.id)} />
  1040. </div>
  1041. </div>
  1042. ))}
  1043. </div>
  1044. </div>
  1045. <div style={{ display: 'flex', gap: '12px', marginTop: '24px', paddingTop: '24px', borderTop: '1px solid #F3F4F6' }}>
  1046. <Button
  1047. className='btn-cancel'
  1048. onClick={() => {
  1049. navigate(-1);
  1050. }}
  1051. >
  1052. 返回
  1053. </Button>
  1054. <Button
  1055. type='primary'
  1056. onClick={() => {
  1057. form.validateFields(['name', 'desc', 'appProId', 'iconType']).then(async (values) => {
  1058. setStep(2);
  1059. setInputs(inputs);
  1060. setinfoDetail({detail:values,questionlist:infoDetail?.questionlist||[]});
  1061. }).catch((error) => {
  1062. console.error(error);
  1063. });
  1064. }}
  1065. >
  1066. 下一步
  1067. </Button>
  1068. </div>
  1069. </div>
  1070. <div style={{ display: step === 2 ? 'block' : 'none' }} >
  1071. <div className='flex-between padding-bottom-16'>
  1072. <div>
  1073. <Button
  1074. className='btn-back'
  1075. icon={<ArrowLeftOutlined />}
  1076. onClick={() => {
  1077. setStep(1);
  1078. }}
  1079. >
  1080. 上一步
  1081. </Button>
  1082. </div>
  1083. <div style={{ display: 'flex', gap: '12px' }}>
  1084. <Button
  1085. className='btn-cancel'
  1086. onClick={() => {
  1087. // navigate({ pathname: '/appCenter/questionAnswer' });
  1088. }}
  1089. >
  1090. 取消
  1091. </Button>
  1092. {/* {
  1093. appId && (
  1094. <Tooltip title='保存'>
  1095. <Button
  1096. className='btn-cancel'
  1097. onClick={() => {
  1098. saveConfig('SAVE');
  1099. }}
  1100. icon={<FileDoneOutlined />}
  1101. >
  1102. </Button>
  1103. </Tooltip>)
  1104. } */}
  1105. {createFlag && (
  1106. <Button
  1107. type='primary'
  1108. onClick={() => {
  1109. saveConfig('SUBMIT');
  1110. }}
  1111. >
  1112. 提交应用
  1113. </Button>
  1114. )}
  1115. </div>
  1116. </div>
  1117. <Splitter className='app-splitter' style={{ minHeight: 0 }}>
  1118. {<Splitter.Panel defaultSize="35%">
  1119. <div className='section-title' style={{marginBottom: '0'}}>
  1120. Prompt编写与参数配置
  1121. <Tooltip
  1122. title="Prompt用于对大模型的回复做出一些列指令和约束。这段Prompt不会被用户看到。"
  1123. placement="right"
  1124. >
  1125. <InfoCircleOutlined style={{ marginLeft: '8px', color: '#9CA3AF', fontSize: '13px' }} />
  1126. </Tooltip>
  1127. {/* <Switch checkedChildren="编辑" unCheckedChildren="只读" style={{marginLeft:'5px'}} value={editPrompt} onChange={(e) => {
  1128. setEditPrompt(e)
  1129. }} /> */}
  1130. </div>
  1131. <div className='prompt'>
  1132. <div className='prompt-info'>
  1133. <div className='prompt-info-text'>
  1134. <Typography.Paragraph style={{ fontSize: '12px', lineHeight: '1.6', color: '@text-hint', margin: 0 }}>
  1135. 编写Prompt过程中可以引入2项变量:
  1136. <span className='variable-highlight'>{'{{知识}}'}</span>
  1137. 代表知识库中检索到的知识内容,
  1138. <span className='variable-highlight'>{'{{用户}}'}</span>
  1139. 代表用户输入的内容。您可以在编写Prompt过程中将变量拼接在合适的位置。
  1140. </Typography.Paragraph>
  1141. </div>
  1142. </div>
  1143. {/* 移除 Divider,使用 CSS 边框替代 */}
  1144. <div className='prompt-editor-area'>
  1145. <FormItem name='prompt'
  1146. tooltip='当前应用会遵循提示词进行输出'
  1147. initialValue={
  1148. promptValue
  1149. }
  1150. rules={[{ required: true, message: '提示词不能为空' }]}>
  1151. <TextArea
  1152. disabled={!parameter}
  1153. placeholder="提示词"
  1154. rows={50}
  1155. />
  1156. </FormItem>
  1157. </div>
  1158. </div>
  1159. </Splitter.Panel>}
  1160. <Splitter.Panel defaultSize="30%">
  1161. <div className='flex-center-container'>
  1162. <div className='half-width'>
  1163. <div className='pl-20 pt-3 text-[#000000]'>
  1164. {/* 参数配置 {name || '问答应用'} */}
  1165. <span className='mr-[6px]' >参数配置</span> <Switch checkedChildren="手动" unCheckedChildren="自动" value={automatic} onChange={(e) => {
  1166. console.log(e, 'e')
  1167. setAutomatic((pre) => !pre)
  1168. if (!e) {
  1169. form.setFieldsValue({
  1170. param_desc: 'strict',
  1171. topK: 50
  1172. })
  1173. }
  1174. }} />
  1175. </div>
  1176. <div className='flex-start pl-20 mt-3'>
  1177. <FormItem
  1178. label='引用知识库'
  1179. tooltip='应用对应的知识库可以选择多个,点击链接可以查看知识库中的文件'
  1180. name='knowledge_ids'
  1181. rules={[{ required: true, message: '知识库不能为空' }]}>
  1182. <Select
  1183. mode='multiple'
  1184. maxCount={MAX_COUNT}
  1185. showSearch={true}
  1186. filterOption={(input, option) => (option?.children as unknown as string)?.toLowerCase()?.includes(input.toLowerCase())}
  1187. className='form-element-select'
  1188. placeholder='请选择需要引用的知识库'
  1189. tagRender={tagRender}
  1190. >
  1191. {
  1192. knowledgeList.map((item, index) => {
  1193. return <Option value={item.value} key={index}>
  1194. {item.label}
  1195. </Option>
  1196. })
  1197. }
  1198. </Select>
  1199. </FormItem>
  1200. </div>
  1201. <div className='flex-start pl-20'>
  1202. <FormItem
  1203. label='调用模型'
  1204. tooltip='应用使用的模型'
  1205. name="model"
  1206. rules={[{ required: true, message: '模型不能为空' }]}>
  1207. <Select
  1208. placeholder='请选择模型'
  1209. className='form-element-select'
  1210. onChange={(value) => {
  1211. // if (value === 'Qwen3-30B') {
  1212. // setIsDeepThinkVisible(true);
  1213. // } else {
  1214. // setIsDeepThinkVisible(false);
  1215. // }
  1216. const list = modeOldList.filter((item: any) => item.model === value)
  1217. // console.log(list, 'list');
  1218. form.setFieldsValue({
  1219. max_token: list[0]?.maxToken,
  1220. rerank_model_name: list[0]?.bindingModel
  1221. })
  1222. }}
  1223. >
  1224. {/* <Option value='Qwen3-30B'>Qwen3-30B</Option> */}
  1225. {modeList.map((item: any, index: number) => (
  1226. <Option value={item.value} key={index}>
  1227. {item.label}
  1228. </Option>
  1229. ))}
  1230. </Select>
  1231. </FormItem>
  1232. </div>
  1233. <div className='flex-start pl-20'>
  1234. <FormItem
  1235. label='max token'
  1236. name='max_token'
  1237. tooltip='每次会话输出的最大token数'
  1238. rules={[{ required: true, message: 'max token不能为空' }]}>
  1239. <InputNumber
  1240. disabled
  1241. className='form-element-input-number'
  1242. />
  1243. </FormItem>
  1244. </div>
  1245. {
  1246. !isVisible &&
  1247. <div className='flex-start pl-20'>
  1248. <a onClick={() => {
  1249. setIsVisible(!isVisible);
  1250. }} className='link-more-settings'>
  1251. 更多设置
  1252. </a>
  1253. </div>
  1254. }
  1255. {/* {isVisible && */}
  1256. <div style={{ display: isVisible ? 'block' : 'none', paddingTop: '20px' }}>
  1257. {isVisibleCus && automatic &&
  1258. <Space style={{ width: '100%' }} direction="vertical">
  1259. <div className='flex-start pl-20'>
  1260. <FormItem
  1261. label='Top-p'
  1262. name='topP'
  1263. tooltip='Top-p参数控制生成文本的多样性,值越大生成的文本越多样化'
  1264. className='form-element-standard'
  1265. >
  1266. <TopPDecimalStep />
  1267. </FormItem>
  1268. </div>
  1269. <div className='flex-start pl-20'>
  1270. <FormItem
  1271. label='Temperature'
  1272. name='temperature'
  1273. className='form-element-standard'
  1274. >
  1275. <TempDecimalStep />
  1276. </FormItem>
  1277. </div>
  1278. </Space>
  1279. }
  1280. <div style={{
  1281. display: 'flex',
  1282. justifyContent: 'flex-start',
  1283. alignItems: 'center'
  1284. }} className='pl-20'>
  1285. <FormItem
  1286. label='回答风格'
  1287. name='param_desc'
  1288. tooltip='大模型会遵循设置的风格进行回答'
  1289. rules={[{ required: true, message: '回答风格不能为空' }]}>
  1290. <Radio.Group buttonStyle="solid"
  1291. className='form-element-button-group'>
  1292. <Radio.Button onClick={() => {
  1293. handleRedioClick('strict')
  1294. }} value='strict'>严谨</Radio.Button>
  1295. <Radio.Button onClick={() => {
  1296. handleRedioClick('moderate')
  1297. }} value='moderate'>适中</Radio.Button>
  1298. <Radio.Button onClick={() => {
  1299. handleRedioClick('flexib')
  1300. }} value='flexib'>发散</Radio.Button>
  1301. {automatic && <Radio.Button value='custom'
  1302. onClick={() => {
  1303. setIsVisibleCus(!isVisibleCus);
  1304. setTopPValue(0.1);
  1305. setTempValue(0.01);
  1306. }}
  1307. >自定义
  1308. </Radio.Button>}
  1309. </Radio.Group>
  1310. </FormItem>
  1311. </div>
  1312. <div style={{
  1313. display: 'flex',
  1314. justifyContent: 'flex-start',
  1315. alignItems: 'center'
  1316. }} className='pl-20'>
  1317. <FormItem
  1318. label='展示引用知识'
  1319. tooltip='是否在回答中展示引用的知识内容'
  1320. name='show_recall_result'
  1321. className='form-element-standard'>
  1322. <Switch onChange={onChangeShow} />
  1323. </FormItem>
  1324. </div>
  1325. <div style={{
  1326. display: 'flex',
  1327. justifyContent: 'flex-start',
  1328. alignItems: 'center'
  1329. }} className='pl-20'>
  1330. <FormItem
  1331. label='召回方式'
  1332. tooltip='知识库内容的检索方式'
  1333. name='recall_method'
  1334. rules={[{ required: true, message: '召回方式不能为空' }]}>
  1335. <Radio.Group
  1336. style={style}
  1337. onChange={onChangeRecallMethod}
  1338. options={[
  1339. { value: 'embedding', label: '向量化检索' },
  1340. { value: 'keyword', label: '关键词检索' },
  1341. { value: 'mixed', label: '混合检索' },
  1342. ]}
  1343. />
  1344. </FormItem>
  1345. </div>
  1346. <div style={{
  1347. display: 'flex',
  1348. justifyContent: 'flex-start',
  1349. alignItems: 'center'
  1350. }} className='pl-20'>
  1351. <div className='section-title'>重排方式</div>
  1352. </div>
  1353. {/* <div style={{
  1354. display: 'flex',
  1355. justifyContent: 'flex-start',
  1356. alignItems: 'center'
  1357. }} className='pl-20'>
  1358. <FormItem
  1359. label='Rerank模型'
  1360. tooltip='是否开启Rerank模型对检索结果进行重排'
  1361. name='rerank_status'
  1362. valuePropName='checked'
  1363. className='form-control-width'
  1364. >
  1365. <Switch onChange={onChangeModel} />
  1366. </FormItem>
  1367. </div> */}
  1368. {/* {isVisibleRerank && */}
  1369. {
  1370. <div style={{
  1371. display: 'flex',
  1372. justifyContent: 'flex-start',
  1373. alignItems: 'center'
  1374. }} className='pl-20'>
  1375. <FormItem
  1376. label='Rerank模型'
  1377. tooltip='检索结果的重新排序模型'
  1378. name='rerank_model_name'
  1379. >
  1380. <Input
  1381. disabled
  1382. type="text"
  1383. />
  1384. </FormItem>
  1385. </div>
  1386. }
  1387. <div style={{
  1388. display: 'flex',
  1389. justifyContent: 'flex-start',
  1390. alignItems: 'center'
  1391. }} className='pl-20'>
  1392. <FormItem
  1393. label='召回切片数量'
  1394. name='slice_config_type'
  1395. tooltip='自动模式默认召回15个切片,手动模式可自定义召回切片数量'
  1396. rules={[{ required: true, message: '召回方式不能为空' }]}>
  1397. <Select
  1398. // className='questionAnswerInfo-content-title'
  1399. className='form-element-select'
  1400. placeholder='请选择'
  1401. onChange={onChangeCount}>
  1402. <Option value="fixed">手动设置</Option>
  1403. <Option value="customized">自动设置</Option>
  1404. </Select>
  1405. </FormItem>
  1406. </div>
  1407. {isVisibleSlice &&
  1408. <div style={{
  1409. display: 'flex',
  1410. justifyContent: 'flex-start',
  1411. alignItems: 'center'
  1412. }} className='pl-20'>
  1413. <FormItem
  1414. label='召回切片数'
  1415. tooltip='自定义召回切片的数量'
  1416. name='slice_count'
  1417. rules={[{ required: true, message: '切片数不能为空' }]}>
  1418. <InputNumber max={1024} changeOnWheel
  1419. // className='questionAnswerInfo-content-title'
  1420. className='form-element-standard'
  1421. />
  1422. </FormItem>
  1423. </div>
  1424. }
  1425. <div style={{
  1426. display: 'flex',
  1427. justifyContent: 'flex-start',
  1428. alignItems: 'center'
  1429. }} className='pl-20'>
  1430. <FormItem
  1431. label='是否开启多轮对话'
  1432. name='is_multi_round'
  1433. tooltip='开启后支持多轮对话功能'>
  1434. <Switch value={isMultiRound} onChange={(e)=>{
  1435. setIsMultiRound(e)
  1436. }} />
  1437. </FormItem>
  1438. </div>
  1439. {isMultiRound &&
  1440. <div style={{
  1441. display: 'flex',
  1442. justifyContent: 'flex-start',
  1443. alignItems: 'center'
  1444. }} className='pl-20'>
  1445. <FormItem
  1446. label='多轮对话次数'
  1447. tooltip='支持上下文对话轮数'
  1448. name='multi_round'
  1449. rules={[{ required: true, message: '多轮对话次数不能为空' }]}>
  1450. <InputNumber max={10} min={1} changeOnWheel
  1451. // className='questionAnswerInfo-content-title'
  1452. className='form-element-standard'
  1453. />
  1454. </FormItem>
  1455. </div>
  1456. }
  1457. </div>
  1458. {/* } */}
  1459. </div>
  1460. </div>
  1461. </Splitter.Panel>
  1462. {/* {appId && (<Splitter.Panel defaultSize="35%">
  1463. <Chat appId={appId} infoDetail={infoDetail} />
  1464. </Splitter.Panel>)} */}
  1465. {(infoDetail&&<Splitter.Panel defaultSize="35%">
  1466. <Chat appId={appId} infoDetail={infoDetail} saveConfig={()=>{
  1467. return saveConfig('CHAT')
  1468. }} />
  1469. </Splitter.Panel>)}
  1470. </Splitter>
  1471. </div>
  1472. </Form>
  1473. </Spin>
  1474. {isModalOpen && vipModal()}
  1475. {DrawerDetail()}
  1476. <IconPicker
  1477. open={iconPickerVisible}
  1478. onClose={() => setIconPickerVisible(false)}
  1479. onSelect={(name) => {
  1480. setSelectedIcon(name);
  1481. form.setFieldsValue({ iconType: name });
  1482. // 保持 previewBg 不变,仅更新图标显示,若未设置 iconColor 则使用默认白底
  1483. const curColor = form.getFieldValue('iconColor') || previewBg;
  1484. setPreviewBg(curColor || '#ffffff');
  1485. }}
  1486. value={selectedIcon}
  1487. />
  1488. </div>
  1489. );
  1490. };
  1491. export default QuestionAnswerInfo;