alemonjs-aichat 1.0.38 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/main.css-Dysn6Zt3.css +1 -0
- package/lib/assets/main.css.js +1 -1
- package/lib/config.js +15 -1
- package/lib/data/help.json.js +5 -1
- package/lib/image/conponent/AiConfig.js +5 -1
- package/lib/response/config/res.js +18 -5
- package/lib/response/setting/res.js +32 -0
- package/lib/response/zreply/capi.js +76 -6
- package/lib/response/zreply/getChatConfig.js +4 -0
- package/lib/routes/commands.js +1 -1
- package/package.json +1 -1
- package/skills/add-skill/SKILL.md +73 -24
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}.absolute{position:absolute}.relative{position:relative}.-left-4{left:-1rem}.-top-4{top:-1rem}.-z-0{z-index:0}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.box-border{box-sizing:border-box}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-0\.5{height:.125rem}.h-1\.5{height:.375rem}.h-24{height:6rem}.min-h-\[120px\]{min-height:120px}.w-1\.5{width:.375rem}.w-1\/3{width:33.333333%}.w-12{width:3rem}.w-20{width:5rem}.w-24{width:6rem}.auto-rows-auto{grid-auto-rows:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.self-start{align-self:flex-start}.overflow-hidden{overflow:hidden}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-amber-400\/20{border-color:rgba(251,191,36,.2)}.border-amber-400\/30{border-color:rgba(251,191,36,.3)}.border-blue-500\/30{border-color:rgba(59,130,246,.3)}.border-cyan-500\/30{border-color:rgba(6,182,212,.3)}.border-green-500\/30{border-color:rgba(34,197,94,.3)}.border-purple-400\/30{border-color:rgba(192,132,252,.3)}.border-red-500\/20{border-color:rgba(239,68,68,.2)}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.border-yellow-500\/30{border-color:rgba(234,179,8,.3)}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-amber-500\/10{background-color:rgba(245,158,11,.1)}.bg-amber-500\/20{background-color:rgba(245,158,11,.2)}.bg-black\/30{background-color:rgba(0,0,0,.3)}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-blue-400{--tw-bg-opacity:1;background-color:rgb(96 165 250/var(--tw-bg-opacity,1))}.bg-blue-500\/20{background-color:rgba(59,130,246,.2)}.bg-cyan-400{--tw-bg-opacity:1;background-color:rgb(34 211 238/var(--tw-bg-opacity,1))}.bg-cyan-500\/20{background-color:rgba(6,182,212,.2)}.bg-green-400{--tw-bg-opacity:1;background-color:rgb(74 222 128/var(--tw-bg-opacity,1))}.bg-green-500\/20{background-color:rgba(34,197,94,.2)}.bg-purple-400\/20{background-color:rgba(192,132,252,.2)}.bg-red-500\/10{background-color:rgba(239,68,68,.1)}.bg-white\/30{background-color:hsla(0,0%,100%,.3)}.bg-white\/5{background-color:hsla(0,0%,100%,.05)}.bg-yellow-500\/20{background-color:rgba(234,179,8,.2)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-300{--tw-gradient-from:#fcd34d var(--tw-gradient-from-position);--tw-gradient-to:rgba(252,211,77,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-amber-400{--tw-gradient-from:#fbbf24 var(--tw-gradient-from-position);--tw-gradient-to:rgba(251,191,36,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-gray-900\/90{--tw-gradient-from:rgba(17,24,39,.9) var(--tw-gradient-from-position);--tw-gradient-to:rgba(17,24,39,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-purple-500\/20{--tw-gradient-from:rgba(168,85,247,.2) var(--tw-gradient-from-position);--tw-gradient-to:rgba(168,85,247,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.via-yellow-200{--tw-gradient-to:hsla(53,98%,77%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fef08a var(--tw-gradient-via-position),var(--tw-gradient-to)}.to-amber-300{--tw-gradient-to:#fcd34d var(--tw-gradient-to-position)}.to-black\/80{--tw-gradient-to:rgba(0,0,0,.8) var(--tw-gradient-to-position)}.to-pink-500\/20{--tw-gradient-to:rgba(236,72,153,.2) var(--tw-gradient-to-position)}.to-purple-400{--tw-gradient-to:#c084fc var(--tw-gradient-to-position)}.bg-cover{background-size:cover}.bg-clip-text{background-clip:text}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.pl-1{padding-left:.25rem}.pt-2{padding-top:.5rem}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.text-\[\#B8AE8E\]{--tw-text-opacity:1;color:rgb(184 174 142/var(--tw-text-opacity,1))}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-200{--tw-text-opacity:1;color:rgb(253 230 138/var(--tw-text-opacity,1))}.text-amber-300{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.text-blue-300{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.text-cyan-200{--tw-text-opacity:1;color:rgb(165 243 252/var(--tw-text-opacity,1))}.text-cyan-300{--tw-text-opacity:1;color:rgb(103 232 249/var(--tw-text-opacity,1))}.text-green-300{--tw-text-opacity:1;color:rgb(134 239 172/var(--tw-text-opacity,1))}.text-purple-200{--tw-text-opacity:1;color:rgb(233 213 255/var(--tw-text-opacity,1))}.text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.text-transparent{color:transparent}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/40{color:hsla(0,0%,100%,.4)}.text-white\/50{color:hsla(0,0%,100%,.5)}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/70{color:hsla(0,0%,100%,.7)}.text-white\/80{color:hsla(0,0%,100%,.8)}.text-yellow-300{--tw-text-opacity:1;color:rgb(253 224 71/var(--tw-text-opacity,1))}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur-2xl{--tw-blur:blur(40px)}.blur-2xl,.drop-shadow-lg{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow-lg{--tw-drop-shadow:drop-shadow(0 10px 8px rgba(0,0,0,.04)) drop-shadow(0 4px 3px rgba(0,0,0,.1))}.backdrop-blur-md{--tw-backdrop-blur:blur(12px)}.backdrop-blur-md,.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}body{display:flex;flex-direction:column;margin:0;padding:0}.backdrop-blur-xs{backdrop-filter:blur(4px)}.last\:border-0:last-child{border-width:0}.hover\:bg-white\/5:hover{background-color:hsla(0,0%,100%,.05)}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:768px){.md\:block{display:block}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:border-l{border-left-width:1px}.md\:border-r-0{border-right-width:0}}
|
package/lib/assets/main.css.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
const reg = ['win32'].includes(process.platform) ? /^file:\/\/\// : /^file:\/\// ;
|
|
2
|
-
const fileUrl = new URL('main.css-
|
|
2
|
+
const fileUrl = new URL('main.css-Dysn6Zt3.css', import.meta.url).href.replace(reg, '');
|
|
3
3
|
|
|
4
4
|
export { fileUrl as default };
|
package/lib/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { redis } from './redis.js';
|
|
|
5
5
|
|
|
6
6
|
const TOOL_LOOP_TTL_SECONDS = 600;
|
|
7
7
|
const TOOL_LOOP_STALE_MS = TOOL_LOOP_TTL_SECONDS * 1000;
|
|
8
|
+
const DEFAULT_CAPI_CONTEXT_LIMIT = 256000;
|
|
8
9
|
class db {
|
|
9
10
|
redis;
|
|
10
11
|
systemPrompt = "";
|
|
@@ -207,6 +208,19 @@ class db {
|
|
|
207
208
|
}
|
|
208
209
|
/** --------------------------------------------------------- */
|
|
209
210
|
/** 工具相关配置 */
|
|
211
|
+
/** 获取 CAPI 自动压缩上下文长度 */
|
|
212
|
+
async getCAPIContextLimit(guid) {
|
|
213
|
+
const raw = await this.redis.get(`ai:capi_context_limit:${guid}`);
|
|
214
|
+
const limit = Number(raw);
|
|
215
|
+
return Number.isFinite(limit) && limit > 0
|
|
216
|
+
? Math.floor(limit)
|
|
217
|
+
: DEFAULT_CAPI_CONTEXT_LIMIT;
|
|
218
|
+
}
|
|
219
|
+
/** 设置 CAPI 自动压缩上下文长度 */
|
|
220
|
+
async setCAPIContextLimit(guid, limit) {
|
|
221
|
+
const normalized = Math.max(1, Math.floor(limit));
|
|
222
|
+
await this.redis.set(`ai:capi_context_limit:${guid}`, String(normalized));
|
|
223
|
+
}
|
|
210
224
|
/** 获取工具开关状态 */
|
|
211
225
|
async getToolSwitch(guid) {
|
|
212
226
|
return (await this.redis.get(`ai:tool:switch:${guid}`)) || "1";
|
|
@@ -1197,4 +1211,4 @@ class db {
|
|
|
1197
1211
|
}
|
|
1198
1212
|
const redisClient = new db(redis);
|
|
1199
1213
|
|
|
1200
|
-
export { redisClient as default };
|
|
1214
|
+
export { DEFAULT_CAPI_CONTEXT_LIMIT, redisClient as default };
|
package/lib/data/help.json.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var title = "IA聊天插件";
|
|
2
2
|
var desc = "一个基于AI的聊天插件,支持多种AI模型和丰富的功能配置,适用于各种聊天场景。";
|
|
3
3
|
var name = "alemonjs-aichat";
|
|
4
|
-
var version = "v1.0.
|
|
4
|
+
var version = "v1.0.39";
|
|
5
5
|
var by = "AlemonJS";
|
|
6
6
|
var list = [
|
|
7
7
|
{
|
|
@@ -137,6 +137,10 @@ var list = [
|
|
|
137
137
|
cmd: "/清空对话",
|
|
138
138
|
desc: "清空当前页面的对话记录。"
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
cmd: "/设置AI上下文长度 [256k|1m]",
|
|
142
|
+
desc: "设置 CAPI 模式自动压缩上下文的长度,默认 256k。"
|
|
143
|
+
},
|
|
140
144
|
{
|
|
141
145
|
cmd: "/撤回",
|
|
142
146
|
desc: "引用一条消息发送撤回指令,自动撤回被引用的消息。"
|
|
@@ -10,6 +10,7 @@ const cmds = [
|
|
|
10
10
|
{ cmd: "/<开启|关闭>仅艾特触发", desc: "开启或关闭仅艾特触发功能" },
|
|
11
11
|
{ cmd: "/<开启|关闭>工具", desc: "开启或关闭AI工具" },
|
|
12
12
|
{ cmd: "/<开启|关闭>工具调用内容", desc: "开启或关闭工具调用前内容发送" },
|
|
13
|
+
{ cmd: "/设置AI上下文长度 256k", desc: "设置CAPI自动压缩上下文长度" },
|
|
13
14
|
{ cmd: "/<开启|关闭>复杂输出", desc: "开启或关闭复杂输出" },
|
|
14
15
|
{ cmd: "/<开启|关闭>好感度", desc: "开启或关闭好感度系统" },
|
|
15
16
|
{ cmd: "/清空对话", desc: "清空当前对话历史" },
|
|
@@ -78,7 +79,10 @@ function App(data) {
|
|
|
78
79
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A", value: data.toolPromptSwitch }),
|
|
79
80
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A\u64A4\u56DE", value: data.toolPromptRevokeSwitch }),
|
|
80
81
|
React.createElement(StatusItem, { label: "\u5DE5\u5177\u63D0\u793A\u4F20\u53C2", value: data.toolPromptArgsSwitch }),
|
|
81
|
-
React.createElement(StatusItem, { label: "\u5DE5\u5177\u8C03\u7528\u5185\u5BB9", value: data.toolCallContentSwitch })
|
|
82
|
+
React.createElement(StatusItem, { label: "\u5DE5\u5177\u8C03\u7528\u5185\u5BB9", value: data.toolCallContentSwitch }),
|
|
83
|
+
React.createElement("div", { className: "flex items-center justify-between py-2 px-2 rounded-lg bg-white/5" },
|
|
84
|
+
React.createElement("span", { className: " text-white/70" }, "CAPI\u4E0A\u4E0B\u6587\u957F\u5EA6"),
|
|
85
|
+
React.createElement("span", { className: "px-3 py-0.5 rounded-full text-xs font-mono font-medium border bg-cyan-500/20 text-cyan-300 border-cyan-500/30" }, data.capiContextLimit))),
|
|
82
86
|
React.createElement("div", { className: "pt-2 border-t border-white/10" },
|
|
83
87
|
React.createElement("div", { className: "flex items-center justify-between py-2" },
|
|
84
88
|
React.createElement("span", { className: " text-white/60" }, "\u5F53\u524D\u4F7F\u7528\u6A21\u578B"),
|
|
@@ -63,9 +63,18 @@ const formatNumberValue = (value) => {
|
|
|
63
63
|
const formatTokenValue = (value) => {
|
|
64
64
|
return formatNumberValue(value);
|
|
65
65
|
};
|
|
66
|
-
const
|
|
66
|
+
const formatContextLimit = (tokens) => {
|
|
67
|
+
if (tokens >= 1000000 && tokens % 1000000 === 0) {
|
|
68
|
+
return `${tokens / 1000000}m`;
|
|
69
|
+
}
|
|
70
|
+
if (tokens >= 1000 && tokens % 1000 === 0) {
|
|
71
|
+
return `${tokens / 1000}k`;
|
|
72
|
+
}
|
|
73
|
+
return `${tokens}`;
|
|
74
|
+
};
|
|
75
|
+
const formatAIContextStatus = (stats, capiContextLimit) => {
|
|
67
76
|
if (!stats) {
|
|
68
|
-
return
|
|
77
|
+
return `当前会话还没有 CAPI 上下文统计。\nCAPI上下文长度: ${formatContextLimit(capiContextLimit)}`;
|
|
69
78
|
}
|
|
70
79
|
const updatedAt = new Date(stats.updatedAt).toLocaleString("zh-CN", {
|
|
71
80
|
hour12: false,
|
|
@@ -83,6 +92,7 @@ const formatAIContextStatus = (stats) => {
|
|
|
83
92
|
`请求次数: ${formatNumberValue(stats.requestCount)}`,
|
|
84
93
|
divider,
|
|
85
94
|
"最近一次上下文:",
|
|
95
|
+
`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}`,
|
|
86
96
|
`消息数: ${formatNumberValue(stats.contextMessageCount)}`,
|
|
87
97
|
`上下文字符数: ${formatNumberValue(stats.contextCharLength)}`,
|
|
88
98
|
`上下文估算token: ${formatTokenValue(stats.estimatedContextTokens)}`,
|
|
@@ -110,7 +120,8 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
110
120
|
// 查看AI状态
|
|
111
121
|
if (/^(\/|#)ai状态$/i.test(e.msg)) {
|
|
112
122
|
const stats = await redisClient.getAIContextStats(e.guid);
|
|
113
|
-
|
|
123
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
124
|
+
message.send(format(Text(formatAIContextStatus(stats, capiContextLimit))));
|
|
114
125
|
}
|
|
115
126
|
// 查看AI配置
|
|
116
127
|
if (/^(\/|#)(ai配置|当前提示词|查看提示词)$/i.test(e.msg)) {
|
|
@@ -128,6 +139,7 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
128
139
|
const toolPromptRevokeSwitch = await redisClient.getToolPromptRevokeSwitch(e.guid); // 工具提示撤回开关
|
|
129
140
|
const toolPromptArgsSwitch = await redisClient.getToolPromptArgsSwitch(e.guid); // 工具提示传参开关
|
|
130
141
|
const toolCallContentSwitch = await redisClient.getToolCallContentSwitch(e.guid); // 工具调用内容发送开关
|
|
142
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
131
143
|
// 发送消息
|
|
132
144
|
try {
|
|
133
145
|
const scale = await redisClient.getRenderPrecision(e.guid);
|
|
@@ -149,6 +161,7 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
149
161
|
toolPromptRevokeSwitch: toolPromptRevokeSwitch == "1",
|
|
150
162
|
toolPromptArgsSwitch: toolPromptArgsSwitch == "1",
|
|
151
163
|
toolCallContentSwitch: toolCallContentSwitch == "1",
|
|
164
|
+
capiContextLimit: formatContextLimit(capiContextLimit),
|
|
152
165
|
}, {
|
|
153
166
|
playwright: {
|
|
154
167
|
context: { deviceScaleFactor: scale },
|
|
@@ -158,11 +171,11 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
158
171
|
message.send(format(Image(img)));
|
|
159
172
|
}
|
|
160
173
|
else {
|
|
161
|
-
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
174
|
+
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
catch (error) {
|
|
165
|
-
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
178
|
+
message.send(format(Text(`当前AI配置:\n`), Text(`总开关: ${config.model != "" ? "开启" : "关闭"}\n`), Text(`工具: ${toolsIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`工具调用内容: ${toolCallContentSwitch == "1" ? "开启" : "关闭"}\n`), Text(`CAPI上下文长度: ${formatContextLimit(capiContextLimit)}\n`), Text(`复杂输出: ${complexResponse == "1" ? "开启" : "关闭"}\n`), Text(`好感度开关: ${affectionIsOpen == "1" ? "开启" : "关闭"}\n`), Text(`TTS回复开关: ${isOpenTTSReply == "1" ? "开启" : "关闭"}\n`), Text(`深度思考: ${deepThinkingIsOpen == "1" ? "默认" : "关闭"}\n`), Text(`仅艾特触发: ${atTriggerSwitch == "1" ? "开启" : "关闭"}\n`), Text(`模型: ${config.model || "未设置"}\n`), Text(`提示词: ${config.systemPrompt || "未设置"}\n`), Text(`当前AI: ${currentAI || "未设置"}\n`), Text(`AI数量: ${aiList.length ? aiList.length : "暂无AI配置"}`)));
|
|
166
179
|
}
|
|
167
180
|
}
|
|
168
181
|
// 查看AI列表
|
|
@@ -8,6 +8,21 @@ const value = getConfigValue().aiChat || {};
|
|
|
8
8
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
9
9
|
// inline regex checks are used in-place; keep `test` as shorthand
|
|
10
10
|
const test = /(\/|#)测试$/i;
|
|
11
|
+
const capiContextLimitCommand = /^(#|\/)设置(AI)?上下文(长度)? ?([0-9]+)(k|m)$/i;
|
|
12
|
+
const formatContextLimit = (tokens) => {
|
|
13
|
+
if (tokens >= 1000000 && tokens % 1000000 === 0) {
|
|
14
|
+
return `${tokens / 1000000}m`;
|
|
15
|
+
}
|
|
16
|
+
if (tokens >= 1000 && tokens % 1000 === 0) {
|
|
17
|
+
return `${tokens / 1000}k`;
|
|
18
|
+
}
|
|
19
|
+
return `${tokens}`;
|
|
20
|
+
};
|
|
21
|
+
const parseContextLimit = (value, unit) => {
|
|
22
|
+
const count = Number(value);
|
|
23
|
+
const multiplier = unit.toLowerCase() === "m" ? 1000000 : 1000;
|
|
24
|
+
return Math.floor(count * multiplier);
|
|
25
|
+
};
|
|
11
26
|
var res = onResponse(selects, async (e, next) => {
|
|
12
27
|
// 创建
|
|
13
28
|
const [message] = useMessage(e);
|
|
@@ -281,6 +296,23 @@ var res = onResponse(selects, async (e, next) => {
|
|
|
281
296
|
message.send(format(Text(`已将当前 AI 的模型修改为 ${modelName.trim()} !`)));
|
|
282
297
|
return;
|
|
283
298
|
}
|
|
299
|
+
// 设置 CAPI 自动压缩上下文长度
|
|
300
|
+
if (capiContextLimitCommand.test(e.msg)) {
|
|
301
|
+
const match = e.msg.match(capiContextLimitCommand);
|
|
302
|
+
if (!match) {
|
|
303
|
+
message.send(format(Text("格式错误,请按照 格式:/设置AI上下文长度 256k 进行设置")));
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const [, , , , value, unit] = match;
|
|
307
|
+
const limit = parseContextLimit(value, unit);
|
|
308
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
309
|
+
message.send(format(Text("上下文长度必须大于 0,例如:/设置AI上下文长度 256k")));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await redisClient.setCAPIContextLimit(e.guid, limit);
|
|
313
|
+
message.send(format(Text(`已设置 CAPI 上下文长度为 ${formatContextLimit(limit)},接近 90% 时会自动压缩上下文。`)));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
284
316
|
// 设置复杂输出开关
|
|
285
317
|
if (/(\/|#)(关闭|开启)复杂(输出|回复)$/i.test(e.msg)) {
|
|
286
318
|
const match = e.msg.match(/(\/|#)(关闭|开启)复杂(输出|回复)$/i);
|
|
@@ -3,13 +3,16 @@ import { log } from 'console';
|
|
|
3
3
|
import OpenAi from 'openai';
|
|
4
4
|
import { TTSClient } from '../../api/tts.js';
|
|
5
5
|
import { availableTools } from '../../api/aitools.js';
|
|
6
|
-
import redisClient from '../../config.js';
|
|
6
|
+
import redisClient, { DEFAULT_CAPI_CONTEXT_LIMIT } from '../../config.js';
|
|
7
7
|
import { getChatConfig, shouldSkipAIReply, buildUserMessage, getDeepThoughtReasoning } from './getChatConfig.js';
|
|
8
8
|
import { createTTSMessage } from './tts.js';
|
|
9
9
|
import { parseAIReply } from './tools.js';
|
|
10
10
|
|
|
11
11
|
const selects = onSelects(["message.create", "private.message.create"]);
|
|
12
12
|
let currentModel = "派蒙-默认"; // 默认音色
|
|
13
|
+
const CAPI_CONTEXT_COMPRESSION_RATIO = 0.9;
|
|
14
|
+
const CAPI_CONTEXT_RETAIN_RATIO = 0.35;
|
|
15
|
+
const AUTO_SUMMARY_DETAIL_MAX_LENGTH = 2000;
|
|
13
16
|
const sendAIReply = async (reply, cfg, e, message) => {
|
|
14
17
|
const aireply = await parseAIReply(reply, cfg, e, message);
|
|
15
18
|
console.log("aireply", aireply);
|
|
@@ -55,6 +58,69 @@ const getTextLength = (value) => {
|
|
|
55
58
|
}
|
|
56
59
|
return 0;
|
|
57
60
|
};
|
|
61
|
+
const estimateContextTokens = (value) => {
|
|
62
|
+
return Math.ceil(getTextLength(value) / 4);
|
|
63
|
+
};
|
|
64
|
+
const trimText = (text, maxLength) => {
|
|
65
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
66
|
+
if (normalized.length <= maxLength) {
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
return `${normalized.slice(0, maxLength)}...`;
|
|
70
|
+
};
|
|
71
|
+
const takeTailMessagesWithinTokenBudget = (history, tokenBudget) => {
|
|
72
|
+
const kept = [];
|
|
73
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
74
|
+
kept.unshift(history[index]);
|
|
75
|
+
if (estimateContextTokens(kept) > tokenBudget) {
|
|
76
|
+
kept.shift();
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return kept;
|
|
81
|
+
};
|
|
82
|
+
const buildAutoSummaryMessage = (record) => {
|
|
83
|
+
const summary = record?.summary || "较早对话已自动归档";
|
|
84
|
+
const detail = trimText(record?.detail || record?.transcript || "无详细摘要", AUTO_SUMMARY_DETAIL_MAX_LENGTH);
|
|
85
|
+
return {
|
|
86
|
+
role: "system",
|
|
87
|
+
content: [
|
|
88
|
+
"【自动压缩上下文】",
|
|
89
|
+
`较早对话已归档为 ${record?.id || "未知ID"},当前上下文只保留摘要和最近消息。`,
|
|
90
|
+
`摘要: ${summary}`,
|
|
91
|
+
`详细信息: ${detail}`,
|
|
92
|
+
"如需更完整内容,可按归档ID或关键词调用 MemoryOperation 检索历史归档。",
|
|
93
|
+
].join("\n"),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const prepareCapiMessagesForRequest = async (guid, systemMessage, currentMessages) => {
|
|
97
|
+
const limit = await redisClient.getCAPIContextLimit(guid);
|
|
98
|
+
const threshold = Math.floor(limit * CAPI_CONTEXT_COMPRESSION_RATIO);
|
|
99
|
+
const estimatedTokens = estimateContextTokens(currentMessages);
|
|
100
|
+
if (estimatedTokens < threshold) {
|
|
101
|
+
return currentMessages;
|
|
102
|
+
}
|
|
103
|
+
const history = await redisClient.getAIChatHistory(guid);
|
|
104
|
+
if (history.length === 0) {
|
|
105
|
+
return currentMessages;
|
|
106
|
+
}
|
|
107
|
+
const archived = await redisClient.archiveAIChatHistory(guid, {
|
|
108
|
+
reason: "auto_context_compress",
|
|
109
|
+
});
|
|
110
|
+
if (!archived) {
|
|
111
|
+
return currentMessages;
|
|
112
|
+
}
|
|
113
|
+
const minRetainBudget = Math.min(Math.floor(DEFAULT_CAPI_CONTEXT_LIMIT * 0.05), Math.floor(limit * 0.5));
|
|
114
|
+
const retainBudget = Math.max(Math.floor(limit * CAPI_CONTEXT_RETAIN_RATIO), minRetainBudget);
|
|
115
|
+
const keptHistory = takeTailMessagesWithinTokenBudget(history, retainBudget);
|
|
116
|
+
const compressedHistory = [buildAutoSummaryMessage(archived), ...keptHistory];
|
|
117
|
+
await redisClient.setAIChatHistory(guid, compressedHistory);
|
|
118
|
+
const rebuiltMessages = [systemMessage, ...compressedHistory];
|
|
119
|
+
const pendingMessages = currentMessages.slice(Math.max(1 + history.length, 1));
|
|
120
|
+
rebuiltMessages.push(...pendingMessages);
|
|
121
|
+
log(`CAPI上下文接近上限,已自动压缩:${estimatedTokens}/${limit} tokens,归档ID:${archived.id}`);
|
|
122
|
+
return rebuiltMessages;
|
|
123
|
+
};
|
|
58
124
|
const getNumber = (value) => {
|
|
59
125
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
60
126
|
};
|
|
@@ -84,7 +150,7 @@ const recordCapiContextStats = async (guid, model, messages, completion) => {
|
|
|
84
150
|
model,
|
|
85
151
|
contextMessageCount: messages.length,
|
|
86
152
|
contextCharLength,
|
|
87
|
-
estimatedContextTokens:
|
|
153
|
+
estimatedContextTokens: estimateContextTokens(messages),
|
|
88
154
|
promptTokens,
|
|
89
155
|
completionTokens,
|
|
90
156
|
totalTokens: getNumber(usage?.total_tokens),
|
|
@@ -110,14 +176,16 @@ const CApiReply = async (e) => {
|
|
|
110
176
|
apiKey: cfg.config.key,
|
|
111
177
|
timeout: 300000,
|
|
112
178
|
});
|
|
179
|
+
const systemMessage = {
|
|
180
|
+
role: "system",
|
|
181
|
+
content: cfg.systemPrompt + `\n性格约束:${cfg.config.systemPrompt}`,
|
|
182
|
+
};
|
|
113
183
|
let messages = [
|
|
114
|
-
|
|
115
|
-
role: "system",
|
|
116
|
-
content: cfg.systemPrompt + `\n性格约束:${cfg.config.systemPrompt}`,
|
|
117
|
-
},
|
|
184
|
+
systemMessage,
|
|
118
185
|
...cfg.historyMessages,
|
|
119
186
|
usermessage,
|
|
120
187
|
];
|
|
188
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
121
189
|
const createParams = {
|
|
122
190
|
model: cfg.config.model,
|
|
123
191
|
messages: messages,
|
|
@@ -182,6 +250,7 @@ const CApiReply = async (e) => {
|
|
|
182
250
|
if (pendingGuidanceMessages.length === 0) {
|
|
183
251
|
break;
|
|
184
252
|
}
|
|
253
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
185
254
|
log("工具调用完成后收到补充引导,继续请求模型:", pendingGuidanceMessages.length);
|
|
186
255
|
const params = {
|
|
187
256
|
model: cfg.config.model,
|
|
@@ -286,6 +355,7 @@ const CApiReply = async (e) => {
|
|
|
286
355
|
if (guidanceMessages.length > 0) {
|
|
287
356
|
log("工具调用期间收到补充引导:", guidanceMessages.length);
|
|
288
357
|
}
|
|
358
|
+
messages = await prepareCapiMessagesForRequest(e.guid, systemMessage, messages);
|
|
289
359
|
const params = {
|
|
290
360
|
model: cfg.config.model,
|
|
291
361
|
messages: messages,
|
|
@@ -60,6 +60,8 @@ const getChatConfig = async (e) => {
|
|
|
60
60
|
const atTriggerSwitch = await redisClient.getAtTriggerSwitch(e.guid);
|
|
61
61
|
/** 历史消息 */
|
|
62
62
|
const historyMessages = await redisClient.getAIChatHistory(e.guid);
|
|
63
|
+
/** CAPI 自动压缩上下文长度 */
|
|
64
|
+
const capiContextLimit = await redisClient.getCAPIContextLimit(e.guid);
|
|
63
65
|
/** 好感度信息 */
|
|
64
66
|
const affections = await redisClient.getAffectionLevelAll(e.guid);
|
|
65
67
|
/** 工具相关配置 */
|
|
@@ -223,6 +225,8 @@ ${memoryPrompt ? `\n【系统预检索记忆】\n${relevantMemoryContext}\n` : "
|
|
|
223
225
|
skills,
|
|
224
226
|
/** 历史消息 */
|
|
225
227
|
historyMessages,
|
|
228
|
+
/** CAPI 自动压缩上下文长度 */
|
|
229
|
+
capiContextLimit,
|
|
226
230
|
/** 好感度信息 */
|
|
227
231
|
affections,
|
|
228
232
|
/** 工具配置 */
|
package/lib/routes/commands.js
CHANGED
|
@@ -24,7 +24,7 @@ const children = [
|
|
|
24
24
|
},
|
|
25
25
|
// setting (包含大量子命令)
|
|
26
26
|
{
|
|
27
|
-
regular: Regular.or(/(\/|#)添加ai$/i, /(\/|#)添加ai\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/i, /(\/|#)(切换)(ai|配置) ?(.*)/i, /(\/|#)添加提示词$/i, /(\/|#)添加提示词 (\S+) ([\s\S]+)/i, /(\/|#)(删除)(提示词|提示词列表) ?(.*)/i, /(\/|#)(切换|设置)提示词 ?(.*)$/i, /(\/|#)清空提示词$/i, /(\/|#)删除ai\s(\S+)$/i, /(\/|#)(开启|启用|关闭|禁用)好感度$/i, /(\/|#)(开启|关闭)(全部)?聊天$/i, /(\/|#)(开启|关闭)语音回复$/i, /(\/|#)切换图床(.*)$/i, /(\/|#)修改 ?(.+) ?模型 ?(.+)$/i, /(\/|#)(关闭|开启)复杂(输出|回复)$/i, /(\/|#)(关闭|禁止|不可以|不允许|开启|可以|允许)(涩涩|色色)$/i, /(\/|#)切换模型(.*)$/i, /(\/|#)(关闭|开启)主动(搭话|回复)$/i, /(\/|#)(开启|关闭)深度思考$/i, /(\/|#)(设置|修改)(ai)?(.*)rapi模式(开启|关闭)$/i, /(\/|#)(开启|关闭)仅艾特触发$/i, /(\/|#)添加正则规则 (.+)$/i, /(\/|#)(删除|移除)正则规则 (.+)$/i, /(\/|#)正则规则列表$/i, /(\/|#)清空正则规则$/i, /(\/|#)切换群容器$/i, /(\/|#)切换用户容器$/i, /(\/|#)切换系统环境$/i, /(\/|#)工具列表$/i, /(\/|#)(开启|关闭)(MCP)?工具$/i, /(\/|#)(开启|关闭)工具(.*)$/i, /(\/|#)(开启|关闭)工具提示$/i, /(\/|#)(开启|关闭)工具(调用)?提示撤回$/i, /(\/|#)(开启|关闭)工具(调用)?提示详情$/i, /(\/|#)(开启|关闭)工具调用内容$/i, /(\/|#)测试$/i, /(\/|#)设置渲染精度\s*(中|高|超高)$/i),
|
|
27
|
+
regular: Regular.or(/(\/|#)添加ai$/i, /(\/|#)添加ai\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/i, /(\/|#)(切换)(ai|配置) ?(.*)/i, /(\/|#)添加提示词$/i, /(\/|#)添加提示词 (\S+) ([\s\S]+)/i, /(\/|#)(删除)(提示词|提示词列表) ?(.*)/i, /(\/|#)(切换|设置)提示词 ?(.*)$/i, /(\/|#)清空提示词$/i, /(\/|#)删除ai\s(\S+)$/i, /(\/|#)(开启|启用|关闭|禁用)好感度$/i, /(\/|#)(开启|关闭)(全部)?聊天$/i, /(\/|#)(开启|关闭)语音回复$/i, /(\/|#)切换图床(.*)$/i, /(\/|#)修改 ?(.+) ?模型 ?(.+)$/i, /(\/|#)(关闭|开启)复杂(输出|回复)$/i, /(\/|#)(关闭|禁止|不可以|不允许|开启|可以|允许)(涩涩|色色)$/i, /(\/|#)切换模型(.*)$/i, /(\/|#)(关闭|开启)主动(搭话|回复)$/i, /(\/|#)(开启|关闭)深度思考$/i, /(#|\/)设置(AI)?上下文(长度)? ?([0-9]+)(k|m)/i, /(\/|#)(设置|修改)(ai)?(.*)rapi模式(开启|关闭)$/i, /(\/|#)(开启|关闭)仅艾特触发$/i, /(\/|#)添加正则规则 (.+)$/i, /(\/|#)(删除|移除)正则规则 (.+)$/i, /(\/|#)正则规则列表$/i, /(\/|#)清空正则规则$/i, /(\/|#)切换群容器$/i, /(\/|#)切换用户容器$/i, /(\/|#)切换系统环境$/i, /(\/|#)工具列表$/i, /(\/|#)(开启|关闭)(MCP)?工具$/i, /(\/|#)(开启|关闭)工具(.*)$/i, /(\/|#)(开启|关闭)工具提示$/i, /(\/|#)(开启|关闭)工具(调用)?提示撤回$/i, /(\/|#)(开启|关闭)工具(调用)?提示详情$/i, /(\/|#)(开启|关闭)工具调用内容$/i, /(\/|#)测试$/i, /(\/|#)设置渲染精度\s*(中|高|超高)$/i),
|
|
28
28
|
handler: lazy(() => import('../response/setting/res.js')),
|
|
29
29
|
},
|
|
30
30
|
// affection
|
package/package.json
CHANGED
|
@@ -3,30 +3,79 @@ name: add-skill
|
|
|
3
3
|
description: 添加新技能, 例如根据用户的需求创建一个新的技能, 或者将现有技能进行组合形成一个新的技能
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Add Skill
|
|
7
|
-
|
|
8
|
-
添加新技能的技能。
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
# Add Skill
|
|
7
|
+
|
|
8
|
+
添加新技能的技能。
|
|
9
|
+
|
|
10
|
+
当前运行环境有权限边界: AI 不能假设自己可以写入软件包内置的 `./skills` 目录、宿主机任意绝对路径或进程工作目录外的文件。新增技能时必须优先使用当前可用的 `Agent*` 文件工具, 将文件写入 `public/{guid}/` 工作目录内。
|
|
11
|
+
|
|
12
|
+
技能路径有两类:
|
|
13
|
+
|
|
14
|
+
- 内置技能目录: 软件包中的 `./skills`。这是只读来源, 除非用户明确说明当前环境有权限直接修改软件包源码, 否则不要把新技能写到这里。
|
|
15
|
+
- 用户自定义技能目录: 由 `alemon.config.yaml` 中的 `aiChat.skills_dir` 指定。默认可配置为 `["./skills"]`, 在受限环境中建议配置为包含某个可写工作区, 例如 `["./skills", "./public/<guid>/skills"]`。
|
|
16
|
+
|
|
17
|
+
在当前受限权限下, 默认把新技能创建到 `public/{guid}/skills/<skill-name>/SKILL.md`。如果该目录已经被加入 `aiChat.skills_dir`, 新技能即可被技能加载逻辑读取; 如果尚未加入, 需要在输出中提醒用户把 `./public/<guid>/skills` 加入 `aiChat.skills_dir` 后重启或刷新服务。
|
|
18
|
+
|
|
19
|
+
所有新建的技能必须要新建一个目录, 例如`./skills/my-skill`, 在该目录下创建一个`SKILL.md`文件, 并将技能的名称和描述写入文件中, 如果需要使用工具函数, 则在该目录下创建一个`<tool>.js`文件, 并将工具函数写入文件中。
|
|
20
|
+
脚本文件需要能够通过命令行直接执行, 并支持传参, 例如`node <tool>.js <arg1> <arg2>`, 脚本必须是执行后就能得到结果的, 不能是需要用户交互的, 例如`node <tool>.js`后需要用户输入一些内容才能得到结果的脚本是不允许的。
|
|
14
21
|
|
|
15
22
|
此外脚本必须写清楚输入输出的格式, 例如输入是一个字符串, 输出是一个字符串, 输入是一个文件路径, 输出是一个文件路径等, 以便AI能够正确地使用工具函数。
|
|
16
23
|
|
|
17
|
-
脚本必须为JavaScript文件, 以便AI能够正确地使用工具函数, 其他语言的脚本文件是不允许的。
|
|
18
|
-
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
脚本必须为JavaScript文件, 以便AI能够正确地使用工具函数, 其他语言的脚本文件是不允许的。
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
- `AgentListWorkspaces`: 不知道可用 `guid` 时, 先列出 `public/` 下已有工作区。
|
|
29
|
+
- `AgentCreateWorkspace`: 没有可用工作区时创建 `public/{guid}/`。
|
|
30
|
+
- `AgentListFiles`: 查看目标 `skills` 目录和已有技能。
|
|
31
|
+
- `AgentReadFileLines`: 读取已有技能内容, 用于组合、复用或避免覆盖。
|
|
32
|
+
- `AgentCreateFile`: 创建新的 `SKILL.md` 或工具脚本。默认不要覆盖已有文件。
|
|
33
|
+
- `AgentWriteFile`: 只在已经确认需要覆盖或追加时使用。
|
|
34
|
+
- `AgentProjectCommand`: 只用于必要的受限检查, 例如创建目录或运行脚本自检。
|
|
35
|
+
|
|
36
|
+
## Workflow
|
|
37
|
+
|
|
38
|
+
1. 接收用户提供的技能需求
|
|
39
|
+
2. 确认本次使用的 `guid`
|
|
40
|
+
- 如果用户已经给出 `guid`, 直接使用
|
|
41
|
+
- 如果不知道 `guid`, 先调用 `AgentListWorkspaces`
|
|
42
|
+
- 如果没有合适的工作区, 调用 `AgentCreateWorkspace`
|
|
43
|
+
3. 规范化技能名
|
|
44
|
+
- 使用小写短横线命名, 例如 `my-skill`
|
|
45
|
+
- 只允许字母、数字和短横线
|
|
46
|
+
- 不允许绝对路径、`../`、空格或会逃逸目录的名称
|
|
47
|
+
4. 判断是创建新技能, 还是将现有技能组合成新技能
|
|
48
|
+
- 需要查看已有技能时, 先用 `getSKILLDetail` 读取内置或已配置技能
|
|
49
|
+
- 需要查看工作区内已有技能时, 用 `AgentListFiles` 和 `AgentReadFileLines`
|
|
50
|
+
5. 创建目标目录和文件
|
|
51
|
+
- 目标路径固定为 `skills/<skill-name>/SKILL.md`, 该路径是相对 `public/{guid}/` 的路径
|
|
52
|
+
- 目录不存在时, 可用 `AgentProjectCommand` 的 `mkdir` 创建 `skills/<skill-name>`
|
|
53
|
+
- 用 `AgentCreateFile` 创建 `skills/<skill-name>/SKILL.md`
|
|
54
|
+
- 如果必须添加工具脚本, 只创建 JavaScript 文件, 例如 `skills/<skill-name>/<tool>.js`
|
|
55
|
+
6. 写入 `SKILL.md`
|
|
56
|
+
- 文件必须包含 frontmatter: `name` 和 `description`
|
|
57
|
+
- 正文要写清楚触发场景、工作流、可用工具、输入输出和限制
|
|
58
|
+
- 如果包含脚本, 必须写清脚本命令、参数和输出格式
|
|
59
|
+
7. 写完后用 `AgentReadFileLines` 读取 `SKILL.md`, 确认内容已经落盘
|
|
60
|
+
8. 返回新技能的名称、描述、文件路径和是否还需要配置
|
|
61
|
+
- 如果 `./public/<guid>/skills` 未确认在 `aiChat.skills_dir` 中, 明确提示用户添加配置
|
|
62
|
+
- 不要声称技能已经全局生效, 除非已确认目标目录被技能加载逻辑读取
|
|
63
|
+
|
|
64
|
+
## Output Format
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
技能名称: <技能名称>
|
|
68
|
+
技能描述: <技能描述>
|
|
69
|
+
技能路径: public/<guid>/skills/<技能名称>/SKILL.md
|
|
70
|
+
配置提示: 如未生效, 请将 ./public/<guid>/skills 加入 alemon.config.yaml 的 aiChat.skills_dir
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Rules
|
|
74
|
+
|
|
75
|
+
- 不要使用 `exec`、终端命令、Docker、容器或任意宿主机文件路径来创建技能文件。
|
|
76
|
+
- 不要写入软件包内置 `./skills` 目录, 除非用户明确说明当前环境允许直接修改源码且可用工具确实能访问该目录。
|
|
77
|
+
- 所有通过 Agent 文件工具创建的路径都必须相对 `public/{guid}/`, 不要传绝对路径。
|
|
78
|
+
- 不要使用 `../` 试图离开工作区。
|
|
79
|
+
- 已有同名技能时, 先读取确认, 再询问用户是否覆盖或改名; 不要静默覆盖。
|
|
80
|
+
- 如果需要组合多个已有技能, 在新 `SKILL.md` 中写明组合顺序和每个被组合技能的职责边界。
|
|
81
|
+
- 如果新技能依赖运行脚本, 脚本必须是非交互式 JavaScript 文件, 并且能够通过 `node <tool>.js <args>` 直接返回结果。
|