diff --git a/.gitignore b/.gitignore index dd4fdeb..b88dc49 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ plugins.json itchat.pkl *.log logs/ +workspace +config.yaml user_datas.pkl chatgpt_tool_hub/ plugins/**/ @@ -31,4 +33,5 @@ plugins/banwords/lib/__pycache__ !plugins/role !plugins/keyword !plugins/linkai +!plugins/agent client_config.json diff --git a/channel/web/chat.html b/channel/web/chat.html index 1b1111c..3670640 100644 --- a/channel/web/chat.html +++ b/channel/web/chat.html @@ -6,7 +6,7 @@ AI Assistant - + @@ -14,6 +14,7 @@ + @@ -728,6 +782,19 @@ let userId = 'user_' + Math.random().toString(36).substring(2, 10); let currentSessionId = 'default_session'; // 使用固定会话ID + // 添加一个变量来跟踪输入法状态 + let isComposing = false; + + // 监听输入法组合状态开始 + input.addEventListener('compositionstart', function() { + isComposing = true; + }); + + // 监听输入法组合状态结束 + input.addEventListener('compositionend', function() { + isComposing = false; + }); + // 自动调整文本区域高度 input.addEventListener('input', function() { this.style.height = 'auto'; @@ -780,15 +847,19 @@ event.preventDefault(); } - // Enter 键发送消息 - else if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey) { + // Enter 键发送消息,但只在不是输入法组合状态时 + else if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !isComposing) { sendMessage(); event.preventDefault(); } }); + // 在发送消息函数前添加调试代码 + console.log('Axios loaded:', typeof axios !== 'undefined'); + // 发送消息函数 function sendMessage() { + console.log('Send message function called'); const userMessage = input.value.trim(); if (userMessage) { // 隐藏欢迎屏幕 @@ -811,33 +882,26 @@ sendButton.disabled = true; // 发送到服务器并等待响应 - fetch('/message', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ + axios({ + method: 'post', + url: '/message', + data: { user_id: userId, message: userMessage, timestamp: timestamp.toISOString(), session_id: currentSessionId - }) + }, + timeout: 120000 // 120秒超时 }) .then(response => { - if (!response.ok) { - throw new Error('Failed to send message'); - } - return response.json(); - }) - .then(data => { // 移除加载消息 if (loadingContainer.parentNode) { messagesDiv.removeChild(loadingContainer); } // 添加AI回复 - if (data.reply) { - addBotMessage(data.reply, new Date()); + if (response.data.reply) { + addBotMessage(response.data.reply, new Date()); } }) .catch(error => { @@ -847,7 +911,11 @@ messagesDiv.removeChild(loadingContainer); } // 显示错误消息 - addBotMessage("抱歉,发生了错误,请稍后再试。", new Date()); + if (error.code === 'ECONNABORTED') { + addBotMessage("请求超时,请再试一次吧。", new Date()); + } else { + addBotMessage("抱歉,发生了错误,请稍后再试。", new Date()); + } }); } } @@ -882,35 +950,30 @@ return botContainer; } - // 格式化消息内容(处理Markdown和代码高亮) + // 替换 formatMessage 函数,使用 markdown-it 替代 marked function formatMessage(content) { - // 配置 marked 以使用 highlight.js - marked.setOptions({ - highlight: function(code, language) { - if (language && hljs.getLanguage(language)) { - try { - return hljs.highlight(code, { language: language }).value; - } catch (e) { - console.error('Error highlighting code:', e); - return code; - } - } - return code; - }, - breaks: true, // 启用换行符转换为
- gfm: true, // 启用 GitHub 风格的 Markdown - headerIds: true, // 为标题生成ID - mangle: false, // 不转义内联HTML - sanitize: false, // 不净化输出 - smartLists: true, // 使用更智能的列表行为 - smartypants: false, // 不使用更智能的标点符号 - xhtml: false // 不使用自闭合标签 - }); - try { - // 使用 marked 解析 Markdown - const parsed = marked.parse(content); - return parsed; + // 初始化 markdown-it 实例 + const md = window.markdownit({ + html: false, // 禁用 HTML 标签 + xhtmlOut: false, // 使用 '/' 关闭单标签 + breaks: true, // 将换行符转换为
+ linkify: true, // 自动将 URL 转换为链接 + typographer: true, // 启用一些语言中性的替换和引号美化 + highlight: function(str, lang) { + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(str, { language: lang }).value; + } catch (e) { + console.error('Error highlighting code:', e); + } + } + return hljs.highlightAuto(str).value; + } + }); + + // 渲染 Markdown + return md.render(content); } catch (e) { console.error('Error parsing markdown:', e); // 如果解析失败,至少确保换行符正确显示 @@ -918,17 +981,30 @@ } } - // 添加消息后应用代码高亮 + // 更新 applyHighlighting 函数 function applyHighlighting() { try { document.querySelectorAll('pre code').forEach((block) => { - // 手动应用高亮 - const language = block.className.replace('language-', ''); + // 确保代码块有正确的类 + if (!block.classList.contains('hljs')) { + block.classList.add('hljs'); + } + + // 尝试获取语言 + let language = ''; + block.classList.forEach(cls => { + if (cls.startsWith('language-')) { + language = cls.replace('language-', ''); + } + }); + + // 应用高亮 if (language && hljs.getLanguage(language)) { try { hljs.highlightBlock(block); } catch (e) { - console.error('Error highlighting block:', e); + console.error('Error highlighting specific language:', e); + hljs.highlightAuto(block); } } else { hljs.highlightAuto(block); diff --git a/channel/web/static/axios.min.js b/channel/web/static/axios.min.js new file mode 100644 index 0000000..79aa153 --- /dev/null +++ b/channel/web/static/axios.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).axios=t()}(this,(function(){"use strict";function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;n2&&void 0!==arguments[2]?arguments[2]:{},s=i.allOwnKeys,a=void 0!==s&&s;if(null!=t)if("object"!==e(t)&&(t=[t]),l(t))for(r=0,o=t.length;r3&&void 0!==arguments[3]?arguments[3]:{},i=r.allOwnKeys;return S(t,(function(t,r){n&&m(t)?e[r]=o(t,n):e[r]=t}),{allOwnKeys:i}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,n,r){e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:function(e,t,n,r){var o,i,s,u={};if(t=t||{},null==e)return t;do{for(i=(o=Object.getOwnPropertyNames(e)).length;i-- >0;)s=o[i],r&&!r(s,e,t)||u[s]||(t[s]=e[s],u[s]=!0);e=!1!==n&&a(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:u,kindOfTest:c,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;if(l(e))return e;var t=e.length;if(!v(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},forEachEntry:function(e,t){for(var n,r=(e&&e[Symbol.iterator]).call(e);(n=r.next())&&!n.done;){var o=n.value;t.call(e,o[0],o[1])}},matchAll:function(e,t){for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:T,hasOwnProperty:x,hasOwnProp:x,reduceDescriptors:N,freezeMethods:function(e){N(e,(function(t,n){var r=e[n];m(r)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not read-only method '"+n+"'")}))}))},toObjectSet:function(e,t){var n={},r=function(e){e.forEach((function(e){n[e]=!0}))};return l(e)?r(e):r(String(e).split(t)),n},toCamelCase:function(e){return e.toLowerCase().replace(/[_-\s]([a-z\d])(\w*)/g,(function(e,t,n){return t.toUpperCase()+n}))},noop:function(){},toFiniteNumber:function(e,t){return e=+e,Number.isFinite(e)?e:t}};function _(e,t,n,r,o){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),o&&(this.response=o)}P.inherits(_,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code,status:this.response&&this.response.status?this.response.status:null}}});var B=_.prototype,D={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach((function(e){D[e]={value:e}})),Object.defineProperties(_,D),Object.defineProperty(B,"isAxiosError",{value:!0}),_.from=function(e,t,n,r,o,i){var s=Object.create(B);return P.toFlatObject(e,s,(function(e){return e!==Error.prototype}),(function(e){return"isAxiosError"!==e})),_.call(s,e.message,t,n,r,o),s.cause=e,s.name=e.name,i&&Object.assign(s,i),s};var F="object"==("undefined"==typeof self?"undefined":e(self))?self.FormData:window.FormData;function U(e){return P.isPlainObject(e)||P.isArray(e)}function k(e){return P.endsWith(e,"[]")?e.slice(0,-2):e}function L(e,t,n){return e?e.concat(t).map((function(e,t){return e=k(e),!n&&t?"["+e+"]":e})).join(n?".":""):t}var q=P.toFlatObject(P,{},null,(function(e){return/^is[A-Z]/.test(e)}));function z(t,n,r){if(!P.isObject(t))throw new TypeError("target must be an object");n=n||new(F||FormData);var o,i=(r=P.toFlatObject(r,{metaTokens:!0,dots:!1,indexes:!1},!1,(function(e,t){return!P.isUndefined(t[e])}))).metaTokens,s=r.visitor||l,a=r.dots,u=r.indexes,c=(r.Blob||"undefined"!=typeof Blob&&Blob)&&((o=n)&&P.isFunction(o.append)&&"FormData"===o[Symbol.toStringTag]&&o[Symbol.iterator]);if(!P.isFunction(s))throw new TypeError("visitor must be a function");function f(e){if(null===e)return"";if(P.isDate(e))return e.toISOString();if(!c&&P.isBlob(e))throw new _("Blob is not supported. Use a Buffer instead.");return P.isArrayBuffer(e)||P.isTypedArray(e)?c&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function l(t,r,o){var s=t;if(t&&!o&&"object"===e(t))if(P.endsWith(r,"{}"))r=i?r:r.slice(0,-2),t=JSON.stringify(t);else if(P.isArray(t)&&function(e){return P.isArray(e)&&!e.some(U)}(t)||P.isFileList(t)||P.endsWith(r,"[]")&&(s=P.toArray(t)))return r=k(r),s.forEach((function(e,t){!P.isUndefined(e)&&n.append(!0===u?L([r],t,a):null===u?r:r+"[]",f(e))})),!1;return!!U(t)||(n.append(L(o,r,a),f(t)),!1)}var d=[],h=Object.assign(q,{defaultVisitor:l,convertValue:f,isVisitable:U});if(!P.isObject(t))throw new TypeError("data must be an object");return function e(t,r){if(!P.isUndefined(t)){if(-1!==d.indexOf(t))throw Error("Circular reference detected in "+r.join("."));d.push(t),P.forEach(t,(function(t,o){!0===(!P.isUndefined(t)&&s.call(n,t,P.isString(o)?o.trim():o,r,h))&&e(t,r?r.concat(o):[o])})),d.pop()}}(t),n}function I(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,(function(e){return t[e]}))}function M(e,t){this._pairs=[],e&&z(e,this,t)}var J=M.prototype;function H(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function V(e,t,n){if(!t)return e;var r=e.indexOf("#");-1!==r&&(e=e.slice(0,r));var o=n&&n.encode||H,i=P.isURLSearchParams(t)?t.toString():new M(t,n).toString(o);return i&&(e+=(-1===e.indexOf("?")?"?":"&")+i),e}J.append=function(e,t){this._pairs.push([e,t])},J.toString=function(e){var t=e?function(t){return e.call(this,t,I)}:I;return this._pairs.map((function(e){return t(e[0])+"="+t(e[1])}),"").join("&")};var W,K=function(){function e(){t(this,e),this.handlers=[]}return r(e,[{key:"use",value:function(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!n&&n.synchronous,runWhen:n?n.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){P.forEach(this.handlers,(function(t){null!==t&&e(t)}))}}]),e}(),X={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},$="undefined"!=typeof URLSearchParams?URLSearchParams:M,Q=FormData,G=("undefined"==typeof navigator||"ReactNative"!==(W=navigator.product)&&"NativeScript"!==W&&"NS"!==W)&&"undefined"!=typeof window&&"undefined"!=typeof document,Y={isBrowser:!0,classes:{URLSearchParams:$,FormData:Q,Blob:Blob},isStandardBrowserEnv:G,protocols:["http","https","file","blob","url","data"]};function Z(e){function t(e,n,r,o){var i=e[o++],s=Number.isFinite(+i),a=o>=e.length;return i=!i&&P.isArray(r)?r.length:i,a?(P.hasOwnProp(r,i)?r[i]=[r[i],n]:r[i]=n,!s):(r[i]&&P.isObject(r[i])||(r[i]=[]),t(e,n,r[i],o)&&P.isArray(r[i])&&(r[i]=function(e){var t,n,r={},o=Object.keys(e),i=o.length;for(t=0;t0;)if(t===(n=r[o]).toLowerCase())return n;return null}function le(e,t){e&&this.set(e),this[se]=t||null}function de(e,t){var n=0,r=function(e,t){e=e||10;var n,r=new Array(e),o=new Array(e),i=0,s=0;return t=void 0!==t?t:1e3,function(a){var u=Date.now(),c=o[s];n||(n=u),r[i]=a,o[i]=u;for(var f=s,l=0;f!==i;)l+=r[f++],f%=e;if((i=(i+1)%e)===s&&(s=(s+1)%e),!(u-n-1,i=P.isObject(e);if(i&&P.isHTMLForm(e)&&(e=new FormData(e)),P.isFormData(e))return o&&o?JSON.stringify(Z(e)):e;if(P.isArrayBuffer(e)||P.isBuffer(e)||P.isStream(e)||P.isFile(e)||P.isBlob(e))return e;if(P.isArrayBufferView(e))return e.buffer;if(P.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(i){if(r.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return z(e,new Y.classes.URLSearchParams,Object.assign({visitor:function(e,t,n,r){return Y.isNode&&P.isBuffer(e)?(this.append(t,e.toString("base64")),!1):r.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((n=P.isFileList(e))||r.indexOf("multipart/form-data")>-1){var s=this.env&&this.env.FormData;return z(n?{"files[]":e}:e,s&&new s,this.formSerializer)}}return i||o?(t.setContentType("application/json",!1),function(e,t,n){if(P.isString(e))try{return(t||JSON.parse)(e),P.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(n||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||be.transitional,n=t&&t.forcedJSONParsing,r="json"===this.responseType;if(e&&P.isString(e)&&(n&&!this.responseType||r)){var o=!(t&&t.silentJSONParsing)&&r;try{return JSON.parse(e)}catch(e){if(o){if("SyntaxError"===e.name)throw _.from(e,_.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Y.classes.FormData,Blob:Y.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*"}}};function ge(e,t){var n=this||be,r=t||n,o=le.from(r.headers),i=r.data;return P.forEach(e,(function(e){i=e.call(n,i,o.normalize(),t?t.status:void 0)})),o.normalize(),i}function Ee(e){return!(!e||!e.__CANCEL__)}function we(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new re}function Oe(e){return we(e),e.headers=le.from(e.headers),e.data=ge.call(e,e.transformRequest),(e.adapter||be.adapter)(e).then((function(t){return we(e),t.data=ge.call(e,e.transformResponse,t),t.headers=le.from(t.headers),t}),(function(t){return Ee(t)||(we(e),t&&t.response&&(t.response.data=ge.call(e,e.transformResponse,t.response),t.response.headers=le.from(t.response.headers))),Promise.reject(t)}))}function Re(e,t){t=t||{};var n={};function r(e,t){return P.isPlainObject(e)&&P.isPlainObject(t)?P.merge(e,t):P.isPlainObject(t)?P.merge({},t):P.isArray(t)?t.slice():t}function o(n){return P.isUndefined(t[n])?P.isUndefined(e[n])?void 0:r(void 0,e[n]):r(e[n],t[n])}function i(e){if(!P.isUndefined(t[e]))return r(void 0,t[e])}function s(n){return P.isUndefined(t[n])?P.isUndefined(e[n])?void 0:r(void 0,e[n]):r(void 0,t[n])}function a(n){return n in t?r(e[n],t[n]):n in e?r(void 0,e[n]):void 0}var u={url:i,method:i,data:i,baseURL:s,transformRequest:s,transformResponse:s,paramsSerializer:s,timeout:s,timeoutMessage:s,withCredentials:s,adapter:s,responseType:s,xsrfCookieName:s,xsrfHeaderName:s,onUploadProgress:s,onDownloadProgress:s,decompress:s,maxContentLength:s,maxBodyLength:s,beforeRedirect:s,transport:s,httpAgent:s,httpsAgent:s,cancelToken:s,socketPath:s,responseEncoding:s,validateStatus:a};return P.forEach(Object.keys(e).concat(Object.keys(t)),(function(e){var t=u[e]||o,r=t(e);P.isUndefined(r)&&t!==a||(n[e]=r)})),n}P.forEach(["delete","get","head"],(function(e){be.headers[e]={}})),P.forEach(["post","put","patch"],(function(e){be.headers[e]=P.merge(ve)}));var Se="1.1.2",Ae={};["object","boolean","number","function","string","symbol"].forEach((function(t,n){Ae[t]=function(r){return e(r)===t||"a"+(n<1?"n ":" ")+t}}));var je={};Ae.transitional=function(e,t,n){function r(e,t){return"[Axios v1.1.2] Transitional option '"+e+"'"+t+(n?". "+n:"")}return function(n,o,i){if(!1===e)throw new _(r(o," has been removed"+(t?" in "+t:"")),_.ERR_DEPRECATED);return t&&!je[o]&&(je[o]=!0,console.warn(r(o," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(n,o,i)}};var Te={assertOptions:function(t,n,r){if("object"!==e(t))throw new _("options must be an object",_.ERR_BAD_OPTION_VALUE);for(var o=Object.keys(t),i=o.length;i-- >0;){var s=o[i],a=n[s];if(a){var u=t[s],c=void 0===u||a(u,s,t);if(!0!==c)throw new _("option "+s+" must be "+c,_.ERR_BAD_OPTION_VALUE)}else if(!0!==r)throw new _("Unknown option "+s,_.ERR_BAD_OPTION)}},validators:Ae},xe=Te.validators,Ce=function(){function e(n){t(this,e),this.defaults=n,this.interceptors={request:new K,response:new K}}return r(e,[{key:"request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var n=(t=Re(this.defaults,t)).transitional;void 0!==n&&Te.assertOptions(n,{silentJSONParsing:xe.transitional(xe.boolean),forcedJSONParsing:xe.transitional(xe.boolean),clarifyTimeoutError:xe.transitional(xe.boolean)},!1),t.method=(t.method||this.defaults.method||"get").toLowerCase();var r=t.headers&&P.merge(t.headers.common,t.headers[t.method]);r&&P.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete t.headers[e]})),t.headers=new le(t.headers,r);var o=[],i=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(i=i&&e.synchronous,o.unshift(e.fulfilled,e.rejected))}));var s,a=[];this.interceptors.response.forEach((function(e){a.push(e.fulfilled,e.rejected)}));var u,c=0;if(!i){var f=[Oe.bind(this),void 0];for(f.unshift.apply(f,o),f.push.apply(f,a),u=f.length,s=Promise.resolve(t);c0;)o._listeners[t](e);o._listeners=null}})),this.promise.then=function(e){var t,n=new Promise((function(e){o.subscribe(e),t=e})).then(e);return n.cancel=function(){o.unsubscribe(t)},n},n((function(e,t,n){o.reason||(o.reason=new re(e,t,n),r(o.reason))}))}return r(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}}],[{key:"source",value:function(){var t;return{token:new e((function(e){t=e})),cancel:t}}}]),e}();var Pe=function e(t){var n=new Ce(t),r=o(Ce.prototype.request,n);return P.extend(r,Ce.prototype,n,{allOwnKeys:!0}),P.extend(r,n,null,{allOwnKeys:!0}),r.create=function(n){return e(Re(t,n))},r}(be);return Pe.Axios=Ce,Pe.CanceledError=re,Pe.CancelToken=Ne,Pe.isCancel=Ee,Pe.VERSION=Se,Pe.toFormData=z,Pe.AxiosError=_,Pe.Cancel=Pe.CanceledError,Pe.all=function(e){return Promise.all(e)},Pe.spread=function(e){return function(t){return e.apply(null,t)}},Pe.isAxiosError=function(e){return P.isObject(e)&&!0===e.isAxiosError},Pe.formToJSON=function(e){return Z(P.isHTMLForm(e)?new FormData(e):e)},Pe})); +//# sourceMappingURL=axios.min.js.map diff --git a/channel/web/web_channel.py b/channel/web/web_channel.py index aac4f68..c62598a 100644 --- a/channel/web/web_channel.py +++ b/channel/web/web_channel.py @@ -126,7 +126,7 @@ class WebChannel(ChatChannel): # 等待响应,最多等待30秒 try: - response = response_queue.get(timeout=30) + response = response_queue.get(timeout=120) return json.dumps({"status": "success", "reply": response["content"]}) except Empty: return json.dumps({"status": "error", "message": "Response timeout"}) @@ -151,13 +151,27 @@ class WebChannel(ChatChannel): logger.info(f"Created static directory: {static_dir}") urls = ( + '/', 'RootHandler', # 添加根路径处理器 '/message', 'MessageHandler', '/chat', 'ChatHandler', '/assets/(.*)', 'AssetsHandler', # 匹配 /assets/任何路径 ) port = conf().get("web_port", 9899) app = web.application(urls, globals(), autoreload=False) - web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) + + # 禁用web.py的默认日志输出 + import io + from contextlib import redirect_stdout + + # 临时重定向标准输出,捕获web.py的启动消息 + with redirect_stdout(io.StringIO()): + web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) + + +class RootHandler: + def GET(self): + # 重定向到/chat + raise web.seeother('/chat') class MessageHandler: @@ -185,11 +199,6 @@ class AssetsHandler: current_dir = os.path.dirname(os.path.abspath(__file__)) static_dir = os.path.join(current_dir, 'static') - # 打印调试信息 - logger.info(f"Current directory: {current_dir}") - logger.info(f"Static directory: {static_dir}") - logger.info(f"Requested file: {file_path}") - full_path = os.path.normpath(os.path.join(static_dir, file_path)) # 安全检查:确保请求的文件在static目录内 diff --git a/plugins/agent/README.md b/plugins/agent/README.md new file mode 100644 index 0000000..15e0e77 --- /dev/null +++ b/plugins/agent/README.md @@ -0,0 +1,77 @@ +# AgentMesh Plugin + +这个插件集成了 AgentMesh 多智能体框架,允许用户通过简单的命令使用多智能体团队来完成各种任务。 + +## 功能介绍 + +AgentMesh 是一个开源的多智能体平台,提供开箱即用的 Agent 开发框架、多 Agent 间的协同策略、任务规划和自主决策能力。通过这个插件,你可以: + +- 使用预配置的智能体团队处理复杂任务 +- 利用多智能体协作能力解决问题 +- 访问各种工具,如搜索引擎、浏览器、文件系统等 + +## 安装 + +1. 确保已安装 AgentMesh SDK: + +```bash +pip install agentmesh-sdk>=0.1.0 +``` + +2. 如需使用浏览器工具,还需安装: + +```bash +pip install browser-use>=0.1.40 +playwright install +``` + +## 配置 + +插件从项目根目录的 `config.yaml` 文件中读取配置。请确保该文件包含正确的团队配置。 + +配置示例: + +```yaml +teams: + general_team: + description: "通用智能体团队,擅长于搜索、研究和执行各种任务" + model: "gpt-4o" + max_steps: 20 + agents: + - name: "通用助手" + description: "全能的通用智能体" + system_prompt: "你是全能的通用智能体,可以帮助用户解决工作、生活、学习上的任何问题,以及使用工具解决各类复杂问题" + tools: ["google_search", "calculator", "current_time"] +``` + +## 使用方法 + +使用 `$agent` 前缀触发插件,支持以下命令: + +- `$agent teams` - 列出可用的团队 +- `$agent use [team_name] [task]` - 使用特定团队执行任务 +- `$agent [task]` - 使用默认团队执行任务 + +### 示例 + +``` +$agent teams +$agent use general_team 帮我分析多智能体技术发展趋势 +$agent 帮我查看当前文件夹路径 +``` + +## 工具支持 + +AgentMesh 支持多种工具,包括但不限于: + +- `calculator`: 数学计算工具 +- `current_time`: 获取当前时间 +- `browser`: 浏览器操作工具 +- `google_search`: 搜索引擎 +- `file_save`: 文件保存工具 +- `terminal`: 终端命令执行工具 + +## 注意事项 + +1. 确保 `config.yaml` 文件中包含正确的团队配置 +2. 如果需要使用浏览器工具,请确保安装了相关依赖 diff --git a/plugins/agent/__init__.py b/plugins/agent/__init__.py new file mode 100644 index 0000000..75642e0 --- /dev/null +++ b/plugins/agent/__init__.py @@ -0,0 +1,3 @@ +from .agent import AgentPlugin + +__all__ = ["AgentPlugin"] \ No newline at end of file diff --git a/plugins/agent/agent.py b/plugins/agent/agent.py new file mode 100644 index 0000000..0532216 --- /dev/null +++ b/plugins/agent/agent.py @@ -0,0 +1,283 @@ +import os +import yaml +from typing import Dict, List, Optional + +from agentmesh import AgentTeam, Agent, LLMModel +from agentmesh.models import ClaudeModel +from agentmesh.tools import ToolManager +from config import conf + +import plugins +from plugins import Plugin, Event, EventContext, EventAction +from bridge.context import ContextType +from bridge.reply import Reply, ReplyType +from common.log import logger + + +@plugins.register( + name="agent", + desc="Use AgentMesh framework to process tasks with multi-agent teams", + version="0.1.0", + author="Saboteur7", + desire_priority=1, +) +class AgentPlugin(Plugin): + """Plugin for integrating AgentMesh framework.""" + + def __init__(self): + super().__init__() + self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context + self.name = "agent" + self.description = "Use AgentMesh framework to process tasks with multi-agent teams" + self.config = self._load_config() + self.tool_manager = ToolManager() + self.tool_manager.load_tools(config_dict=self.config.get("tools")) + logger.info("[agent] inited") + + def _load_config(self) -> Dict: + """Load configuration from config.yaml file.""" + config_path = os.path.join(self.path, "config.yaml") + if not os.path.exists(config_path): + logger.warning(f"Config file not found at {config_path}") + return {} + + with open(config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + + def get_help_text(self, verbose=False, **kwargs): + """Return help message for the agent plugin.""" + help_text = "AgentMesh插件: 使用多智能体团队处理任务和回答问题,支持多种工具和智能体协作能力。" + trigger_prefix = conf().get("plugin_trigger_prefix", "$") + + if not verbose: + return help_text + + teams = self.get_available_teams() + teams_str = ", ".join(teams) if teams else "未配置任何团队" + + help_text += "\n\n使用说明:\n" + help_text += f"{trigger_prefix}agent teams - 列出可用的团队\n" + help_text += f"{trigger_prefix}agent use [team_name] [task] - 使用特定团队执行任务\n" + help_text += f"{trigger_prefix}agent [task] - 使用默认团队执行任务\n\n" + help_text += f"可用团队: {teams_str}\n\n" + help_text += f"示例:\n" + help_text += f"{trigger_prefix}agent use general_team 帮我分析多智能体技术发展趋势\n" + help_text += f"{trigger_prefix}agent 帮我查看当前文件夹路径" + + return help_text + + def get_available_teams(self) -> List[str]: + """Get list of available teams from configuration.""" + teams_config = self.config.get("teams", {}) + return list(teams_config.keys()) + + + def create_team_from_config(self, team_name: str) -> Optional[AgentTeam]: + """Create a team from configuration.""" + # Get teams configuration + teams_config = self.config.get("teams", {}) + + # Check if the specified team exists + if team_name not in teams_config: + logger.error(f"Team '{team_name}' not found in configuration.") + available_teams = list(teams_config.keys()) + logger.info(f"Available teams: {', '.join(available_teams)}") + return None + + # Get team configuration + team_config = teams_config[team_name] + + # Get team's model + team_model_name = team_config.get("model", "gpt-4.1-mini") + team_model = self.create_llm_model(team_model_name) + + # Get team's max_steps (default to 20 if not specified) + team_max_steps = team_config.get("max_steps", 20) + + # Create team with the model + team = AgentTeam( + name=team_name, + description=team_config.get("description", ""), + rule=team_config.get("rule", ""), + model=team_model, + max_steps=team_max_steps + ) + + # Create and add agents to the team + agents_config = team_config.get("agents", []) + for agent_config in agents_config: + # Check if agent has a specific model + if agent_config.get("model"): + agent_model = self.create_llm_model(agent_config.get("model")) + else: + agent_model = team_model + + # Get agent's max_steps + agent_max_steps = agent_config.get("max_steps") + + agent = Agent( + name=agent_config.get("name", ""), + system_prompt=agent_config.get("system_prompt", ""), + model=agent_model, # Use agent's model if specified, otherwise will use team's model + description=agent_config.get("description", ""), + max_steps=agent_max_steps + ) + + # Add tools to the agent if specified + tool_names = agent_config.get("tools", []) + for tool_name in tool_names: + tool = self.tool_manager.create_tool(tool_name) + if tool: + agent.add_tool(tool) + else: + if tool_name == "browser": + logger.warning( + "Tool 'Browser' loaded failed, " + "please install the required dependency with: \n" + "'pip install browser-use>=0.1.40' or 'pip install agentmesh-sdk[full]'\n" + ) + else: + logger.warning(f"Tool '{tool_name}' not found for agent '{agent.name}'\n") + + # Add agent to team + team.add(agent) + + return team + + def on_handle_context(self, e_context: EventContext): + """Handle the message context.""" + if e_context['context'].type != ContextType.TEXT: + return + content = e_context['context'].content + trigger_prefix = conf().get("plugin_trigger_prefix", "$") + + if not content.startswith(f"{trigger_prefix}agent "): + e_context.action = EventAction.CONTINUE + return + + if not self.config: + reply = Reply() + reply.type = ReplyType.ERROR + reply.content = "未找到插件配置,请在 plugins/agent 目录下创建 config.yaml 配置文件,可根据 config-template.yml 模板文件复制" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + # Extract the actual task + task = content[len(f"{trigger_prefix}agent "):].strip() + + # If task is empty, return help message + if not task: + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = self.get_help_text(verbose=True) + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + # Check if task is asking for available teams + if task.lower() in ["teams", "list teams", "show teams"]: + teams = self.get_available_teams() + reply = Reply() + reply.type = ReplyType.TEXT + + if not teams: + reply.content = "未配置任何团队。请检查 config.yaml 文件。" + else: + reply.content = f"可用团队: {', '.join(teams)}" + + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + # Check if task specifies a team + team_name = None + if task.startswith("use "): + parts = task[4:].split(" ", 1) + if len(parts) > 0: + team_name = parts[0] + if len(parts) > 1: + task = parts[1].strip() + else: + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = f"已选择团队 '{team_name}'。请输入您想执行的任务。" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + if not team_name: + team_name = self.config.get("team") + + # If no team specified, use default or first available + if not team_name: + teams = self.configself.get_available_teams() + if not teams: + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = "未配置任何团队。请检查 config.yaml 文件。" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + team_name = teams[0] + + # Create team + team = self.create_team_from_config(team_name) + if not team: + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = f"创建团队 '{team_name}' 失败。请检查配置。" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + # Run the task + try: + logger.info(f"[agent] Running task '{task}' with team '{team_name}', team_model={team.model.model}") + result = team.run_async(task=task) + for agent_result in result: + res_text = f"🤖 {agent_result.get('agent_name')}\n\n{agent_result.get('final_answer')}" + _send_text(e_context, content=res_text) + + reply = Reply() + reply.type = ReplyType.TEXT + reply.content = "" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + + except Exception as e: + logger.exception(f"Error running task with team '{team_name}'") + + reply = Reply() + reply.type = ReplyType.ERROR + reply.content = f"执行任务时出错: {str(e)}" + e_context['reply'] = reply + e_context.action = EventAction.BREAK_PASS + return + + def create_llm_model(self, model_name) -> LLMModel: + if conf().get("use_linkai"): + api_base = "https://api.link-ai.tech/v1" + api_key = conf().get("linkai_api_key") + elif model_name.startswith(("gpt", "text-davinci", "o1", "o3")): + api_base = conf().get("open_ai_api_base") or "https://api.openai.com/v1" + api_key = conf().get("open_ai_api_key") + elif model_name.startswith("claude"): + return ClaudeModel(model=model_name, api_key=conf().get("claude_api_key")) + elif model_name.startswith("moonshot"): + api_base = "https://api.moonshot.cn/v1" + api_key = conf().get("moonshot_api_key") + elif model_name.startswith("qwen"): + api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1" + api_key = conf().get("dashscope_api_key") + else: + api_base = conf().get("open_ai_api_base") or "https://api.openai.com/v1" + api_key = conf().get("open_ai_api_key") + + llm_model = LLMModel(model=model_name, api_key=api_key, api_base=api_base) + return llm_model + + +def _send_text(e_context: EventContext, content: str): + reply = Reply(ReplyType.TEXT, content) + channel = e_context["channel"] + channel.send(reply, e_context["context"]) diff --git a/plugins/agent/config-template.yaml b/plugins/agent/config-template.yaml new file mode 100644 index 0000000..3fd2b43 --- /dev/null +++ b/plugins/agent/config-template.yaml @@ -0,0 +1,49 @@ +# 选中的Agent Team +team: general_team + +tools: + google_search: + # get your apikey from https://serper.dev/ + api_key: "e7a21d840d6bb0ba832d850bb5aa4dee337415c4" + +# Team config +teams: + general_team: + model: "qwen-plus" + description: "A versatile research and information agent team" + max_steps: 5 + agents: + - name: "通用智能助手" + description: "Universal assistant specializing in research, information synthesis, and task execution" + system_prompt: "You are a versatile assistant who answers questions and completes tasks using available tools. Reply in a clearly structured, attractive and easy to read format." + tools: + - time + - calculator + - google_search + - browser + - terminal + + software_team: + model: "gpt-4.1-mini" + description: "A software development team with product manager, developer and tester." + rule: "A normal R&D process should be that Product Manager writes PRD, Developer writes code based on PRD, and Finally, Tester performs testing." + max_steps: 10 + agents: + - name: "Product-Manager" + description: "Responsible for product requirements and documentation" + system_prompt: "You are an experienced product manager who creates concise PRDs, focusing on user needs and feature specifications. You always format your responses in Markdown." + tools: + - time + - file_save + - name: "Developer" + description: "Implements code based on PRD" + system_prompt: "You are a skilled developer. When developing web application, you creates single-page website based on user needs, you deliver HTML files with embedded JavaScript and CSS that are visually appealing, responsive, and user-friendly, featuring a grand layout and beautiful background. The HTML, CSS, and JavaScript code should be well-structured and effectively organized." + tools: + - file_save + - name: "Tester" + description: "Tests code and verifies functionality" + system_prompt: "You are a tester who validates code against requirements. For HTML applications, use browser tools to test functionality. For Python or other client-side applications, use the terminal tool to run and test. You only need to test a few core cases." + tools: + - file_save + - browser + - terminal diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index fe35879..ecf519c 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -155,7 +155,7 @@ def get_help_text(isadmin, isgroup): for plugin in plugins: if plugins[plugin].enabled and not plugins[plugin].hidden: namecn = plugins[plugin].namecn - help_text += "\n%s:" % namecn + help_text += "\n%s: " % namecn help_text += PluginManager().instances[plugin].get_help_text(verbose=False).strip() if ADMIN_COMMANDS and isadmin: diff --git a/requirements.txt b/requirements.txt index 255172e..06f285a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ Pillow pre-commit web.py linkai>=0.0.6.0 - +agentmesh-sdk>=0.1.2