zentaobiz.ui.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. (({jsx, ReactComponent, ComponentFromReact, signal, fetchData, reactComponents}) =>
  2. {
  3. if(zui.AIKnowledgeChunkList) return;
  4. /**
  5. * Knowledge chunk list component.
  6. *
  7. * Props:
  8. * - id: Knowledge item ID.
  9. */
  10. class AIKnowledgeChunkListJSX extends ReactComponent
  11. {
  12. chunks$ = signal([]); // Loaded chunks list.
  13. error$ = signal(''); // Error message.
  14. loading$ = signal(false); // Loading state.
  15. synced$ = signal(false); // Whether synced.
  16. needSync$ = signal(false); // Whether need to sync.
  17. waitTimer$ = signal(0); // Wait timer after synced.
  18. componentDidMount()
  19. {
  20. this.load();
  21. }
  22. componentDidUpdate(prevProps)
  23. {
  24. if(prevProps.id !== this.props.id) this.load(true);
  25. }
  26. componentWillUnmount()
  27. {
  28. this.resetTimer();
  29. }
  30. resetTimer(callback, delay)
  31. {
  32. if(this.waitTimer$.value) clearInterval(this.waitTimer$.value);
  33. this.waitTimer$.value = 0;
  34. if(callback) this.waitTimer$.value = setInterval(callback, delay || 5000);
  35. }
  36. /** Start sync knowledge item to zai and wait chunks. */
  37. async startSync(reset)
  38. {
  39. if(reset)
  40. {
  41. this.needSync$.value = true;
  42. this.loading$.value = true;
  43. this.chunks$.value = [];
  44. }
  45. if(!this.needSync$.value || this.waitTimer$.value) return;
  46. this.synced$.value = true;
  47. try
  48. {
  49. const url = $.createLink('ai', 'ajaxSyncKnowledgeItem', `id=${this.props.id}&force=yes`);
  50. const result = await fetchData(url, [], {method: 'POST'});
  51. if(result.result !== 'success') this.error$.value = result.message;
  52. this.loading$.value = true;
  53. this.resetTimer(() =>
  54. {
  55. this.loading$.value = false;
  56. this.load();
  57. }, 5000);
  58. }
  59. catch(error)
  60. {
  61. if(config.debug) console.error(error);
  62. this.error$.value = String(error);
  63. }
  64. this.needSync$.value = false;
  65. }
  66. /** Load chunks list. */
  67. async load(reset)
  68. {
  69. if(this.loading$.value) return;
  70. this.loading$.value = true;
  71. this.error$.value = '';
  72. if(reset)
  73. {
  74. this.resetTimer();
  75. this.chunks$.value = [];
  76. this.needSync$.value = false;
  77. this.synced$.value = false;
  78. }
  79. const url = $.createLink('ai', 'ajaxGetKnowledgeChunks', `id=${this.props.id}&force=yes`);
  80. try
  81. {
  82. const result = await fetchData(url);
  83. if(result.result === 'success')
  84. {
  85. this.chunks$.value = result.data;
  86. this.error$.value = '';
  87. this.resetTimer();
  88. if(Array.isArray(result.data) && !result.data.length && !result.needSync)
  89. {
  90. this.waitTimer$.value = setTimeout(() =>
  91. {
  92. if(!this.loading$.value) this.load();
  93. }, 5000);
  94. }
  95. }
  96. else
  97. {
  98. this.chunks$.value = [];
  99. this.error$.value = result.message + (result.error ? ` (${result.error})` : '');
  100. if(result.error) this.resetTimer();
  101. }
  102. if(result.needSync && !this.chunks$.value.length)
  103. {
  104. this.needSync$.value = true;
  105. if(!this.synced$.value) this.startSync();
  106. }
  107. }
  108. catch(error)
  109. {
  110. if(config.debug) console.error(error);
  111. this.error$.value = String(error);
  112. }
  113. this.loading$.value = false;
  114. }
  115. render()
  116. {
  117. const props = this.props;
  118. const chunks = this.chunks$.value || [];
  119. const error = this.error$.value;
  120. const hasTimer = this.waitTimer$.value;
  121. let contentView = null;
  122. if(chunks.length)
  123. {
  124. const {MarkdownContent} = reactComponents;
  125. contentView = chunks.map((chunk) => {
  126. if(chunk.payload && chunk.payload.content_type === 'markdown') return jsx`<div key=${chunk.id} class="knowledge-chunk-item whitespace-pre-wrap surface p-4"><${MarkdownContent} content=${chunk.content}><//></div>`;
  127. return jsx`<div key=${chunk.id} class="knowledge-chunk-item whitespace-pre-wrap surface p-4"><p>${chunk.content}</p></div>`;
  128. });
  129. }
  130. else
  131. {
  132. let hintText = '';
  133. if(this.needSync$.value && !hasTimer) hintText = props.needSyncKnowledgeItemText;
  134. else if(error) hintText = jsx`<p class="text-danger">${error}</p>`;
  135. else if(hasTimer) hintText = jsx`<p><i class="icon spin icon-spinner-indicator"></i> ${props.syncingKnowledgeItemText}</p>`;
  136. else hintText = this.synced$.value ? props.emptyKnowledgeDataText : '';
  137. contentView = jsx`<div class="alert col gap-2 py-8"><p class="text-info">${hintText}</p></div>`;
  138. }
  139. return jsx`<div class="knowledge-chunk-list col gap-4 relative load-indicator${(this.loading$.value && !hasTimer) ? ' loading' : ''}">${contentView}</div>`;
  140. }
  141. }
  142. class AIKnowledgeChunkList extends ComponentFromReact
  143. {
  144. static NAME = 'AIKnowledgeChunkList';
  145. static Component = AIKnowledgeChunkListJSX;
  146. }
  147. AIKnowledgeChunkList.register();
  148. zui.AIKnowledgeChunkList = AIKnowledgeChunkList;
  149. })(zui);
  150. window.changePromptName = function(event)
  151. {
  152. const hasValue = event.target.value?.length > 0;
  153. $('button[type="submit"]').toggleClass('disabled', !hasValue).prop('disabled', !hasValue);
  154. }
  155. window.initPromptForm = function()
  156. {
  157. if(!$('input[name="name"]').val()?.length) $('button[type="submit"]').addClass('disabled').attr('disabled', 'disabled');
  158. }
  159. window.syncKnowledgeItemsToZAI = async function(idList, options)
  160. {
  161. options = options || {};
  162. const {beforeSync, afterSync} = options;
  163. const syncedDateMap = {};
  164. const updateDTable = (id, syncedDate) =>
  165. {
  166. const dtable = zui.DTable.query('#knowledgeObjectList');
  167. if(!dtable || !dtable.$) return;
  168. syncedDateMap[id] = syncedDate;
  169. dtable.$.setState({syncedDateMap: {...syncedDateMap}});
  170. };
  171. for(const id of idList)
  172. {
  173. beforeSync && beforeSync(id);
  174. let isSuccess = false;
  175. let error = null;
  176. try
  177. {
  178. updateDTable(id, true);
  179. const result = await zui.fetchData($.createLink('ai', 'ajaxSyncKnowledgeItem', `id=${id}&force=${options.updateFromSource ? 'update' : (options.force ? 'yes' : 'no')}`), [], {method: 'POST'});
  180. isSuccess = typeof result === 'object' && result.result === 'success';
  181. updateDTable(id, isSuccess ? (result.syncedDate || Date.now()) : false);
  182. }
  183. catch(_)
  184. {
  185. if(config.debug) console.error('Sync items to zai error:', _, id);
  186. error = _;
  187. }
  188. afterSync && afterSync(id, isSuccess, error);
  189. }
  190. }
  191. window.requestSyncObjectListToZAI = async function(knowledgeLibID, objectType, silence, updateFromSourceFirst)
  192. {
  193. const $btn = $('#syncObjectListBtn');
  194. const originText = $btn.find('.text').text();
  195. const finish = (message, isSuccess) =>
  196. {
  197. $btn.removeClass('disabled');
  198. $btn.find('.text').text(originText);
  199. $btn.find('.icon').removeClass('spin');
  200. if(message) zui.Messager[isSuccess ? 'success' : 'fail'](message);
  201. };
  202. try
  203. {
  204. objectType = objectType || 'all';
  205. const res = await zui.fetchData($.createLink('ai', 'ajaxGetKnowledgeObjectList', `knowledgeLibID=${knowledgeLibID}&objectType=${objectType}`));
  206. if(!res || typeof res !== 'object') return finish(typeof res === 'string' ? res : '');
  207. if(res.result !== 'success') return finish(typeof res.message === 'string' ? res.message : '');
  208. const {needSyncList, allList, lang} = res.data;
  209. if((!needSyncList.length && !updateFromSourceFirst) || (!allList.length && updateFromSourceFirst))
  210. {
  211. if(!silence) zui.Modal.alert(lang.noDataNeedToUpdate);
  212. return finish();
  213. }
  214. if(!silence)
  215. {
  216. const confirmed = await zui.Modal.confirm(updateFromSourceFirst ? lang.updateFromSourceConfirm : lang.syncListToZAIConfirm);
  217. if(!confirmed) return finish();
  218. }
  219. const finishedList = [];
  220. const totalCount = updateFromSourceFirst ? allList.length : needSyncList.length;
  221. $btn.find('.icon').addClass('spin');
  222. let failedCount = 0;
  223. await syncKnowledgeItemsToZAI(updateFromSourceFirst ? allList : needSyncList,
  224. {
  225. updateFromSource: updateFromSourceFirst,
  226. beforeSync: () => $btn.find('.text').text(`${lang.syncingData} ${finishedList.length}/${totalCount}`),
  227. afterSync: (id, isSuccess) =>
  228. {
  229. finishedList.push(id);
  230. $btn.find('.text').text(`${lang.syncingData} ${finishedList.length}/${totalCount}`);
  231. if(!isSuccess) failedCount++;
  232. }
  233. });
  234. if(failedCount && !silence)
  235. {
  236. zui.Modal.alert(lang.syncFailedCountAlert.replace('%s', failedCount));
  237. }
  238. if(updateFromSourceFirst) loadCurrentPage();
  239. finish();
  240. }
  241. catch(error)
  242. {
  243. finish(String(error));
  244. }
  245. }