zentaobiz.ui.js 27 KB

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