alemonjs-aichat 1.0.39 → 1.0.40

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.
@@ -0,0 +1,224 @@
1
+ import React from 'react';
2
+ import { defineConfig } from 'jsxp';
3
+ import { existsSync, readdirSync } from 'fs';
4
+ import App$1 from '../image/conponent/help.js';
5
+ import App from '../image/conponent/AiConfig.js';
6
+ import PanelPreview from '../image/conponent/PanelPreview.js';
7
+ import redisClient from '../config.js';
8
+ import { loadPanelGroupConfig, listPanelGroupGuids, loadPanelGroupConfigs } from './groupConfig.js';
9
+ import { resolvePanelChatIdentity, getPanelChatMessages, PANEL_BOT_ID } from './chat.js';
10
+ import { dirname, join } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const panelDir = dirname(fileURLToPath(import.meta.url));
14
+ const packageRoot = join(panelDir, "..", "..");
15
+ const runtimeRoot = join(panelDir, "..");
16
+ const panelPort = Number(process.env.JSXP_PORT || process.env.PORT || 8080);
17
+ const toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
18
+ const withTimeout = async (promise, ms, message) => Promise.race([
19
+ promise,
20
+ new Promise((_, reject) => {
21
+ setTimeout(() => reject(new Error(message)), ms);
22
+ }),
23
+ ]);
24
+ const getPanelCurrentAI = async () => {
25
+ const currentKeys = await redisClient.redis.keys("ai:currentAI:*");
26
+ const currentKey = currentKeys[0];
27
+ if (!currentKey) {
28
+ return {
29
+ currentAI: "",
30
+ currentGuid: "global",
31
+ };
32
+ }
33
+ return {
34
+ currentAI: (await redisClient.redis.get(currentKey)) || "",
35
+ currentGuid: currentKey.replace("ai:currentAI:", "") || "global",
36
+ };
37
+ };
38
+ const getPanelQueryText = (ctx, key) => {
39
+ const value = ctx?.query?.[key];
40
+ if (Array.isArray(value))
41
+ return String(value[0] || "");
42
+ return String(value || "");
43
+ };
44
+ const parsePanelOpenRows = (value) => {
45
+ if (!value)
46
+ return [];
47
+ try {
48
+ const parsed = JSON.parse(value);
49
+ return Array.isArray(parsed)
50
+ ? parsed.filter((item) => typeof item === "string")
51
+ : [];
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ };
57
+ const parsePanelOpenGroups = (value) => {
58
+ if (!value)
59
+ return [];
60
+ try {
61
+ const parsed = JSON.parse(value);
62
+ return Array.isArray(parsed)
63
+ ? parsed.filter((item) => typeof item === "string")
64
+ : [];
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ };
70
+ const parsePanelScrollY = (value) => {
71
+ const parsed = Number(value);
72
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
73
+ };
74
+ const loadPanelAIConfigs = async () => {
75
+ const names = await withTimeout(redisClient.getAIList(), 1200, "读取 ai:list:* 超时");
76
+ const aiConfigs = await withTimeout(Promise.all(names.map(async (name) => {
77
+ const raw = await redisClient.redis.get(`ai:list:${name}`);
78
+ if (!raw)
79
+ return null;
80
+ const config = JSON.parse(raw);
81
+ return {
82
+ name,
83
+ host: config.host || "",
84
+ key: config.key || "",
85
+ model: config.model || "",
86
+ rapi: Boolean(config.rapi),
87
+ };
88
+ })), 1200, "读取 ai:list 详情超时");
89
+ return aiConfigs.filter((item) => Boolean(item));
90
+ };
91
+ const loadPanelWorkspaceNames = async () => {
92
+ const publicRoot = join(process.cwd(), "public");
93
+ if (!existsSync(publicRoot)) {
94
+ return [];
95
+ }
96
+ const ignoredNames = new Set(["sandbox", "image_out"]);
97
+ return readdirSync(publicRoot, { withFileTypes: true })
98
+ .filter((entry) => entry.isDirectory() && !ignoredNames.has(entry.name))
99
+ .map((entry) => entry.name)
100
+ .sort((a, b) => a.localeCompare(b, "zh-CN"));
101
+ };
102
+ const loadPanelPromptOptions = async () => {
103
+ const names = await withTimeout(redisClient.getPromptList(), 1200, "读取 ai:prompt:* 超时");
104
+ return withTimeout(Promise.all(names.map(async (name) => {
105
+ const content = await redisClient.getPrompt(name);
106
+ if (content === null)
107
+ return null;
108
+ return { name, content };
109
+ })), 1200, "读取 ai:prompt 详情超时").then((items) => items.filter((item) => Boolean(item)));
110
+ };
111
+ const loadPanelProps = async (page, ctx) => {
112
+ const panelStatus = getPanelQueryText(ctx, "panelStatus");
113
+ const panelMessage = getPanelQueryText(ctx, "panelMessage");
114
+ const restoreGroupListOpen = getPanelQueryText(ctx, "panelGroupListOpen") !== "0";
115
+ const restoreAiListOpen = getPanelQueryText(ctx, "panelAiListOpen") === "1";
116
+ const restoreOpenGroups = parsePanelOpenGroups(getPanelQueryText(ctx, "panelOpenGroups"));
117
+ const restoreOpenRows = parsePanelOpenRows(getPanelQueryText(ctx, "panelOpenRows"));
118
+ const restoreScrollY = parsePanelScrollY(getPanelQueryText(ctx, "panelScrollY"));
119
+ const chatConfigOpen = getPanelQueryText(ctx, "chatConfigOpen") === "1";
120
+ if (page === "chat") {
121
+ const identity = await resolvePanelChatIdentity(getPanelQueryText(ctx, "guid"), getPanelQueryText(ctx, "userId"), getPanelQueryText(ctx, "nickname"));
122
+ const chatMessages = await withTimeout(getPanelChatMessages(identity.guid, identity.userId), 1200, "读取网页聊天记录超时").catch(() => []);
123
+ const aiConfig = await withTimeout(redisClient.getAIConfig(identity.guid), 800, "读取当前聊天AI配置超时").catch(() => null);
124
+ const aiConfigs = await loadPanelAIConfigs().catch(() => []);
125
+ const currentAI = await withTimeout(redisClient.getCurrentAI(identity.guid), 800, "读取当前AI名称超时").catch(() => "");
126
+ const currentGroupConfig = await withTimeout(loadPanelGroupConfig(identity.guid), 1200, "读取当前群配置超时").catch(() => null);
127
+ const groupGuidOptions = await withTimeout(listPanelGroupGuids(), 1200, "读取群号列表超时").catch(() => []);
128
+ const workspaceNames = await withTimeout(loadPanelWorkspaceNames(), 1200, "读取工作区目录列表超时").catch(() => []);
129
+ const promptOptions = await loadPanelPromptOptions().catch(() => []);
130
+ const currentPrompt = promptOptions.find((item) => item.content === aiConfig?.systemPrompt)
131
+ ?.name || "";
132
+ return {
133
+ page,
134
+ currentGuid: identity.guid,
135
+ aiConfigs,
136
+ currentAI,
137
+ chatGuid: identity.guid,
138
+ chatUserId: identity.userId,
139
+ chatNickname: identity.nickname,
140
+ chatBotId: PANEL_BOT_ID,
141
+ chatMode: aiConfig?.rapi ? "rapi" : "capi",
142
+ chatModel: aiConfig?.model || "",
143
+ groupGuidOptions,
144
+ workspaceNames,
145
+ promptOptions,
146
+ currentPrompt,
147
+ chatMessages,
148
+ currentGroupConfig,
149
+ panelStatus,
150
+ panelMessage,
151
+ chatConfigOpen,
152
+ };
153
+ }
154
+ try {
155
+ const aiConfigs = await loadPanelAIConfigs();
156
+ const currentState = await withTimeout(getPanelCurrentAI(), 800, "读取 ai:currentAI 超时").catch(() => ({ currentAI: "", currentGuid: "global" }));
157
+ const groupConfigs = await withTimeout(loadPanelGroupConfigs(), 1500, "读取群配置列表超时").catch(() => []);
158
+ return {
159
+ page,
160
+ aiConfigs,
161
+ groupConfigs,
162
+ currentAI: currentState.currentAI,
163
+ currentGuid: currentState.currentGuid,
164
+ panelStatus,
165
+ panelMessage,
166
+ restoreGroupListOpen,
167
+ restoreOpenGroups,
168
+ restoreAiListOpen,
169
+ restoreOpenRows,
170
+ restoreScrollY,
171
+ };
172
+ }
173
+ catch (error) {
174
+ return {
175
+ page,
176
+ aiConfigs: [],
177
+ groupConfigs: [],
178
+ currentAI: "",
179
+ currentGuid: "global",
180
+ redisError: toErrorMessage(error),
181
+ panelStatus,
182
+ panelMessage,
183
+ restoreGroupListOpen,
184
+ restoreOpenGroups,
185
+ restoreAiListOpen,
186
+ restoreOpenRows,
187
+ restoreScrollY,
188
+ };
189
+ }
190
+ };
191
+ const statics = [
192
+ join(runtimeRoot, "assets"),
193
+ join(packageRoot, "public"),
194
+ join(process.cwd(), "public"),
195
+ ].filter((item, index, list) => list.indexOf(item) === index);
196
+ var panelConfig = defineConfig({
197
+ port: Number.isFinite(panelPort) && panelPort > 0 ? panelPort : 8080,
198
+ statics,
199
+ routes: {
200
+ "/": {
201
+ component: React.createElement(App$1, null),
202
+ },
203
+ "/panel": {
204
+ element: PanelPreview,
205
+ propsCall: (ctx) => loadPanelProps("config", ctx),
206
+ },
207
+ "/panel/config": {
208
+ element: PanelPreview,
209
+ propsCall: (ctx) => loadPanelProps("config", ctx),
210
+ },
211
+ "/panel/chat": {
212
+ element: PanelPreview,
213
+ propsCall: (ctx) => loadPanelProps("chat", ctx),
214
+ },
215
+ "/ai-config": {
216
+ component: (React.createElement(App, { switch: true, tools: true, complex: true, affection: true, tts: true, deep: false, model: "gpt-4", prompt: "\u4F60\u662F\u4E00\u4E2A\u53CB\u597D\u7684AI\u52A9\u624B", atTrigger: false, currentAI: "OpenAI", aiList: ["OpenAI", "Claude", "Gemini"], skillList: [
217
+ { name: "use-alemon", description: "控制AI配置和框架开关" },
218
+ { name: "send-media", description: "发送图片、音频和视频等媒体" },
219
+ ], toolList: { 搜索工具: "0", 翻译工具: "1" }, toolPromptSwitch: true, toolPromptRevokeSwitch: false, toolPromptArgsSwitch: true, toolCallContentSwitch: false })),
220
+ },
221
+ },
222
+ });
223
+
224
+ export { panelConfig as default };
@@ -0,0 +1,170 @@
1
+ import redisClient from '../config.js';
2
+
3
+ const panelGroupSwitchDefinitions = [
4
+ { key: "chatSwitch", label: "聊天总开关", desc: "控制这个群是否启用聊天能力" },
5
+ { key: "toolSwitch", label: "工具总开关", desc: "允许模型调用已启用工具" },
6
+ {
7
+ key: "toolCallContentSwitch",
8
+ label: "工具调用内容",
9
+ desc: "展示工具调用前的回复内容",
10
+ },
11
+ { key: "complexOutput", label: "复杂输出", desc: "保留更完整的富文本结果" },
12
+ { key: "affectionSwitch", label: "好感度系统", desc: "记录长期会话偏好" },
13
+ { key: "ttsResponseSwitch", label: "语音回复", desc: "允许启用 TTS 输出" },
14
+ { key: "deepThoughtSwitch", label: "深度思考", desc: "允许模型使用深度思考" },
15
+ { key: "r18Switch", label: "R18开关", desc: "允许当前群启用 R18 内容" },
16
+ { key: "atTriggerSwitch", label: "仅艾特触发", desc: "群聊中只响应艾特消息" },
17
+ { key: "toolPromptSwitch", label: "工具提示", desc: "工具调用时发送提示" },
18
+ {
19
+ key: "toolPromptRevokeSwitch",
20
+ label: "提示撤回",
21
+ desc: "工具提示发送后自动撤回",
22
+ },
23
+ {
24
+ key: "toolPromptArgsSwitch",
25
+ label: "提示参数",
26
+ desc: "工具提示里显示传参",
27
+ },
28
+ { key: "proactiveSwitch", label: "主动搭话", desc: "允许 AI 主动发起对话" },
29
+ ];
30
+ const panelGroupPrefixes = [
31
+ "ai:config:",
32
+ "ai:currentAI:",
33
+ "ai:tool:switch:",
34
+ "ai:tool_prompt:switch:",
35
+ "ai:tool_prompt_revoke:switch:",
36
+ "ai:tool_prompt_args:switch:",
37
+ "ai:tool_call_content:switch:",
38
+ "ai:complex_output:",
39
+ "ai:affection:switch:",
40
+ "ai:tts:response:",
41
+ "ai:deep_thought:switch:",
42
+ "ai:r18:switch:",
43
+ "ai:at_trigger:switch:",
44
+ "ai:proactive:switch:",
45
+ "ai:capi_context_limit:",
46
+ ];
47
+ const uniqueGuids = (groups) => [...new Set(groups.map((item) => item.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b, "zh-CN"));
48
+ const stripPrefix = (prefix, key) => key.replace(prefix, "");
49
+ const listPanelGroupGuids = async () => {
50
+ const keyGroups = await Promise.all(panelGroupPrefixes.map(async (prefix) => {
51
+ const keys = await redisClient.redis.keys(`${prefix}*`);
52
+ return keys.map((key) => stripPrefix(prefix, key));
53
+ }));
54
+ return uniqueGuids(keyGroups.flat());
55
+ };
56
+ const toBool = (value) => value === "1";
57
+ const loadPanelGroupConfig = async (guid) => {
58
+ const [currentAI, aiConfig, capiContextLimit, toolSwitch, toolCallContentSwitch, complexOutput, affectionSwitch, ttsResponseSwitch, deepThoughtSwitch, r18Switch, atTriggerSwitch, toolPromptSwitch, toolPromptRevokeSwitch, toolPromptArgsSwitch, proactiveSwitch,] = await Promise.all([
59
+ redisClient.getCurrentAI(guid),
60
+ redisClient.getAIConfig(guid),
61
+ redisClient.getCAPIContextLimit(guid),
62
+ redisClient.getToolSwitch(guid),
63
+ redisClient.getToolCallContentSwitch(guid),
64
+ redisClient.getComplexOutput(guid),
65
+ redisClient.getAffectionSwitch(guid),
66
+ redisClient.getTTSResponseSwitch(guid),
67
+ redisClient.getDeepThoughtSwitch(guid),
68
+ redisClient.getR18Switch(guid),
69
+ redisClient.getAtTriggerSwitch(guid),
70
+ redisClient.getToolPromptSwitch(guid),
71
+ redisClient.getToolPromptRevokeSwitch(guid),
72
+ redisClient.getToolPromptArgsSwitch(guid),
73
+ redisClient.getProactiveSwitch(guid),
74
+ ]);
75
+ return {
76
+ guid,
77
+ currentAI,
78
+ model: aiConfig.model || "",
79
+ capiContextLimit,
80
+ switches: {
81
+ chatSwitch: Boolean(aiConfig.model && aiConfig.model.trim()),
82
+ toolSwitch: toBool(toolSwitch),
83
+ toolCallContentSwitch: toBool(toolCallContentSwitch),
84
+ complexOutput: toBool(complexOutput),
85
+ affectionSwitch: toBool(affectionSwitch),
86
+ ttsResponseSwitch: toBool(ttsResponseSwitch),
87
+ deepThoughtSwitch: toBool(deepThoughtSwitch),
88
+ r18Switch: toBool(r18Switch),
89
+ atTriggerSwitch: toBool(atTriggerSwitch),
90
+ toolPromptSwitch: toBool(toolPromptSwitch),
91
+ toolPromptRevokeSwitch: toBool(toolPromptRevokeSwitch),
92
+ toolPromptArgsSwitch: toBool(toolPromptArgsSwitch),
93
+ proactiveSwitch: toBool(proactiveSwitch),
94
+ },
95
+ };
96
+ };
97
+ const loadPanelGroupConfigs = async () => {
98
+ const guids = await listPanelGroupGuids();
99
+ return Promise.all(guids.map((guid) => loadPanelGroupConfig(guid)));
100
+ };
101
+ const addPanelGroupConfig = async (input) => {
102
+ const guid = input.guid.trim();
103
+ if (!guid) {
104
+ throw new Error("缺少群号 guid");
105
+ }
106
+ const existingGuids = await listPanelGroupGuids();
107
+ if (existingGuids.includes(guid)) {
108
+ throw new Error(`群 ${guid} 已存在`);
109
+ }
110
+ const nextAI = String(input.currentAI || "").trim();
111
+ if (nextAI) {
112
+ const aiConfigStr = await redisClient.redis.get(`ai:list:${nextAI}`);
113
+ if (!aiConfigStr) {
114
+ throw new Error(`未找到AI:${nextAI}`);
115
+ }
116
+ }
117
+ // 先创建默认配置,让这个 guid 能被配置面板发现。
118
+ await redisClient.getAIConfig(guid);
119
+ if (nextAI) {
120
+ await redisClient.redis.set(`ai:currentAI:${guid}`, nextAI);
121
+ }
122
+ if (input.chatSwitch) {
123
+ if (!nextAI) {
124
+ throw new Error("开启聊天总开关前,请先选择一个AI");
125
+ }
126
+ const switched = await redisClient.switchAI(guid, nextAI);
127
+ if (!switched) {
128
+ throw new Error(`切换失败,未找到AI:${nextAI}`);
129
+ }
130
+ }
131
+ };
132
+ const updatePanelGroupConfig = async (input) => {
133
+ const guid = input.guid.trim();
134
+ if (!guid) {
135
+ throw new Error("缺少群号 guid");
136
+ }
137
+ const nextAI = String(input.currentAI || "").trim();
138
+ if (!input.switches.chatSwitch) {
139
+ await redisClient.closeChat(guid);
140
+ }
141
+ else {
142
+ const activeAI = nextAI || (await redisClient.getCurrentAI(guid));
143
+ if (!activeAI) {
144
+ throw new Error("请先选择一个AI,再开启聊天总开关");
145
+ }
146
+ const switched = await redisClient.switchAI(guid, activeAI);
147
+ if (!switched) {
148
+ throw new Error(`切换失败,未找到AI:${activeAI}`);
149
+ }
150
+ }
151
+ if (Number.isFinite(input.capiContextLimit) && input.capiContextLimit) {
152
+ await redisClient.setCAPIContextLimit(guid, Number(input.capiContextLimit));
153
+ }
154
+ await Promise.all([
155
+ redisClient.setToolSwitch(guid, input.switches.toolSwitch),
156
+ redisClient.setToolCallContentSwitch(guid, input.switches.toolCallContentSwitch),
157
+ redisClient.setComplexOutput(guid, input.switches.complexOutput),
158
+ redisClient.setAffectionSwitch(guid, input.switches.affectionSwitch),
159
+ redisClient.setTTSResponseSwitch(guid, input.switches.ttsResponseSwitch),
160
+ redisClient.setDeepThoughtSwitch(guid, input.switches.deepThoughtSwitch),
161
+ redisClient.setR18Switch(guid, input.switches.r18Switch),
162
+ redisClient.setAtTriggerSwitch(guid, input.switches.atTriggerSwitch),
163
+ redisClient.setToolPromptSwitch(guid, input.switches.toolPromptSwitch),
164
+ redisClient.setToolPromptRevokeSwitch(guid, input.switches.toolPromptRevokeSwitch),
165
+ redisClient.setToolPromptArgsSwitch(guid, input.switches.toolPromptArgsSwitch),
166
+ redisClient.setProactiveSwitch(guid, input.switches.proactiveSwitch),
167
+ ]);
168
+ };
169
+
170
+ export { addPanelGroupConfig, listPanelGroupGuids, loadPanelGroupConfig, loadPanelGroupConfigs, panelGroupSwitchDefinitions, updatePanelGroupConfig };