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