| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- window.checkZAIPanel = async function(showMessage)
- {
- const zaiPanel = zui.AIPanel.shared;
- const store = zaiPanel ? zaiPanel.store : null;
- if(!store || !store.isConfigOK)
- {
- if(showMessage) zui.Modal.alert((store ? store.error : '') || {content: {html: zaiLang.zaiConfigNotValid}});
- return;
- }
- const isOK = await store.isOK();
- if(!isOK)
- {
- if(showMessage) zui.Modal.alert((store ? store.error : '') || {content: {html: zaiLang.unauthorizedError}});
- return;
- }
- return zaiPanel;
- };
- window.openPageForm = function(url, data, callback)
- {
- return new Promise((resolve, reject) => {
- localStorage.setItem('aiResult', JSON.stringify(data));
- const openedApp = openUrl(url);
- if(!openedApp) return;
- let updateTimer = 0;
- const tryUpdateForm = () =>
- {
- if(updateTimer) clearTimeout(updateTimer);
- updateTimer = setTimeout(() =>
- {
- try
- {
- if(data)
- {
- const iframe = openedApp.iframe;
- iframe.contentWindow.applyFormData(data);
- }
- callback && callback(openedApp);
- resolve(openedApp);
- } catch (error) {reject(error)}
- }, 2000);
- };
- openedApp.$app.one('updateapp.apps updatepage.app', tryUpdateForm);
- setTimeout(() => openedApp.$app.off('updateapp.apps', tryUpdateForm), 5000);
- });
- }
- function getPromptFormConfig(fields, extraConfig)
- {
- if(!Array.isArray(fields) || !fields.length) return;
- const typeMap = {radio: 'picker', checkbox: 'multiPicker', text: 'input'};
- const properties = fields.reduce((properties, field, index) =>
- {
- field.code = `field-${field.id}`;
- properties[field.code] = {
- type : 'string',
- widget : typeMap[field.type] || field.type,
- title : field.name,
- placeholder: field.placeholder,
- order : index,
- required : field.required && field.required !== '0',
- props : zui.isNotEmptyString(field.options) ? {items: field.options.split(',').map(x => ({text: x, value: x}))}: undefined
- };
- return properties;
- }, {});
- return $.extend(
- {
- schema: {type: 'object', properties: properties},
- prompt: (data) => fields.map(x => `* ${x.name}: ${data[x.code] || ''}`).join('\n')
- }, extraConfig);
- }
- window.executeZentaoPrompt = async function(info, testingMode)
- {
- testingMode = testingMode && testingMode !== '0';
- const zaiPanel = await checkZAIPanel(true);
- if(!zaiPanel) return;
- const langData = zaiPanel.options.langData || {};
- const noTargetForm = !info.targetForm || info.targetForm === 'empty.empty';
- const toolName = `zentao_tool_${info.promptID}`;
- const agentTool = noTargetForm ? null : {
- name : toolName,
- displayName: info.name,
- description: info.name,
- parameters :
- {
- type: 'object',
- properties:
- {
- data: info.schema,
- title: {type: 'string', description: langData.promptResultTitle},
- summary: {type: 'string', description: langData.agentResultSummary},
- },
- required: ['data', 'summary'],
- },
- };
- const tools = noTargetForm ? [] : [{
- ...agentTool,
- fn: (response) => {
- const result = response.data;
- const targetForm = info.targetForm;
- if(!targetForm) return {result: result};
- const taskResult =
- {
- agentID : info.promptID,
- id : `zentao-agent-result-${info.promptID}`,
- tool : agentTool,
- title : response.title,
- result : response,
- formLocation : info.formLocation,
- targetFormName: info.targetFormName,
- targetForm : info.targetForm,
- objectID : info.objectID,
- objectType : info.objectType,
- objectData : info.objectData,
- objectProps : info.dataPropNames,
- actions: info.promptAudit ? [{
- text : langData.goTesting,
- url : $.createLink('ai', 'promptAudit', `promptId=${info.promptID}&objectId=${info.objectID || 0}`),
- type : 'primary-pale',
- 'data-toggle': 'modal',
- }] : [],
- };
- const message =
- {
- role: 'user',
- content: [response.title, zui.formatString(langData.processedDataResult, {data: JSON.stringify(result)}), response.summary, zui.formatString(langData.promptResultReturn, {formName: info.targetFormName})].join('\n\n'),
- custom_data: {taskResults: [taskResult], asRole: 'assistant'}
- };
- return {message: message};
- },
- }];
- const klibs = (info.knowledgeLib ? info.knowledgeLib.split(',') : []).filter(Boolean).map(x => `zentao:${x}`);
- const formConfig = getPromptFormConfig(info.fields, info.formConfig);
- const popupOptions = {
- id : 'zentao-prompt-popoup',
- viewType : 'chat',
- width : info.content ? 800 : 600,
- postMessage: {content: [{role: 'user', content: info.purpose, custom_data: {invisible: true}}]},
- creatingChat: {
- title : info.name,
- type : 'agent',
- model : info.model,
- tools : tools,
- prompt : [info.role, zui.formatString(langData.processDataPrefix, {data: info.dataPrompt}), noTargetForm ? null : zui.formatString(langData.promptExtraLimit, {toolName: toolName})].filter(Boolean).join('\n\n'),
- form : formConfig,
- memories : klibs.length ? [{collections: klibs}] : undefined,
- },
- };
- zaiPanel.openPopup(popupOptions);
- };
- window.openAITaskPopup = async function(taskID)
- {
- const zaiPanel = await checkZAIPanel(true);
- if(!zaiPanel) return;
- const popupOptions = {
- id : 'zentao-task-popoup',
- viewType : 'task',
- width : 600,
- chatID : `task-${taskID}`,
- };
- zaiPanel.openPopup(popupOptions);
- };
- window.callZentaoAgent = async function(agentID, objectID)
- {
- const res = await $.ajax({url: $.createLink('ai', 'promptExecute', `promptId=${agentID}&objectId=${objectID}`), 'dataType': 'json'});
- if(!res || res.result !== 'success' || !res.callback) return;
- return executeZentaoPrompt(res.callback.params[0], res.callback.params[1]);
- };
- /* 加载数字员工列表,并注册菜单 */
- function loadAndRegisterAiTeammates(lang, plugin)
- {
- plugin.defineContextProvider({
- code: 'ai-teammate',
- title: lang.teammate,
- icon: 'hand-right',
- items: async function()
- {
- const res = await zui.fetchData($.createLink('ai', 'ajaxGetTeammates'));
- if(!res || res.result !== 'success' || !res.data) return [];
- const teammates = res.data;
- if(!teammates.length) return;
- const items = teammates.map((item) => {
- const collections = [];
- if(item.klibs && item.klibs.length)
- {
- item.klibs.forEach(klibID => collections.push(`zentao:${klibID}`));
- }
- const promptParts = [];
- if(item.roleName)
- {
- const prefix = lang.teammatePromptPrefix;
- promptParts.push(`${prefix}${item.roleName}`);
- }
- if(item.desc) promptParts.push(item.desc);
- if(item.klibNames && item.klibNames.length)
- {
- const klibNamesStr = item.klibNames.join(', ');
- const knowledgePrefix = lang.teammateKnowledgePrefix;
- const knowledgeSuffix = lang.teammateKnowledgeSuffix;
- promptParts.push(`${knowledgePrefix}${klibNamesStr}${knowledgeSuffix}`);
- }
- const data = {
- prompt: promptParts.join(', '),
- };
- if(collections.length) data.memory = {collections};
- return {
- code: `zentao-aiteammate-${item.id}`,
- title: item.name,
- hint: item.desc || item.name,
- data,
- llm: item.llm || undefined
- };
- });
- return items;
- },
- });
- }
- function registerZentaoAIPlugin(lang)
- {
- const plugin = zui.AIPlugin.define('zentao', {name: lang.name, icon: 'zentao'});
- plugin.defineContextProvider(
- {
- code: 'currentPage',
- title: lang.currentPage,
- icon: 'globe',
- recommend: true,
- when: () => $.apps,
- data: () => {
- const pageWindow = $.apps.getLastApp().iframe.contentWindow;
- const page$ = pageWindow.$;
- const $mainContainer = page$('#mainContainer');
- const pageContent = $mainContainer.length ? $mainContainer.text() : page$('body').text();
- return {
- prompt: [
- `当前页面标题:${document.title}`,
- "当前页面内容:",
- pageContent
- ].join('\n\n')
- };
- },
- generate: ({userPrompt}) => {
- if(new RegExp(`@(${lang.currentPage})`, 'i').test(userPrompt)) return {};
- }
- });
- const objectIcons = {
- story : 'file-text',
- demand : 'file-text',
- bug : 'bug',
- doc : 'doc',
- design : 'design',
- feedback: 'feedback',
- };
- const zentaoVersion = window.config?.version || '';
- const [_, zentaoEdition] = zentaoVersion.match(/^([a-zA-Z]+)?(\d+\.\d+(\.\d+)?)$/) || [];
- ['story', 'demand', 'bug', 'doc', 'design', 'feedback'].forEach(objectType => {
- if(objectType === 'feedback' && !zentaoEdition) return;
- if(objectType === 'demand' && zentaoEdition !== 'ipd') return;
- plugin.defineContextProvider({
- code: `${objectType}Lib`,
- title: lang[objectType],
- icon: objectIcons[objectType],
- when: ({store}) => !!store.globalMemory,
- data:
- {
- memory: {collections: ['zentao:global'], content_filter: {attrs: {objectType}}},
- },
- generate: ({userPrompt}) => {
- const objectName = lang[objectType] || objectType;
- const matches = [...userPrompt.matchAll(new RegExp(`@(${objectName}${objectType !== objectName ? `|${objectType}` : ''})\\s?#?(\\d+)`, 'gi'))];
- if(matches.length)
- {
- return matches.map(match => {
- const objectID = match[2];
- return {
- code: `${objectType}-${objectID}`,
- recommend: true,
- title: `${objectName} #${objectID}`,
- data: () => ({
- memory:
- {
- collections: ['zentao:global'],
- content_filter: {attrs: {objectKey: `${objectType}-${objectID}`}},
- },
- })
- };
- });
- }
- if(new RegExp(`@(${objectName}${objectType !== objectName ? `|${objectType}` : ''})`, 'i').test(userPrompt)) return {};
- }
- })
- });
- plugin.defineContextProvider(
- {
- code : 'currentDocContent',
- title : lang.currentDocContent,
- icon : 'doc',
- recommend: true,
- hidden : true,
- when: () => {
- if(!window.config) return;
- const pageWindow = $.apps.getLastApp().iframe.contentWindow;
- const page$ = pageWindow.$;
- const editor = page$("[z-use-editor]").zui();
- return !!editor;
- },
- data: async () => {
- const pageWindow = $.apps.getLastApp().iframe.contentWindow;
- const page$ = pageWindow.$;
- const editor = page$("[z-use-editor]").zui();
- const html = await editor.getHtml();
- const text = $(html).text();
- return {prompt: ["当前文档内容:", text].join('\n\n')};
- },
- generate: ({userPrompt}) => {
- if (new RegExp(`@(${lang.currentDocContent})`, 'i').test(userPrompt)) return {};
- }
- });
- plugin.defineContextProvider({
- code : 'globalMemory',
- title: lang.globalMemoryTitle,
- icon : 'book',
- when : context => !!context.store.globalMemory,
- data : {memory: {collections: ['zentao:global']}},
- });
- if(lang.knowledgeLib)
- {
- plugin.defineContextProvider({
- code : 'knowledgeLibs',
- title: lang.knowledgeLib,
- icon : 'book',
- contexts : function()
- {
- return new Promise((resolve) => {
- zui.Modal.open({url: $.createLink('ai', 'selectknowledgelib', `selectedID=&callback=getKnowledgeLibsByForm`), size: 'sm'});
- window.getKnowledgeLibsByForm = function(libs)
- {
- if(!libs.length) return resolve();
- const res = [];
- libs.forEach(item => {
- res.push({
- title: item.name,
- hint: item.name,
- code: `zentao-knowledgeLib-${item.id}`,
- data: {
- memory: {collections: [`zentao:${item.id}`]}
- }
- })
- });
- resolve(res);
- }
- });
- },
- });
- }
- window.enableAITeammate && loadAndRegisterAiTeammates(lang, plugin);
- plugin.defineSuggestion(
- {
- when: ({state}) =>
- {
- const page = state ? state.zentaoPage : null;
- if(!page) return;
- const openedApp = $.apps.openedApps[page.app];
- if(!openedApp) return;
- const aiSuggestions = openedApp.iframe.contentWindow ? openedApp.iframe.contentWindow.aiSuggestions : null;
- return Array.isArray(aiSuggestions) && aiSuggestions.length;
- },
- items: function({state})
- {
- const zentaoPage = state ? state.zentaoPage : null;
- if(!zentaoPage) return;
- const openedApp = $.apps.openedApps[zentaoPage.app];
- if(!openedApp) return;
- const aiSuggestions = openedApp.iframe.contentWindow ? openedApp.iframe.contentWindow.aiSuggestions : null;
- return aiSuggestions.map(suggestion => {
- const {page = '', zentaoAgent, ...others} = suggestion;
- const pageList = page.split(',').filter(Boolean);
- if(pageList && !pageList.some(x => x === zentaoPage.path || x === zentaoPage.currentModule)) return;
- return {
- ...others,
- ...(zentaoAgent ? {action: () => callZentaoAgent(zentaoAgent.agentID, zentaoAgent.objectID)} : {}),
- };
- }).filter(Boolean);
- }
- });
- plugin.defineCallback('onCreateChat', async function(info)
- {
- if(info.isLocal) return;
- const originMemories = info.options.memories;
- if(!originMemories || !originMemories.length) return;
- const knowledgeLibs = {};
- const otherMemories = originMemories.reduce((others, memory) =>
- {
- const ohterCollections = [];
- for(const collection of memory.collections)
- {
- if(collection.startsWith('zentao:'))
- {
- const lib = collection.substr(7);
- const newFilter = $.extend(true, {}, memory.content_filter);
- if(!Object.keys(newFilter).length)
- {
- knowledgeLibs[lib] = {};
- break;;
- }
- const oldFilter = knowledgeLibs[lib] ? knowledgeLibs[lib] : null;
- const finalFilter = $.extend(true, {}, oldFilter, newFilter);
- if(newFilter && newFilter.attrs && oldFilter && oldFilter.attrs)
- {
- Object.keys(oldFilter.attrs).forEach(attrName =>
- {
- const oldAttr = oldFilter.attrs[attrName];
- const newAttr = newFilter.attrs[attrName];
- if(oldAttr === undefined || newAttr === undefined) return;
- const finalAttr = typeof oldAttr === 'object' ? oldAttr : {$in: [oldAttr]};
- if(typeof newAttr === 'object') finalAttr.$in = [...finalAttr.$in, ...newAttr.$in];
- else finalAttr.$in = [...finalAttr.$in, newAttr];
- finalFilter.attrs[attrName] = finalAttr;
- });
- }
- knowledgeLibs[lib] = finalFilter;
- continue;
- }
- ohterCollections.push(collection);
- }
- if(ohterCollections.length) others.push($.extend({}, memory, {collections: ohterCollections}));
- return others;
- }, []);
- if(!Object.keys(knowledgeLibs).length) return;
- return {memories: otherMemories, customData: {ztklibs: knowledgeLibs}};
- });
- plugin.defineCallback('onPostMessage', async function(info)
- {
- if(!info.postingMessages || !info.postingMessages.length) return;
- if(!info.chat.custom_data || !info.chat.custom_data.ztklibs) return;
- const userPrompts = [];
- const systemPrompts = [];
- info.postingMessages.forEach(x =>
- {
- if(x.role === 'user') userPrompts.push(x.content);
- else if(x.role === 'system') systemPrompts.push(x.content);
- });
- let searchPrompt = userPrompts.filter(Boolean).join('\n').trim();
- if(!searchPrompt.length) searchPrompt = systemPrompts.filter(Boolean).join('\n').trim();
- if(!searchPrompt.length) return;
- info.updateState(lang.searchingKLibs);
- const ztklibs = info.chat.custom_data.ztklibs;
- const ztChunks = info.chat.$local.ztChunks || {};
- const [response] = await $.ajaxSubmit(
- {
- url: $.createLink('zai', 'ajaxSearchKnowledges'),
- data: {userPrompt: searchPrompt, filters: JSON.stringify(ztklibs)}
- });
- if(response && response.result === 'success' && response.data && Array.isArray(response.data) && response.data.length)
- {
- const newPropms = [];
- const newRefs = [];
- const refKeys = new Set();
- response.data.forEach(item =>
- {
- if(ztChunks[item.id]) return;
- ztChunks[item.id] = 1;
- newPropms.push(item.content);
- if(refKeys.has(item.key)) return;
- const itemAttrs = item.attrs || {};
- newRefs.push({key: item.key, name: itemAttrs.objectTitle || item.knowledgeTitle, type: itemAttrs.objectType || 'knowledge', id: itemAttrs.objectID || item.knowledgeID})
- refKeys.add(item.key);
- });
- info.chat.$local.ztChunks = ztChunks;
- return {systemPrompt: newPropms.filter(Boolean).join('\n\n'), refs: newRefs};
- }
- });
- }
- /* Bind AI commands in app when app is loaded, example:
- $(document).on('loadapp.apps updateapp.apps', (e, args) =>
- {
- const win = (e.type === 'updateapp' ? $.apps.openedApps[args[0]] : args).iframe.contentWindow;
- bindAICommandsInApp(win, 1000);
- });*/
- function bindAICommandsInApp(win, delay)
- {
- if(!win || !win.zui || win._bindedAICommands !== undefined) return;
- const panel = win.zui.AIPanel.shared;
- if(!panel) return;
- if(win._bindedAICommands) clearTimeout(win._bindedAICommands);
- win._bindedAICommands = setTimeout(() =>
- {
- win.zui.bindCommands(win.document.body,
- {
- commands: {},
- scope: panel.commandScope,
- onCommand: panel.executeCommand.bind(panel)
- });
- win._bindedAICommands = 0;
- }, delay || 0);
- }
- $(() =>
- {
- if(getZentaoPageType() !== 'home') return bindAICommandsInApp(window);
- const zentaoConfig = window.config
- if(!zentaoConfig || zentaoConfig.currentModule !== 'index' || zentaoConfig.currentMethod !== 'index') return;
- const zaiConfig = window.zai || window.top.zai;
- const isOpenVersion = /^\d/.test(config.version || $('#zuiCSS').attr('href').split('?v=').pop());
- if(zaiConfig)
- {
- registerZentaoAIPlugin(zaiLang);
- let userAvatarProps;
- const getAvatar = (type, info) =>
- {
- if(type === 'role' && info.role === 'user')
- {
- if(userAvatarProps) return userAvatarProps;
- const $avatar = $.apps.getLastApp().iframe?.contentWindow.$('#userMenu-toggle>.avatar');
- if($avatar?.length)
- {
- userAvatarProps =
- {
- text : $avatar.find('.avatar-text').text(),
- code : window.config.account,
- src : $avatar.find('img').attr('src'),
- icon : undefined,
- background: $avatar.css('backgroundColor'),
- foreColor : $avatar.css('color'),
- };
- }
- return userAvatarProps;
- }
- if(type === 'chat' && info.chat.teammate)
- {
- const teammate = zaiConfig.teammateMap[info.chat.teammate] || {id: info.chat.teammate, name: info.chat.teammate};
- return {src: teammate.avatar, size: 24, code: teammate.id};
- }
- };
- const aiStore = zui.ZAIStore.createFromZentao($.extend({getAvatar: getAvatar}, zaiConfig));
- if(!aiStore) return
- zui.AIPanel.init(
- {
- store : aiStore,
- position : {bottom: +window.config.debug > 4 ? 56 : 40, right: 16},
- maximizedPosition: {left: 'calc(var(--zt-menu-width) + 4px)', top: 4, bottom: 'calc(var(--zt-apps-bar-height) + 4px)', right: 16},
- langData : zaiLang,
- getErrorContent: (error) =>
- {
- let html = '';
- if(error.type === 'unauthorized' && zaiLang.unauthorizedError) html = zui.formatString(zaiLang.unauthorizedError, {zaiConfigUrl: $.createLink('zai', 'setting')})
- else if(error.type === 'configNotValid' && zaiLang.zaiConfigNotValid) html = zui.formatString(zaiLang.zaiConfigNotValid, {zaiConfigUrl: $.createLink('zai', 'setting')})
- 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>`};
- return error.message;
- },
- tabs: !window.enableAITeammate ? undefined : [
- {key: 'RECENTS', title: zaiLang.recentChats, chatTypes: ['chat']},
- {key: 'TASKS', title: zaiLang.aiTeammateTasks, chatsFetcher: (store) => store.getTasks(), onCreate: false, searchBox: {placeholder: zaiLang.searchTasks}},
- ]
- });
- $(document).on('updatepage.app openapp.apps openOldPage.apps', (e, args) =>
- {
- const panel = zui.AIPanel.shared;
- if(!panel) return;
- const pageInfo = e.type === 'openapp' ? args[0]?.getPageInfo?.() : args[0];
- if(!pageInfo || !pageInfo.id) return
- panel.reactions.trigger(
- e.type === 'openapp' ? 'openPage' : 'updatepage',
- {page: pageInfo},
- {zentaoPage: pageInfo, event: e}
- );
- const lastPageID = panel.reactions.state.lastPageID;
- if(lastPageID !== pageInfo.id)
- {
- panel.reactions.trigger(
- 'openNewPage',
- {page: pageInfo},
- {zentaoLastPageID: pageInfo.id, event: e},
- {lifeTime: 5000}
- );
- }
- }
- );
- aiStore.isOK().then(isOK => {window.isZaiOK = isOK;});
- }
- /* Bind AI commands in app when app is loaded. */
- $(document).on('loadapp.apps', (_, args) =>
- {
- setTimeout(() => bindAICommandsInApp(args[0].iframe.contentWindow), 1000);
- });
- });
|