ai.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. window.checkZAIPanel = async function(showMessage)
  2. {
  3. const zaiPanel = zui.AIPanel.shared;
  4. const store = zaiPanel ? zaiPanel.store : null;
  5. if(!store || !store.isConfigOK)
  6. {
  7. if(showMessage) zui.Modal.alert((store ? store.error : '') || {content: {html: zaiLang.zaiConfigNotValid}});
  8. return;
  9. }
  10. const isOK = await store.isOK();
  11. if(!isOK)
  12. {
  13. if(showMessage) zui.Modal.alert((store ? store.error : '') || {content: {html: zaiLang.unauthorizedError}});
  14. return;
  15. }
  16. return zaiPanel;
  17. };
  18. window.openPageForm = function(url, data, callback)
  19. {
  20. return new Promise((resolve, reject) => {
  21. localStorage.setItem('aiResult', JSON.stringify(data));
  22. const openedApp = openUrl(url);
  23. if(!openedApp) return;
  24. let updateTimer = 0;
  25. const tryUpdateForm = () =>
  26. {
  27. if(updateTimer) clearTimeout(updateTimer);
  28. updateTimer = setTimeout(() =>
  29. {
  30. try
  31. {
  32. if(data)
  33. {
  34. const iframe = openedApp.iframe;
  35. iframe.contentWindow.applyFormData(data);
  36. }
  37. callback && callback(openedApp);
  38. resolve(openedApp);
  39. } catch (error) {reject(error)}
  40. }, 2000);
  41. };
  42. openedApp.$app.one('updateapp.apps updatepage.app', tryUpdateForm);
  43. setTimeout(() => openedApp.$app.off('updateapp.apps', tryUpdateForm), 5000);
  44. });
  45. }
  46. function getPromptFormConfig(fields, extraConfig)
  47. {
  48. if(!Array.isArray(fields) || !fields.length) return;
  49. const typeMap = {radio: 'picker', checkbox: 'multiPicker', text: 'input'};
  50. const properties = fields.reduce((properties, field, index) =>
  51. {
  52. field.code = `field-${field.id}`;
  53. properties[field.code] = {
  54. type : 'string',
  55. widget : typeMap[field.type] || field.type,
  56. title : field.name,
  57. placeholder: field.placeholder,
  58. order : index,
  59. required : field.required && field.required !== '0',
  60. props : zui.isNotEmptyString(field.options) ? {items: field.options.split(',').map(x => ({text: x, value: x}))}: undefined
  61. };
  62. return properties;
  63. }, {});
  64. return $.extend(
  65. {
  66. schema: {type: 'object', properties: properties},
  67. prompt: (data) => fields.map(x => `* ${x.name}: ${data[x.code] || ''}`).join('\n')
  68. }, extraConfig);
  69. }
  70. window.executeZentaoPrompt = async function(info, testingMode)
  71. {
  72. testingMode = testingMode && testingMode !== '0';
  73. const zaiPanel = await checkZAIPanel(true);
  74. if(!zaiPanel) return;
  75. const langData = zaiPanel.options.langData || {};
  76. const noTargetForm = !info.targetForm || info.targetForm === 'empty.empty';
  77. const toolName = `zentao_tool_${info.promptID}`;
  78. const agentTool = noTargetForm ? null : {
  79. name : toolName,
  80. displayName: info.name,
  81. description: info.name,
  82. parameters :
  83. {
  84. type: 'object',
  85. properties:
  86. {
  87. data: info.schema,
  88. title: {type: 'string', description: langData.promptResultTitle},
  89. summary: {type: 'string', description: langData.agentResultSummary},
  90. },
  91. required: ['data', 'summary'],
  92. },
  93. };
  94. const tools = noTargetForm ? [] : [{
  95. ...agentTool,
  96. fn: (response) => {
  97. const result = response.data;
  98. const targetForm = info.targetForm;
  99. if(!targetForm) return {result: result};
  100. const taskResult =
  101. {
  102. agentID : info.promptID,
  103. id : `zentao-agent-result-${info.promptID}`,
  104. tool : agentTool,
  105. title : response.title,
  106. result : response,
  107. formLocation : info.formLocation,
  108. targetFormName: info.targetFormName,
  109. targetForm : info.targetForm,
  110. objectID : info.objectID,
  111. objectType : info.objectType,
  112. objectData : info.objectData,
  113. objectProps : info.dataPropNames,
  114. actions: info.promptAudit ? [{
  115. text : langData.goTesting,
  116. url : $.createLink('ai', 'promptAudit', `promptId=${info.promptID}&objectId=${info.objectID || 0}`),
  117. type : 'primary-pale',
  118. 'data-toggle': 'modal',
  119. }] : [],
  120. };
  121. const message =
  122. {
  123. role: 'user',
  124. content: [response.title, zui.formatString(langData.processedDataResult, {data: JSON.stringify(result)}), response.summary, zui.formatString(langData.promptResultReturn, {formName: info.targetFormName})].join('\n\n'),
  125. custom_data: {taskResults: [taskResult], asRole: 'assistant'}
  126. };
  127. return {message: message};
  128. },
  129. }];
  130. const klibs = (info.knowledgeLib ? info.knowledgeLib.split(',') : []).filter(Boolean).map(x => `zentao:${x}`);
  131. const formConfig = getPromptFormConfig(info.fields, info.formConfig);
  132. const popupOptions = {
  133. id : 'zentao-prompt-popoup',
  134. viewType : 'chat',
  135. width : info.content ? 800 : 600,
  136. postMessage: {content: [{role: 'user', content: info.purpose, custom_data: {invisible: true}}]},
  137. creatingChat: {
  138. title : info.name,
  139. type : 'agent',
  140. model : info.model,
  141. tools : tools,
  142. prompt : [info.role, zui.formatString(langData.processDataPrefix, {data: info.dataPrompt}), noTargetForm ? null : zui.formatString(langData.promptExtraLimit, {toolName: toolName})].filter(Boolean).join('\n\n'),
  143. form : formConfig,
  144. memories : klibs.length ? [{collections: klibs}] : undefined,
  145. },
  146. };
  147. zaiPanel.openPopup(popupOptions);
  148. };
  149. window.openAITaskPopup = async function(taskID)
  150. {
  151. const zaiPanel = await checkZAIPanel(true);
  152. if(!zaiPanel) return;
  153. const popupOptions = {
  154. id : 'zentao-task-popoup',
  155. viewType : 'task',
  156. width : 600,
  157. chatID : `task-${taskID}`,
  158. };
  159. zaiPanel.openPopup(popupOptions);
  160. };
  161. window.callZentaoAgent = async function(agentID, objectID)
  162. {
  163. const res = await $.ajax({url: $.createLink('ai', 'promptExecute', `promptId=${agentID}&objectId=${objectID}`), 'dataType': 'json'});
  164. if(!res || res.result !== 'success' || !res.callback) return;
  165. return executeZentaoPrompt(res.callback.params[0], res.callback.params[1]);
  166. };
  167. /* 加载数字员工列表,并注册菜单 */
  168. function loadAndRegisterAiTeammates(lang, plugin)
  169. {
  170. plugin.defineContextProvider({
  171. code: 'ai-teammate',
  172. title: lang.teammate,
  173. icon: 'hand-right',
  174. items: async function()
  175. {
  176. const res = await zui.fetchData($.createLink('ai', 'ajaxGetTeammates'));
  177. if(!res || res.result !== 'success' || !res.data) return [];
  178. const teammates = res.data;
  179. if(!teammates.length) return;
  180. const items = teammates.map((item) => {
  181. const collections = [];
  182. if(item.klibs && item.klibs.length)
  183. {
  184. item.klibs.forEach(klibID => collections.push(`zentao:${klibID}`));
  185. }
  186. const promptParts = [];
  187. if(item.roleName)
  188. {
  189. const prefix = lang.teammatePromptPrefix;
  190. promptParts.push(`${prefix}${item.roleName}`);
  191. }
  192. if(item.desc) promptParts.push(item.desc);
  193. if(item.klibNames && item.klibNames.length)
  194. {
  195. const klibNamesStr = item.klibNames.join(', ');
  196. const knowledgePrefix = lang.teammateKnowledgePrefix;
  197. const knowledgeSuffix = lang.teammateKnowledgeSuffix;
  198. promptParts.push(`${knowledgePrefix}${klibNamesStr}${knowledgeSuffix}`);
  199. }
  200. const data = {
  201. prompt: promptParts.join(', '),
  202. };
  203. if(collections.length) data.memory = {collections};
  204. return {
  205. code: `zentao-aiteammate-${item.id}`,
  206. title: item.name,
  207. hint: item.desc || item.name,
  208. data,
  209. llm: item.llm || undefined
  210. };
  211. });
  212. return items;
  213. },
  214. });
  215. }
  216. function registerZentaoAIPlugin(lang)
  217. {
  218. const plugin = zui.AIPlugin.define('zentao', {name: lang.name, icon: 'zentao'});
  219. plugin.defineContextProvider(
  220. {
  221. code: 'currentPage',
  222. title: lang.currentPage,
  223. icon: 'globe',
  224. recommend: true,
  225. when: () => $.apps,
  226. data: () => {
  227. const pageWindow = $.apps.getLastApp().iframe.contentWindow;
  228. const page$ = pageWindow.$;
  229. const $mainContainer = page$('#mainContainer');
  230. const pageContent = $mainContainer.length ? $mainContainer.text() : page$('body').text();
  231. return {
  232. prompt: [
  233. `当前页面标题:${document.title}`,
  234. "当前页面内容:",
  235. pageContent
  236. ].join('\n\n')
  237. };
  238. },
  239. generate: ({userPrompt}) => {
  240. if(new RegExp(`@(${lang.currentPage})`, 'i').test(userPrompt)) return {};
  241. }
  242. });
  243. const objectIcons = {
  244. story : 'file-text',
  245. demand : 'file-text',
  246. bug : 'bug',
  247. doc : 'doc',
  248. design : 'design',
  249. feedback: 'feedback',
  250. };
  251. const zentaoVersion = window.config?.version || '';
  252. const [_, zentaoEdition] = zentaoVersion.match(/^([a-zA-Z]+)?(\d+\.\d+(\.\d+)?)$/) || [];
  253. ['story', 'demand', 'bug', 'doc', 'design', 'feedback'].forEach(objectType => {
  254. if(objectType === 'feedback' && !zentaoEdition) return;
  255. if(objectType === 'demand' && zentaoEdition !== 'ipd') return;
  256. plugin.defineContextProvider({
  257. code: `${objectType}Lib`,
  258. title: lang[objectType],
  259. icon: objectIcons[objectType],
  260. when: ({store}) => !!store.globalMemory,
  261. data:
  262. {
  263. memory: {collections: ['zentao:global'], content_filter: {attrs: {objectType}}},
  264. },
  265. generate: ({userPrompt}) => {
  266. const objectName = lang[objectType] || objectType;
  267. const matches = [...userPrompt.matchAll(new RegExp(`@(${objectName}${objectType !== objectName ? `|${objectType}` : ''})\\s?#?(\\d+)`, 'gi'))];
  268. if(matches.length)
  269. {
  270. return matches.map(match => {
  271. const objectID = match[2];
  272. return {
  273. code: `${objectType}-${objectID}`,
  274. recommend: true,
  275. title: `${objectName} #${objectID}`,
  276. data: () => ({
  277. memory:
  278. {
  279. collections: ['zentao:global'],
  280. content_filter: {attrs: {objectKey: `${objectType}-${objectID}`}},
  281. },
  282. })
  283. };
  284. });
  285. }
  286. if(new RegExp(`@(${objectName}${objectType !== objectName ? `|${objectType}` : ''})`, 'i').test(userPrompt)) return {};
  287. }
  288. })
  289. });
  290. plugin.defineContextProvider(
  291. {
  292. code : 'currentDocContent',
  293. title : lang.currentDocContent,
  294. icon : 'doc',
  295. recommend: true,
  296. hidden : true,
  297. when: () => {
  298. if(!window.config) return;
  299. const pageWindow = $.apps.getLastApp().iframe.contentWindow;
  300. const page$ = pageWindow.$;
  301. const editor = page$("[z-use-editor]").zui();
  302. return !!editor;
  303. },
  304. data: async () => {
  305. const pageWindow = $.apps.getLastApp().iframe.contentWindow;
  306. const page$ = pageWindow.$;
  307. const editor = page$("[z-use-editor]").zui();
  308. const html = await editor.getHtml();
  309. const text = $(html).text();
  310. return {prompt: ["当前文档内容:", text].join('\n\n')};
  311. },
  312. generate: ({userPrompt}) => {
  313. if (new RegExp(`@(${lang.currentDocContent})`, 'i').test(userPrompt)) return {};
  314. }
  315. });
  316. plugin.defineContextProvider({
  317. code : 'globalMemory',
  318. title: lang.globalMemoryTitle,
  319. icon : 'book',
  320. when : context => !!context.store.globalMemory,
  321. data : {memory: {collections: ['zentao:global']}},
  322. });
  323. if(lang.knowledgeLib)
  324. {
  325. plugin.defineContextProvider({
  326. code : 'knowledgeLibs',
  327. title: lang.knowledgeLib,
  328. icon : 'book',
  329. contexts : function()
  330. {
  331. return new Promise((resolve) => {
  332. zui.Modal.open({url: $.createLink('ai', 'selectknowledgelib', `selectedID=&callback=getKnowledgeLibsByForm`), size: 'sm'});
  333. window.getKnowledgeLibsByForm = function(libs)
  334. {
  335. if(!libs.length) return resolve();
  336. const res = [];
  337. libs.forEach(item => {
  338. res.push({
  339. title: item.name,
  340. hint: item.name,
  341. code: `zentao-knowledgeLib-${item.id}`,
  342. data: {
  343. memory: {collections: [`zentao:${item.id}`]}
  344. }
  345. })
  346. });
  347. resolve(res);
  348. }
  349. });
  350. },
  351. });
  352. }
  353. window.enableAITeammate && loadAndRegisterAiTeammates(lang, plugin);
  354. plugin.defineSuggestion(
  355. {
  356. when: ({state}) =>
  357. {
  358. const page = state ? state.zentaoPage : null;
  359. if(!page) return;
  360. const openedApp = $.apps.openedApps[page.app];
  361. if(!openedApp) return;
  362. const aiSuggestions = openedApp.iframe.contentWindow ? openedApp.iframe.contentWindow.aiSuggestions : null;
  363. return Array.isArray(aiSuggestions) && aiSuggestions.length;
  364. },
  365. items: function({state})
  366. {
  367. const zentaoPage = state ? state.zentaoPage : null;
  368. if(!zentaoPage) return;
  369. const openedApp = $.apps.openedApps[zentaoPage.app];
  370. if(!openedApp) return;
  371. const aiSuggestions = openedApp.iframe.contentWindow ? openedApp.iframe.contentWindow.aiSuggestions : null;
  372. return aiSuggestions.map(suggestion => {
  373. const {page = '', zentaoAgent, ...others} = suggestion;
  374. const pageList = page.split(',').filter(Boolean);
  375. if(pageList && !pageList.some(x => x === zentaoPage.path || x === zentaoPage.currentModule)) return;
  376. return {
  377. ...others,
  378. ...(zentaoAgent ? {action: () => callZentaoAgent(zentaoAgent.agentID, zentaoAgent.objectID)} : {}),
  379. };
  380. }).filter(Boolean);
  381. }
  382. });
  383. plugin.defineCallback('onCreateChat', async function(info)
  384. {
  385. if(info.isLocal) return;
  386. const originMemories = info.options.memories;
  387. if(!originMemories || !originMemories.length) return;
  388. const knowledgeLibs = {};
  389. const otherMemories = originMemories.reduce((others, memory) =>
  390. {
  391. const ohterCollections = [];
  392. for(const collection of memory.collections)
  393. {
  394. if(collection.startsWith('zentao:'))
  395. {
  396. const lib = collection.substr(7);
  397. const newFilter = $.extend(true, {}, memory.content_filter);
  398. if(!Object.keys(newFilter).length)
  399. {
  400. knowledgeLibs[lib] = {};
  401. break;;
  402. }
  403. const oldFilter = knowledgeLibs[lib] ? knowledgeLibs[lib] : null;
  404. const finalFilter = $.extend(true, {}, oldFilter, newFilter);
  405. if(newFilter && newFilter.attrs && oldFilter && oldFilter.attrs)
  406. {
  407. Object.keys(oldFilter.attrs).forEach(attrName =>
  408. {
  409. const oldAttr = oldFilter.attrs[attrName];
  410. const newAttr = newFilter.attrs[attrName];
  411. if(oldAttr === undefined || newAttr === undefined) return;
  412. const finalAttr = typeof oldAttr === 'object' ? oldAttr : {$in: [oldAttr]};
  413. if(typeof newAttr === 'object') finalAttr.$in = [...finalAttr.$in, ...newAttr.$in];
  414. else finalAttr.$in = [...finalAttr.$in, newAttr];
  415. finalFilter.attrs[attrName] = finalAttr;
  416. });
  417. }
  418. knowledgeLibs[lib] = finalFilter;
  419. continue;
  420. }
  421. ohterCollections.push(collection);
  422. }
  423. if(ohterCollections.length) others.push($.extend({}, memory, {collections: ohterCollections}));
  424. return others;
  425. }, []);
  426. if(!Object.keys(knowledgeLibs).length) return;
  427. return {memories: otherMemories, customData: {ztklibs: knowledgeLibs}};
  428. });
  429. plugin.defineCallback('onPostMessage', async function(info)
  430. {
  431. if(!info.postingMessages || !info.postingMessages.length) return;
  432. if(!info.chat.custom_data || !info.chat.custom_data.ztklibs) return;
  433. const userPrompts = [];
  434. const systemPrompts = [];
  435. info.postingMessages.forEach(x =>
  436. {
  437. if(x.role === 'user') userPrompts.push(x.content);
  438. else if(x.role === 'system') systemPrompts.push(x.content);
  439. });
  440. let searchPrompt = userPrompts.filter(Boolean).join('\n').trim();
  441. if(!searchPrompt.length) searchPrompt = systemPrompts.filter(Boolean).join('\n').trim();
  442. if(!searchPrompt.length) return;
  443. info.updateState(lang.searchingKLibs);
  444. const ztklibs = info.chat.custom_data.ztklibs;
  445. const ztChunks = info.chat.$local.ztChunks || {};
  446. const [response] = await $.ajaxSubmit(
  447. {
  448. url: $.createLink('zai', 'ajaxSearchKnowledges'),
  449. data: {userPrompt: searchPrompt, filters: JSON.stringify(ztklibs)}
  450. });
  451. if(response && response.result === 'success' && response.data && Array.isArray(response.data) && response.data.length)
  452. {
  453. const newPropms = [];
  454. const newRefs = [];
  455. const refKeys = new Set();
  456. response.data.forEach(item =>
  457. {
  458. if(ztChunks[item.id]) return;
  459. ztChunks[item.id] = 1;
  460. newPropms.push(item.content);
  461. if(refKeys.has(item.key)) return;
  462. const itemAttrs = item.attrs || {};
  463. newRefs.push({key: item.key, name: itemAttrs.objectTitle || item.knowledgeTitle, type: itemAttrs.objectType || 'knowledge', id: itemAttrs.objectID || item.knowledgeID})
  464. refKeys.add(item.key);
  465. });
  466. info.chat.$local.ztChunks = ztChunks;
  467. return {systemPrompt: newPropms.filter(Boolean).join('\n\n'), refs: newRefs};
  468. }
  469. });
  470. }
  471. /* Bind AI commands in app when app is loaded, example:
  472. $(document).on('loadapp.apps updateapp.apps', (e, args) =>
  473. {
  474. const win = (e.type === 'updateapp' ? $.apps.openedApps[args[0]] : args).iframe.contentWindow;
  475. bindAICommandsInApp(win, 1000);
  476. });*/
  477. function bindAICommandsInApp(win, delay)
  478. {
  479. if(!win || !win.zui || win._bindedAICommands !== undefined) return;
  480. const panel = win.zui.AIPanel.shared;
  481. if(!panel) return;
  482. if(win._bindedAICommands) clearTimeout(win._bindedAICommands);
  483. win._bindedAICommands = setTimeout(() =>
  484. {
  485. win.zui.bindCommands(win.document.body,
  486. {
  487. commands: {},
  488. scope: panel.commandScope,
  489. onCommand: panel.executeCommand.bind(panel)
  490. });
  491. win._bindedAICommands = 0;
  492. }, delay || 0);
  493. }
  494. $(() =>
  495. {
  496. if(getZentaoPageType() !== 'home') return bindAICommandsInApp(window);
  497. const zentaoConfig = window.config
  498. if(!zentaoConfig || zentaoConfig.currentModule !== 'index' || zentaoConfig.currentMethod !== 'index') return;
  499. const zaiConfig = window.zai || window.top.zai;
  500. const isOpenVersion = /^\d/.test(config.version || $('#zuiCSS').attr('href').split('?v=').pop());
  501. if(zaiConfig)
  502. {
  503. registerZentaoAIPlugin(zaiLang);
  504. let userAvatarProps;
  505. const getAvatar = (type, info) =>
  506. {
  507. if(type === 'role' && info.role === 'user')
  508. {
  509. if(userAvatarProps) return userAvatarProps;
  510. const $avatar = $.apps.getLastApp().iframe?.contentWindow.$('#userMenu-toggle>.avatar');
  511. if($avatar?.length)
  512. {
  513. userAvatarProps =
  514. {
  515. text : $avatar.find('.avatar-text').text(),
  516. code : window.config.account,
  517. src : $avatar.find('img').attr('src'),
  518. icon : undefined,
  519. background: $avatar.css('backgroundColor'),
  520. foreColor : $avatar.css('color'),
  521. };
  522. }
  523. return userAvatarProps;
  524. }
  525. if(type === 'chat' && info.chat.teammate)
  526. {
  527. const teammate = zaiConfig.teammateMap[info.chat.teammate] || {id: info.chat.teammate, name: info.chat.teammate};
  528. return {src: teammate.avatar, size: 24, code: teammate.id};
  529. }
  530. };
  531. const aiStore = zui.ZAIStore.createFromZentao($.extend({getAvatar: getAvatar}, zaiConfig));
  532. if(!aiStore) return
  533. zui.AIPanel.init(
  534. {
  535. store : aiStore,
  536. position : {bottom: +window.config.debug > 4 ? 56 : 40, right: 16},
  537. maximizedPosition: {left: 'calc(var(--zt-menu-width) + 4px)', top: 4, bottom: 'calc(var(--zt-apps-bar-height) + 4px)', right: 16},
  538. langData : zaiLang,
  539. getErrorContent: (error) =>
  540. {
  541. let html = '';
  542. if(error.type === 'unauthorized' && zaiLang.unauthorizedError) html = zui.formatString(zaiLang.unauthorizedError, {zaiConfigUrl: $.createLink('zai', 'setting')})
  543. else if(error.type === 'configNotValid' && zaiLang.zaiConfigNotValid) html = zui.formatString(zaiLang.zaiConfigNotValid, {zaiConfigUrl: $.createLink('zai', 'setting')})
  544. if(html.length) return {html: `<div class="row gap-3"><i class="mt-1 icon icon-exclamation text-warning"></i><div class="text-left pr-8">${html}</div></div>`};
  545. return error.message;
  546. },
  547. tabs: !window.enableAITeammate ? undefined : [
  548. {key: 'RECENTS', title: zaiLang.recentChats, chatTypes: ['chat']},
  549. {key: 'TASKS', title: zaiLang.aiTeammateTasks, chatsFetcher: (store) => store.getTasks(), onCreate: false, searchBox: {placeholder: zaiLang.searchTasks}},
  550. ]
  551. });
  552. $(document).on('updatepage.app openapp.apps openOldPage.apps', (e, args) =>
  553. {
  554. const panel = zui.AIPanel.shared;
  555. if(!panel) return;
  556. const pageInfo = e.type === 'openapp' ? args[0]?.getPageInfo?.() : args[0];
  557. if(!pageInfo || !pageInfo.id) return
  558. panel.reactions.trigger(
  559. e.type === 'openapp' ? 'openPage' : 'updatepage',
  560. {page: pageInfo},
  561. {zentaoPage: pageInfo, event: e}
  562. );
  563. const lastPageID = panel.reactions.state.lastPageID;
  564. if(lastPageID !== pageInfo.id)
  565. {
  566. panel.reactions.trigger(
  567. 'openNewPage',
  568. {page: pageInfo},
  569. {zentaoLastPageID: pageInfo.id, event: e},
  570. {lifeTime: 5000}
  571. );
  572. }
  573. }
  574. );
  575. aiStore.isOK().then(isOK => {window.isZaiOK = isOK;});
  576. }
  577. /* Bind AI commands in app when app is loaded. */
  578. $(document).on('loadapp.apps', (_, args) =>
  579. {
  580. setTimeout(() => bindAICommandsInApp(args[0].iframe.contentWindow), 1000);
  581. });
  582. });