zentaobiz.ui.js 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  1. $(document).ready(function()
  2. {
  3. initData();
  4. fieldItemActions();
  5. watchFieldTypeChange();
  6. fieldOptionActions();
  7. fieldOptionsChange();
  8. fieldModalSave();
  9. formFieldChange();
  10. initPromptEditor();
  11. knowledgeLibItemActions();
  12. actionSave();
  13. });
  14. /* 是否已保存 */
  15. let isSaved = true;
  16. /* Markdown 组件 */
  17. let markdownComponent = null;
  18. /* Prompt 初始值 */
  19. const initPrompt = processPromptText(currentPrompt || '');
  20. /* Prompt Data */
  21. let promptData = {
  22. editor: null,
  23. text: initPrompt,
  24. html: '',
  25. };
  26. /* 表单数据管理 */
  27. const FieldManager = {
  28. state: {
  29. fields: [],
  30. fieldsMap: {},
  31. },
  32. setFields(fields)
  33. {
  34. this.state.fields = [];
  35. fields.forEach(field => {
  36. this.state.fields.push(field.id);
  37. this.state.fieldsMap[field.id] = field;
  38. });
  39. renderList();
  40. },
  41. setFieldIds(fieldIds)
  42. {
  43. this.state.fields = fieldIds;
  44. renderList();
  45. },
  46. addField(field, afterId = null)
  47. {
  48. /* 生成 ID */
  49. const id = Date.now();
  50. field.id = id;
  51. const index = afterId ? this.state.fields.indexOf(afterId) : -1;
  52. if(index === -1)
  53. {
  54. this.state.fields.push(id);
  55. }
  56. else
  57. {
  58. this.state.fields.splice(index + 1, 0, id);
  59. }
  60. this.state.fieldsMap[id] = field;
  61. renderList();
  62. },
  63. updateField(field)
  64. {
  65. const oldName = this.state.fieldsMap[field.id].name;
  66. const newName = field.name;
  67. this.state.fieldsMap[field.id] = field;
  68. renderList();
  69. if(oldName !== newName)
  70. {
  71. updatePromptData({
  72. action: 'update',
  73. id: field.id,
  74. oldName,
  75. newName,
  76. });
  77. }
  78. },
  79. removeField(fieldId)
  80. {
  81. const index = this.state.fields.indexOf(fieldId);
  82. if(index > -1)
  83. {
  84. const name = this.state.fieldsMap[fieldId].name;
  85. this.state.fields.splice(index, 1);
  86. delete this.state.fieldsMap[fieldId];
  87. renderList();
  88. updatePromptData({
  89. action: 'delete',
  90. id: fieldId,
  91. name,
  92. });
  93. }
  94. },
  95. getField(id)
  96. {
  97. return this.state.fieldsMap[id];
  98. },
  99. getFields()
  100. {
  101. return this.state.fields.map(id => this.state.fieldsMap[id]);
  102. },
  103. };
  104. /* 知识库数据管理 */
  105. const KnowledgeLibManager = {
  106. state: {
  107. libs: [],
  108. libsMap: {},
  109. action: '',
  110. targetId: '',
  111. },
  112. setLibs(libs)
  113. {
  114. this.state.libs = [];
  115. libs.forEach(lib => {
  116. this.state.libs.push(lib.id);
  117. this.state.libsMap[lib.id] = lib;
  118. });
  119. renderKnowledgeLibs();
  120. },
  121. setLibIds(libIds)
  122. {
  123. this.state.libs = libIds;
  124. renderKnowledgeLibs();
  125. },
  126. addLib(lib, afterId = null)
  127. {
  128. const index = afterId ? this.state.libs.indexOf(afterId) : -1;
  129. if(index === -1)
  130. {
  131. this.state.libs.push(lib.id);
  132. }
  133. else
  134. {
  135. this.state.libs.splice(index + 1, 0, lib.id);
  136. }
  137. this.state.libsMap[lib.id] = lib;
  138. renderKnowledgeLibs();
  139. },
  140. updateLib(lib, targetId)
  141. {
  142. const index = this.state.libs.indexOf(targetId);
  143. if(index === -1) return;
  144. this.state.libs[index] = lib.id;
  145. delete this.state.libsMap[targetId];
  146. this.state.libsMap[lib.id] = lib;
  147. renderKnowledgeLibs();
  148. },
  149. removeLib(libId)
  150. {
  151. const index = this.state.libs.indexOf(libId);
  152. if(index > -1)
  153. {
  154. this.state.libs.splice(index, 1);
  155. delete this.state.libsMap[libId];
  156. renderKnowledgeLibs();
  157. }
  158. },
  159. getLib(id)
  160. {
  161. return this.state.libsMap[id];
  162. },
  163. getLibs()
  164. {
  165. return this.state.libs.map(id => this.state.libsMap[id]);
  166. },
  167. setAction(action, targetId)
  168. {
  169. this.state.action = action;
  170. this.state.targetId = targetId;
  171. },
  172. getAction()
  173. {
  174. const { action, targetId } = this.state;
  175. return { action, targetId };
  176. },
  177. };
  178. /* Modal 数据管理 */
  179. const ModalManager = {
  180. state: {
  181. action: '',
  182. id: '',
  183. type: '',
  184. options: [],
  185. },
  186. setAction(action)
  187. {
  188. this.state.action = action;
  189. },
  190. getAction()
  191. {
  192. return this.state.action;
  193. },
  194. setId(id)
  195. {
  196. this.state.id = id;
  197. },
  198. getId()
  199. {
  200. return this.state.id;
  201. },
  202. setType(type)
  203. {
  204. this.state.type = type;
  205. renderOptions();
  206. },
  207. getType()
  208. {
  209. return this.state.type;
  210. },
  211. setOptions(options, render = true)
  212. {
  213. this.state.options = options;
  214. if(render) renderOptions();
  215. },
  216. getOptions()
  217. {
  218. return this.state.options;
  219. },
  220. setState(state)
  221. {
  222. this.state = state;
  223. renderOptions();
  224. },
  225. getState()
  226. {
  227. return this.state;
  228. },
  229. };
  230. /* 数据初始化 */
  231. function initData()
  232. {
  233. let fields = [];
  234. if(currentFields && currentFields.length > 0)
  235. {
  236. fields = currentFields.map(field => ({
  237. ...field,
  238. options: field.options ? field.options.split(',') : [],
  239. }));
  240. }
  241. FieldManager.setFields(fields);
  242. KnowledgeLibManager.setLibs(knowledgeLibs || []);
  243. }
  244. /* 检查内容,更新提交按钮状态 */
  245. function checkAndUpdateSubmit()
  246. {
  247. const prompt = promptData.text.trim();
  248. $('#btn-save').prop('disabled', prompt.length === 0);
  249. }
  250. /* 渲染列表 */
  251. function renderList()
  252. {
  253. const fields = FieldManager.getFields();
  254. renderFieldList(fields);
  255. }
  256. /* 渲染字段列表占位符 */
  257. function renderFieldListPlaceholder()
  258. {
  259. return `
  260. <div class="field-empty" id="add-field">
  261. <div class="action">
  262. <i class="icon icon-plus"></i>
  263. <span>${fieldAdd}</span>
  264. </div>
  265. <div class="tips">${fieldAddTip}</div>
  266. </div>
  267. `;
  268. }
  269. /* 渲染表单项 */
  270. function renderFormField(field)
  271. {
  272. let fieldHtml = '';
  273. switch(field.type)
  274. {
  275. case 'text':
  276. fieldHtml = `
  277. <input type="text"
  278. class="form-control"
  279. name="${field.name}"
  280. placeholder="${field.placeholder || fieldPlaceholder}"
  281. ${field.required === '1' ? 'required' : ''}>
  282. `;
  283. break;
  284. case 'textarea':
  285. fieldHtml = `
  286. <textarea
  287. class="form-control"
  288. name="${field.name}"
  289. placeholder="${field.placeholder || fieldPlaceholder}"
  290. rows="3"
  291. ${field.required === '1' ? 'required' : ''}></textarea>
  292. `;
  293. break;
  294. case 'radio':
  295. case 'checkbox':
  296. if(field.options && field.options.length > 0)
  297. {
  298. const options = {
  299. items: field.options.map(item => ({
  300. text: item,
  301. value: item,
  302. })),
  303. name: field.name,
  304. limitValueInList: false,
  305. multiple: field.type === 'checkbox',
  306. required: field.required === '1',
  307. };
  308. const pickerOptions = JSON.stringify(options).replace(/"/g, '&quot;');
  309. const dataAttributes = [
  310. `zui-create="picker"`,
  311. `zui-create-picker="${pickerOptions}"`,
  312. ].join(' ');
  313. fieldHtml = `
  314. <div
  315. class="input-group-control"
  316. ${dataAttributes}>
  317. </div>
  318. `;
  319. }
  320. break;
  321. default:
  322. return '';
  323. }
  324. return fieldHtml;
  325. }
  326. /* 渲染字段项 */
  327. function renderFieldItem(field)
  328. {
  329. return `
  330. <li class="field-item" data-id="${field.id}">
  331. <i class="icon icon-move"></i>
  332. <span class="name text-ellipsis">${field.name}</span>
  333. <span class="asterisk">${field.required === 1 ? '*' : ''}</span>
  334. ${renderFormField(field)}
  335. <button class="btn size-sm square" data-action="add">
  336. <i class="icon icon-plus"></i>
  337. </button>
  338. <button class="btn size-sm square" data-action="edit">
  339. <i class="icon icon-edit"></i>
  340. </button>
  341. <button class="btn size-sm square" data-action="delete">
  342. <i class="icon icon-trash"></i>
  343. </button>
  344. </li>
  345. `;
  346. }
  347. /* 渲染字段列表 */
  348. function renderFieldList(fields)
  349. {
  350. const fieldList = $('#field-list');
  351. fieldList.empty();
  352. let html = '';
  353. if(fields.length === 0)
  354. {
  355. html = renderFieldListPlaceholder();
  356. }
  357. else
  358. {
  359. fields.forEach(field => {
  360. html += renderFieldItem(field);
  361. });
  362. }
  363. fieldList.html(html);
  364. /* 初始化拖动排序 */
  365. fieldListSortable();
  366. }
  367. /* 字段列表拖动排序 */
  368. function fieldListSortable()
  369. {
  370. if($('#field-list').data('sortable-initialized')) return;
  371. new zui.Sortable('#field-list', {
  372. handle: '.field-item',
  373. animation: 150,
  374. ghostClass: 'bg-primary-pale',
  375. onSort: function(event)
  376. {
  377. const newOrder = [];
  378. $('#field-list .field-item').each(function()
  379. {
  380. const fieldId = $(this).data('id');
  381. newOrder.push(fieldId);
  382. });
  383. FieldManager.setFieldIds(newOrder);
  384. }
  385. });
  386. $('#field-list').data('sortable-initialized', true);
  387. }
  388. /* 渲染选项 */
  389. function renderOption(option, index)
  390. {
  391. const disabledAttr = index === 0 ? 'disabled' : '';
  392. return `
  393. <div class="input-group" data-index="${index}">
  394. <span class="input-group-addon">${fieldOption}${index + 1}</span>
  395. <div class="input-control">
  396. <input class="form-control" type="text" placeholder="${pleaseInput}" value="${option}" />
  397. </div>
  398. <button type="button" data-action="add" class="btn square input-group-addon">
  399. <i class="icon icon-plus"></i>
  400. </button>
  401. <button type="button" data-action="delete" class="btn square input-group-addon" ${disabledAttr}>
  402. <i class="icon icon-minus"></i>
  403. </button>
  404. </div>
  405. `;
  406. }
  407. /* 渲染选项列表 */
  408. function renderOptions()
  409. {
  410. const type = ModalManager.getType();
  411. const options = ModalManager.getOptions();
  412. if(!['radio', 'checkbox'].includes(type))
  413. {
  414. $('#field-placeholder').removeClass('hidden');
  415. $('#field-options').addClass('hidden');
  416. return;
  417. }
  418. const optionsList = $('#field-options');
  419. optionsList.empty();
  420. let html = '';
  421. options.forEach((option, index) => {
  422. html += renderOption(option, index);
  423. });
  424. optionsList.html(html);
  425. $('#field-placeholder').addClass('hidden');
  426. $('#field-options').removeClass('hidden');
  427. }
  428. /* 监听字段类型变化 */
  429. function watchFieldTypeChange()
  430. {
  431. $('#field-modal select[name="field-type"]').on('change', function()
  432. {
  433. const type = $(this).val();
  434. ModalManager.setType(type);
  435. if(['radio', 'checkbox'].includes(type))
  436. {
  437. let options = ModalManager.getOptions();
  438. if(!options || options.length === 0) options = ['', '', ''];
  439. ModalManager.setOptions(options);
  440. }
  441. });
  442. }
  443. /* 打开 Modal(添加/编辑字段) */
  444. function openModal(action, fieldId)
  445. {
  446. $('#field-modal .modal-header span').text(action === 'add' ? fieldAdd : fieldEdit);
  447. const field = FieldManager.getField(fieldId);
  448. const type = action === 'add' ? 'text' : field.type;
  449. let options = action === 'add' ? ['', '', ''] : field.options;
  450. if(['radio', 'checkbox'].includes(type) && (!options || options.length === 0)) options = ['', '', ''];
  451. ModalManager.setState({
  452. action,
  453. id: fieldId,
  454. type,
  455. options,
  456. });
  457. $('#field-modal input[name="field-name"]').val(action === 'add' ? '' : field.name);
  458. $('#field-modal select[name="field-type"]').val(type);
  459. $('#field-modal input[name="field-placeholder"]').val(action === 'add' ? '' : field.placeholder);
  460. $('#field-modal input[name="field-required"]').val(action === 'add' ? '1' : String(field.required));
  461. zui.Modal.open({
  462. id: 'field-modal',
  463. });
  464. }
  465. /* 确认并删除字段 */
  466. function confirmToDeleteField(fieldId)
  467. {
  468. zui.Modal.confirm(deleteTip).then(confirmed => {
  469. if(confirmed) FieldManager.removeField(fieldId);
  470. });
  471. }
  472. /* 字段列表操作 */
  473. function fieldItemActions()
  474. {
  475. $('#field-list')
  476. .on('click', 'button', function(event)
  477. {
  478. event.preventDefault();
  479. const action = $(this).data('action');
  480. const fieldId = $(this).closest('.field-item').data('id');
  481. switch(action)
  482. {
  483. case 'add':
  484. openModal('add', fieldId);
  485. break;
  486. case 'edit':
  487. openModal('edit', fieldId);
  488. break;
  489. case 'delete':
  490. confirmToDeleteField(fieldId);
  491. break;
  492. default:
  493. }
  494. })
  495. .on('click', '#add-field', function(event)
  496. {
  497. event.preventDefault();
  498. openModal('add', null);
  499. });
  500. }
  501. /* 字段选项操作 */
  502. function fieldOptionActions()
  503. {
  504. $('#field-options').on('click', 'button', function()
  505. {
  506. const action = $(this).data('action');
  507. const index = $(this).closest('.input-group').data('index');
  508. const options = ModalManager.getOptions();
  509. if(action === 'add') options.splice(parseInt(index, 10) + 1, 0, '');
  510. else options.splice(parseInt(index, 10), 1);
  511. ModalManager.setOptions(options);
  512. });
  513. }
  514. /* 字段选项变化 */
  515. function fieldOptionsChange()
  516. {
  517. $('#field-options').on('change', 'input', function()
  518. {
  519. const options = ModalManager.getOptions();
  520. const value = $(this).val();
  521. const index = $(this).closest('.input-group').data('index');
  522. options[parseInt(index, 10)] = value;
  523. ModalManager.setOptions(options, false);
  524. });
  525. }
  526. /* field Modal 数据验证 */
  527. function validateFieldData(id, name, type, options)
  528. {
  529. /* 1. name 不能为空 */
  530. if(!name.trim())
  531. {
  532. zui.Messager.show({
  533. content: emptyWarning.replace('%s', fieldName),
  534. type: 'danger',
  535. });
  536. return false;
  537. }
  538. const fields = FieldManager.getFields();
  539. /* 2. name 不能重复 */
  540. if(fields.some(field => field.id !== id && field.name === name))
  541. {
  542. zui.Messager.show({
  543. content: duplicatedWarning.replace('%s', fieldName),
  544. type: 'danger',
  545. });
  546. return false;
  547. }
  548. /* 3. options 不能为空 */
  549. if(['radio', 'checkbox'].includes(type) && options.length === 0)
  550. {
  551. zui.Messager.show({
  552. content: emptyOptionWarning,
  553. type: 'danger',
  554. });
  555. return false;
  556. }
  557. return true;
  558. }
  559. /* field Modal 保存 */
  560. function fieldModalSave()
  561. {
  562. $('#field-modal-save').on('click', function()
  563. {
  564. const fieldState = ModalManager.getState();
  565. const name = $('#field-modal input[name="field-name"]').val().trim();
  566. const placeholder = $('#field-modal input[name="field-placeholder"]').val();
  567. const required = parseInt($('#field-modal input[name="field-required"]:checked').val() || '1', 10);
  568. const type = fieldState.type;
  569. let options = [];
  570. if(['radio', 'checkbox'].includes(type)) options = fieldState.options.filter(option => option.trim());
  571. const id = fieldState.action === 'add' ? null : fieldState.id;
  572. const valid = validateFieldData(id, name, type, options);
  573. if(!valid) return;
  574. const field = {
  575. id,
  576. name,
  577. type,
  578. required,
  579. };
  580. if(['radio', 'checkbox'].includes(type)) field.options = options;
  581. else field.placeholder = placeholder;
  582. if(fieldState.action === 'add') FieldManager.addField(field, fieldState.id);
  583. else FieldManager.updateField(field);
  584. zui.Modal.hide('#field-modal');
  585. });
  586. }
  587. /* 表单数据 */
  588. const FormFieldData = {};
  589. /* 表单字段变化 */
  590. function formFieldChange()
  591. {
  592. $('#field-list').on('change', 'input, textarea, select', function() {
  593. const fieldId = $(this).closest('.field-item').data('id');
  594. const value = $(this).val();
  595. FormFieldData[fieldId] = value;
  596. generatePromptPreview();
  597. });
  598. }
  599. /* 构建输入提示列表 */
  600. function buildInputSuggestions(query = '')
  601. {
  602. const list = FieldManager.getFields().map(field => ({
  603. text: field.name,
  604. value: `${field.name}>`,
  605. }));
  606. if(!query) return list;
  607. return list.filter(item => item.text.includes(query));
  608. }
  609. /* 处理 Prompt 文本 */
  610. function processPromptText(text)
  611. {
  612. if(!text) return '';
  613. return text.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
  614. }
  615. /* 将文本转换为 HTML */
  616. function propmtTextToHtml(text)
  617. {
  618. let html = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  619. const fields = FieldManager.getFields();
  620. const fieldNames = {};
  621. fields.forEach(field => {
  622. fieldNames[field.name] = field.id;
  623. });
  624. html = html.replace(/&lt;([^&]+)&gt;/g, function(match, fieldName)
  625. {
  626. if(!fieldNames[fieldName]) return match;
  627. return `<span class="mention-label" data-type="mention" data-id="${fieldName}&gt;" data-label="${fieldName}&gt;" data-mention-suggestion-char="&lt;">&lt;${fieldName}&gt;</span>`;
  628. });
  629. const list = html.split('\n');
  630. return list.map((line) => `<p>${line}</p>`).join('');
  631. }
  632. /* 初始化 Prompt Editor */
  633. function initPromptEditor()
  634. {
  635. /* 是否为初始化 */
  636. let isInit = true;
  637. promptData.html = propmtTextToHtml(initPrompt);
  638. promptData.editor = new zui.TEditor('#prompt-editor', {
  639. content: promptData.html,
  640. mode: 'plain',
  641. suggestions: {
  642. '<': query => buildInputSuggestions(query),
  643. },
  644. onChange: function(content)
  645. {
  646. promptData.text = content.replace(/\n\n/g, '\n');
  647. promptData.html = promptData.editor.getHtml();
  648. /* 跳过初始化导致的改动 */
  649. if(isInit) isInit = false;
  650. else isSaved = false;
  651. checkAndUpdateSubmit();
  652. generatePromptPreview();
  653. },
  654. });
  655. generatePromptPreview();
  656. }
  657. /* 更新 Prompt 数据 */
  658. function updatePromptData(data)
  659. {
  660. switch(data.action)
  661. {
  662. case 'update':
  663. promptData.text = promptData.text.replace(new RegExp(`<${data.oldName}>`, 'g'), `<${data.newName}>`);
  664. break;
  665. case 'delete':
  666. promptData.text = promptData.text.replace(new RegExp(` <${data.name}> `, 'g'), '');
  667. break;
  668. default:
  669. return;
  670. }
  671. isSaved = false;
  672. promptData.html = propmtTextToHtml(promptData.text);
  673. promptData.editor.setContent(promptData.html);
  674. generatePromptPreview();
  675. }
  676. /* 生成 Prompt 预览 */
  677. function generatePromptPreview()
  678. {
  679. const promptPreview = $('#prompt-preview');
  680. promptPreview.empty();
  681. let html = promptData.text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  682. const fields = FieldManager.getFields();
  683. fields.forEach(field => {
  684. const { id, name, type } = field;
  685. let value = `&lt;${name}&gt;`;
  686. if(FormFieldData[id])
  687. {
  688. if(type === 'checkbox')
  689. {
  690. const options = FormFieldData[id].filter(item => item);
  691. if(options.length > 0) value = options.join(',');
  692. }
  693. else
  694. {
  695. value = FormFieldData[id];
  696. }
  697. }
  698. const list = html.split(`&lt;${name}&gt;`).map(item => item.trim());
  699. html = list.join(`<span>${value}</span>`);
  700. });
  701. promptPreview.html(html);
  702. }
  703. /* 渲染字段列表占位符 */
  704. function renderKnowledgeLibsPlaceholder()
  705. {
  706. return `
  707. <div class="field-empty" id="add-knowledge-lib">
  708. <div class="action">
  709. <i class="icon icon-plus"></i>
  710. <span>${knowledgeLibAdd}</span>
  711. </div>
  712. <div class="tips">${knowledgeLibAddTip}</div>
  713. </div>
  714. `;
  715. }
  716. /* 渲染字段项 */
  717. function renderKnowledgeLibItem(lib)
  718. {
  719. const available = lib.published && !lib.deleted;
  720. let badge = '';
  721. if(!lib.published) badge = `<span class="badge badge-danger">${unpublished}</span>`;
  722. if(lib.deleted) badge = `<span class="badge badge-danger">${deleted}</span>`;
  723. return `
  724. <li class="field-item" data-id="${lib.id}">
  725. <div class="rect">
  726. <span class="text-ellipsis" title="${lib.name}">${lib.name}</span>
  727. ${badge}
  728. </div>
  729. <button class="btn size-sm square" data-action="add">
  730. <i class="icon icon-plus"></i>
  731. </button>
  732. <button class="btn size-sm square ${available ? '' : 'disabled'}" data-action="edit" ${available ? '' : 'disabled'}>
  733. <i class="icon icon-edit"></i>
  734. </button>
  735. <button class="btn size-sm square" data-action="delete">
  736. <i class="icon icon-trash"></i>
  737. </button>
  738. </li>
  739. `;
  740. }
  741. /* 渲染知识库列表 */
  742. function renderKnowledgeLibs()
  743. {
  744. const libList = $('#knowledge-libs');
  745. libList.empty();
  746. const libs = KnowledgeLibManager.getLibs();
  747. const ids = libs.map(lib => lib.id).join(',');
  748. $('input[name="knowledgeLib"]').val(ids);
  749. let html = '';
  750. if(libs.length === 0)
  751. {
  752. html = renderKnowledgeLibsPlaceholder();
  753. }
  754. else
  755. {
  756. libs.forEach(lib => {
  757. html += renderKnowledgeLibItem(lib);
  758. });
  759. }
  760. libList.html(html);
  761. const knowledgeLibNames = $('#knowledgelib-names');
  762. knowledgeLibNames.text('');
  763. const names = [];
  764. libs.forEach(lib => {
  765. if(lib.published === '1' && lib.deleted === '0') names.push(lib.name);
  766. });
  767. knowledgeLibNames.text(names.join(', '));
  768. }
  769. /* 知识库列表操作 */
  770. function knowledgeLibItemActions()
  771. {
  772. $('#knowledge-libs')
  773. .on('click', 'button', function(event)
  774. {
  775. event.preventDefault();
  776. const action = $(this).data('action');
  777. const libId = $(this).closest('.field-item').data('id');
  778. switch(action)
  779. {
  780. case 'add':
  781. openKnowledgeLibModal('add', libId);
  782. break;
  783. case 'edit':
  784. openKnowledgeLibModal('edit', libId);
  785. break;
  786. case 'delete':
  787. confirmToDeleteKnowledgeLib(libId);
  788. break;
  789. default:
  790. }
  791. })
  792. .on('click', '#add-knowledge-lib', function(event)
  793. {
  794. event.preventDefault();
  795. openKnowledgeLibModal('add', null);
  796. });
  797. }
  798. /* 打开知识库 Modal */
  799. function openKnowledgeLibModal(action, libId)
  800. {
  801. KnowledgeLibManager.setAction(action, libId);
  802. const selectedID = KnowledgeLibManager.getLibs().map(lib => lib.id).join(',');
  803. zui.Modal.open({
  804. id: 'selectKnowledgeLibModal',
  805. url: $.createLink('ai', 'selectKnowledgeLib', `selectedID=${selectedID}&callback=knowledgeLibOnSelect&multiple=0&showSelected=1${action === 'edit' ? '&current=' + libId : ''}`),
  806. size: 'sm',
  807. });
  808. }
  809. /* 知识库选中回调 */
  810. window.knowledgeLibOnSelect = function(libs)
  811. {
  812. const { action, targetId } = KnowledgeLibManager.getAction();
  813. if(action === 'add')
  814. {
  815. KnowledgeLibManager.addLib(libs[0], targetId);
  816. }
  817. else if(action === 'edit')
  818. {
  819. KnowledgeLibManager.updateLib(libs[0], targetId);
  820. }
  821. };
  822. /* 确认并删除知识库 */
  823. function confirmToDeleteKnowledgeLib(libId)
  824. {
  825. zui.Modal.confirm(confirmDelete)
  826. .then(confirmed => {
  827. if(confirmed) KnowledgeLibManager.removeLib(libId);
  828. });
  829. }
  830. /* 获取字段数据 */
  831. function getFieldsData()
  832. {
  833. const fields = FieldManager.getFields();
  834. return fields.map(field => {
  835. const { name, type, required, placeholder, options } = field;
  836. const fieldData = {
  837. name,
  838. type,
  839. required: required ? '1' : '0',
  840. appID: promptID,
  841. };
  842. if(['text', 'textarea'].includes(type))
  843. {
  844. fieldData.placeholder = placeholder || '';
  845. }
  846. else if(['radio', 'checkbox'].includes(type))
  847. {
  848. fieldData.options = options ? options.join(',') : '';
  849. }
  850. return fieldData;
  851. });
  852. }
  853. /* 发送页面数据 */
  854. function postData(data)
  855. {
  856. $.ajax({
  857. url: $.createLink('ai', 'promptSetPurpose', `promptID=${promptID}`),
  858. type: 'POST',
  859. data: JSON.stringify(data),
  860. processData: false,
  861. contentType: 'application/json; charset=UTF-8',
  862. dataType: 'json',
  863. })
  864. .done(function(response) {
  865. const message = response.msg || response.message;
  866. if(response.result === 'success')
  867. {
  868. isSaved = true;
  869. if(message)
  870. {
  871. zui.Messager.show({
  872. content: message,
  873. type: 'success',
  874. });
  875. }
  876. if(response.locate) openUrl(response.locate);
  877. }
  878. else if(message)
  879. {
  880. zui.Messager.show({
  881. content: message,
  882. type: 'danger',
  883. });
  884. }
  885. })
  886. .fail(function(xhr, status, error) {
  887. zui.Messager.show({
  888. content: saveFailed + ':' + error,
  889. type: 'danger',
  890. });
  891. });
  892. }
  893. /* 保存页面数据 */
  894. function saveData()
  895. {
  896. const fields = getFieldsData();
  897. const prompt = promptData.text.trim();
  898. const knowledgeLib = KnowledgeLibManager.getLibs().map(lib => lib.id).join(',');
  899. if(!prompt)
  900. {
  901. zui.Messager.show({
  902. content: promptPlaceholder,
  903. type: 'danger',
  904. });
  905. return;
  906. }
  907. postData({
  908. fields,
  909. purpose: prompt,
  910. knowledgeLib,
  911. jumpToNext: 1,
  912. });
  913. }
  914. /* 保存 */
  915. function actionSave()
  916. {
  917. $('#btn-save').on('click', function() {
  918. saveData();
  919. });
  920. }