luckytri 1.0.0 → 1.0.2
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/.github/SECURITY.md +4 -4
- package/README.en.md +252 -250
- package/README.md +252 -250
- package/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +8 -0
- package/docs/en/connect.md +18 -18
- package/docs/en/guide.md +202 -216
- package/docs/en/npm-release.md +25 -25
- package/docs/zh/CHANGELOG.md +8 -0
- package/docs/zh/connect.md +17 -17
- package/docs/zh/guide.md +202 -216
- package/docs/zh/npm-release.md +33 -33
- package/package.json +110 -108
- package/plugins/sdk/index.d.ts +155 -155
- package/plugins/sdk/testing.js +58 -58
- package/plugins/weather/index.js +82 -82
- package/plugins/weather/luckytri-plugin.json +29 -29
- package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
- package/public/app/assets/Empty-DP6WMGHi.js +2 -0
- package/public/app/assets/Field-CtbdqaiB.js +1 -0
- package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
- package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
- package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
- package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
- package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
- package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
- package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
- package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
- package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
- package/public/app/assets/TimePage-7-awH5XL.js +3 -0
- package/public/app/assets/index-4rItIkD-.js +4 -0
- package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
- package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
- package/public/app/assets/media-SMfTT-yc.js +1 -0
- package/public/app/index.html +3 -3
- package/public/guide.en.html +72 -99
- package/public/guide.html +44 -86
- package/public/tutorial.en.md +25 -39
- package/public/tutorial.md +25 -39
- package/scripts/backup.js +311 -311
- package/scripts/browser.js +41 -41
- package/scripts/import-data.js +128 -0
- package/scripts/launch.js +101 -94
- package/scripts/plugin.js +150 -150
- package/scripts/reset-password.js +22 -0
- package/scripts/runtime.js +25 -25
- package/scripts/service.js +56 -47
- package/scripts/setup.js +50 -50
- package/scripts/start-server.js +41 -41
- package/scripts/stop.js +36 -36
- package/server/app.js +112 -96
- package/server/auth.js +217 -0
- package/server/channels/contract.js +57 -57
- package/server/channels/hub.js +165 -165
- package/server/channels/index.js +28 -25
- package/server/channels/onebot/index.js +335 -336
- package/server/channels/onebot/token.js +22 -3
- package/server/core/capabilities.js +208 -208
- package/server/core/events.js +64 -60
- package/server/core/image-preprocessor.js +208 -208
- package/server/core/model-manager.js +1213 -1213
- package/server/core/net-guard.js +49 -49
- package/server/core/orchestrator.js +569 -569
- package/server/core/perceivers.js +91 -91
- package/server/core/persona-manager.js +243 -243
- package/server/core/repository.js +293 -293
- package/server/core/response-validator.js +508 -508
- package/server/core/turn-processor.js +630 -630
- package/server/core/turn.js +236 -236
- package/server/core/vision-manager.js +633 -633
- package/server/i18n/errors.js +548 -497
- package/server/i18n/labels.js +649 -653
- package/server/index.js +151 -102
- package/server/knowledge/manager.js +386 -386
- package/server/knowledge/schema.js +12 -8
- package/server/local-access.js +32 -0
- package/server/mind/anticipations.js +507 -507
- package/server/mind/api.js +703 -703
- package/server/mind/budget.js +152 -152
- package/server/mind/context-selection.js +61 -61
- package/server/mind/day-planner.js +324 -324
- package/server/mind/evidence.js +148 -148
- package/server/mind/index.js +445 -445
- package/server/mind/life/diary.js +786 -786
- package/server/mind/life/index.js +1049 -1049
- package/server/mind/observations.js +84 -84
- package/server/mind/own-day.js +324 -324
- package/server/mind/own-voice.js +318 -318
- package/server/mind/senses.js +53 -53
- package/server/mind/time/activity-clock.js +196 -196
- package/server/mind/time/availability.js +69 -69
- package/server/mind/time/commitment-review.js +198 -198
- package/server/mind/time/executor.js +512 -512
- package/server/mind/time/experiences.js +128 -128
- package/server/mind/time/games.js +562 -562
- package/server/mind/time/index.js +739 -739
- package/server/mind/time/intent.js +166 -166
- package/server/mind/time/kinds.js +171 -171
- package/server/mind/time/plugin-activity.js +207 -207
- package/server/mind/time/tasks.js +587 -587
- package/server/mind/util.js +110 -110
- package/server/mind/view.js +273 -273
- package/server/paths.js +102 -102
- package/server/plugins/api.js +204 -204
- package/server/plugins/host.js +967 -967
- package/server/plugins/install.js +295 -295
- package/server/plugins/manifest.js +201 -201
- package/server/plugins/permissions.js +42 -42
- package/server/plugins/runner/guard.mjs +36 -36
- package/server/plugins/runner/worker.mjs +250 -250
- package/server/plugins/schema.js +91 -91
- package/server/plugins/zip.js +112 -112
- package/server/startup-lock.js +118 -118
- package/server/storage/archive.js +242 -241
- package/server/storage/backup-cleanup-worker.js +8 -8
- package/server/storage/backup-cleanup.js +415 -414
- package/server/storage/backup-lock.js +76 -76
- package/server/storage/backup-scheduler.js +131 -131
- package/server/storage/data-package.js +298 -0
- package/server/storage/data-transfer-api.js +50 -0
- package/server/storage/data-transfer-worker.js +18 -0
- package/server/storage/data-transfer.js +415 -0
- package/server/storage/store.js +202 -199
- package/server/studio/readiness.js +181 -190
- package/server/studio/settings.js +236 -236
- package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
- package/public/app/assets/Empty-CpP5kQHH.js +0 -2
- package/public/app/assets/Field-D4gY-INU.js +0 -1
- package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
- package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
- package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
- package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
- package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
- package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
- package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
- package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
- package/public/app/assets/TimePage-Cw44inM4.js +0 -3
- package/public/app/assets/index-BheRW-1t.js +0 -4
- package/public/app/assets/media-BzPDBm7e.js +0 -1
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
import { runtimePaths } from "../paths.js";
|
|
2
|
-
import { VERSION } from "../version.js";
|
|
3
|
-
import { publicSession } from "../channels/session-key.js";
|
|
4
|
-
import { CHANNEL_TYPES } from "../channels/hub.js";
|
|
5
|
-
import { readiness } from "./readiness.js";
|
|
6
|
-
import { FEEDBACK_LABELS } from "../core/feedback.js";
|
|
7
|
-
import {
|
|
8
|
-
EFFORT_LABELS,
|
|
9
|
-
MODEL_CATALOG,
|
|
10
|
-
MODEL_PRESETS,
|
|
11
|
-
REASONING_EFFORTS,
|
|
12
|
-
} from "../core/model-presets.js";
|
|
13
|
-
import { applySpeakerNames, speakerNames } from "../core/speaker-names.js";
|
|
14
|
-
import { displayNames, displaySession } from "./display-names.js";
|
|
15
|
-
|
|
16
|
-
export function mountStudio(app, store, runtime, chat) {
|
|
17
|
-
app.get("/api/storage/cleanup", (req, res) => {
|
|
18
|
-
if (!runtime.cleanup)
|
|
19
|
-
return res.status(501).json({ error: "备份整理不可用" });
|
|
20
|
-
res.json(runtime.cleanup.info());
|
|
21
|
-
});
|
|
22
|
-
app.patch("/api/storage/cleanup", (req, res) => {
|
|
23
|
-
if (!runtime.cleanup)
|
|
24
|
-
return res.status(501).json({ error: "备份整理不可用" });
|
|
25
|
-
try {
|
|
26
|
-
res.json(runtime.cleanup.save(req.body));
|
|
27
|
-
} catch (error) {
|
|
28
|
-
res.status(400).json({ error: error.message });
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
app.post("/api/storage/cleanup/manual", async (req, res) => {
|
|
32
|
-
if (!runtime.cleanup)
|
|
33
|
-
return res.status(501).json({ error: "备份整理不可用" });
|
|
34
|
-
try {
|
|
35
|
-
res.json(await runtime.cleanup.manual(req.body.items));
|
|
36
|
-
} catch (error) {
|
|
37
|
-
res.status(409).json({ error: error.message });
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
app.get("/api/service/status", (req, res) =>
|
|
41
|
-
res.json({
|
|
42
|
-
app: "luckybot",
|
|
43
|
-
workspace: runtimePaths().package,
|
|
44
|
-
home: runtimePaths().home,
|
|
45
|
-
version: VERSION,
|
|
46
|
-
pid: process.pid,
|
|
47
|
-
schemaVersion: store.db.prepare("PRAGMA user_version").get().user_version,
|
|
48
|
-
}),
|
|
49
|
-
);
|
|
50
|
-
app.post("/api/service/stop", (req, res) => {
|
|
51
|
-
res.json({ ok: true });
|
|
52
|
-
setTimeout(runtime.shutdown, 150);
|
|
53
|
-
});
|
|
54
|
-
app.get("/api/state", (req, res) => {
|
|
55
|
-
// Secrets are written from the studio and never read back into it.
|
|
56
|
-
const {
|
|
57
|
-
apiKey,
|
|
58
|
-
qqbotSecret,
|
|
59
|
-
onebotToken: _onebotToken,
|
|
60
|
-
...settings
|
|
61
|
-
} = store.settings();
|
|
62
|
-
const demo = Number(settings.demo);
|
|
63
|
-
const connection = runtime.connection();
|
|
64
|
-
const namesForDisplay = displayNames(store.db, chat?.now() ?? Date.now());
|
|
65
|
-
res.json({
|
|
66
|
-
settings: {
|
|
67
|
-
...settings,
|
|
68
|
-
hasApiKey: !!(apiKey || process.env.LLM_API_KEY),
|
|
69
|
-
hasQqbotSecret: !!(qqbotSecret || process.env.QQBOT_APP_SECRET),
|
|
70
|
-
},
|
|
71
|
-
readiness: readiness(store, {
|
|
72
|
-
online: connection.online,
|
|
73
|
-
tokenConfigured: connection.onebot?.tokenConfigured,
|
|
74
|
-
channel: connection.channel,
|
|
75
|
-
qqbot: connection.qqbot,
|
|
76
|
-
}),
|
|
77
|
-
feedbackLabels: FEEDBACK_LABELS,
|
|
78
|
-
modelPresets: MODEL_PRESETS,
|
|
79
|
-
modelCatalog: MODEL_CATALOG,
|
|
80
|
-
reasoningEfforts: REASONING_EFFORTS,
|
|
81
|
-
effortLabels: EFFORT_LABELS,
|
|
82
|
-
connection,
|
|
83
|
-
backup: runtime.backup?.() ?? null,
|
|
84
|
-
sessions: store.db
|
|
85
|
-
.prepare(
|
|
86
|
-
"SELECT s.*, (SELECT text FROM messages WHERE session_id=s.id AND is_demo=? ORDER BY id DESC LIMIT 1) preview,(SELECT COUNT(*) FROM messages WHERE session_id=s.id AND is_demo=?) message_count FROM sessions s ORDER BY s.id",
|
|
87
|
-
)
|
|
88
|
-
.all(demo, demo)
|
|
89
|
-
.map((session) => ({
|
|
90
|
-
...displaySession(publicSession(session), namesForDisplay),
|
|
91
|
-
style: store.groupStyle(session.id, demo),
|
|
92
|
-
})),
|
|
93
|
-
decisions: (() => {
|
|
94
|
-
const rows = store.db
|
|
95
|
-
.prepare(
|
|
96
|
-
"SELECT d.*,f.tag AS feedback FROM decisions d LEFT JOIN reply_feedback f ON f.decision_id=d.id WHERE d.is_demo=? ORDER BY d.id DESC LIMIT 80",
|
|
97
|
-
)
|
|
98
|
-
.all(demo);
|
|
99
|
-
const names = speakerNames(
|
|
100
|
-
store.db,
|
|
101
|
-
rows.map((row) => row.session_id),
|
|
102
|
-
);
|
|
103
|
-
return rows.map((row) => ({
|
|
104
|
-
...row,
|
|
105
|
-
reason: applySpeakerNames(row.reason, names),
|
|
106
|
-
}));
|
|
107
|
-
})(),
|
|
108
|
-
stats: {
|
|
109
|
-
messages: store.db
|
|
110
|
-
.prepare("SELECT COUNT(*) n FROM messages WHERE is_demo=?")
|
|
111
|
-
.get(demo).n,
|
|
112
|
-
replies: store.db
|
|
113
|
-
.prepare(
|
|
114
|
-
"SELECT COUNT(*) n FROM decisions WHERE reply!='' AND is_demo=?",
|
|
115
|
-
)
|
|
116
|
-
.get(demo).n,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
// Connection and runtime switches only. Who she is lives in her nature;
|
|
121
|
-
// whether she speaks is hers to decide.
|
|
122
|
-
app.patch("/api/settings", (req, res) => {
|
|
123
|
-
const v = req.body;
|
|
124
|
-
const allowed = {};
|
|
125
|
-
for (const k of [
|
|
126
|
-
"aliases",
|
|
127
|
-
"baseUrl",
|
|
128
|
-
"model",
|
|
129
|
-
"apiKey",
|
|
130
|
-
"providerPreset",
|
|
131
|
-
"reasoningEffort",
|
|
132
|
-
"channel",
|
|
133
|
-
"qqbotAppId",
|
|
134
|
-
"qqbotSecret",
|
|
135
|
-
])
|
|
136
|
-
if (k in v) {
|
|
137
|
-
if (typeof v[k] !== "string" || v[k].length > 4000)
|
|
138
|
-
return res.status(400).json({ error: "文本配置无效" });
|
|
139
|
-
allowed[k] = v[k];
|
|
140
|
-
}
|
|
141
|
-
if ("channel" in allowed) {
|
|
142
|
-
if (!CHANNEL_TYPES.includes(allowed.channel))
|
|
143
|
-
return res.status(400).json({ error: "接入方式无效" });
|
|
144
|
-
const forced = String(process.env.LUCKYTRI_CHANNEL || "").trim();
|
|
145
|
-
if (forced && forced !== allowed.channel)
|
|
146
|
-
return res
|
|
147
|
-
.status(409)
|
|
148
|
-
.json({ error: "接入方式已由环境变量 LUCKYTRI_CHANNEL 固定" });
|
|
149
|
-
}
|
|
150
|
-
if ("qqbotAppId" in allowed) {
|
|
151
|
-
allowed.qqbotAppId = allowed.qqbotAppId.trim();
|
|
152
|
-
if (
|
|
153
|
-
allowed.qqbotAppId &&
|
|
154
|
-
!/^[0-9A-Za-z_-]{1,40}$/.test(allowed.qqbotAppId)
|
|
155
|
-
)
|
|
156
|
-
return res.status(400).json({ error: "AppID 格式无效" });
|
|
157
|
-
}
|
|
158
|
-
if ("qqbotSecret" in allowed) {
|
|
159
|
-
allowed.qqbotSecret = allowed.qqbotSecret.trim();
|
|
160
|
-
if (allowed.qqbotSecret.length > 200)
|
|
161
|
-
return res.status(400).json({ error: "AppSecret 格式无效" });
|
|
162
|
-
}
|
|
163
|
-
for (const k of ["enabled", "demo", "memoryEnabled", "memoryCandidates"])
|
|
164
|
-
if (k in v) {
|
|
165
|
-
if (typeof v[k] !== "boolean")
|
|
166
|
-
return res.status(400).json({ error: "开关配置无效" });
|
|
167
|
-
allowed[k] = v[k];
|
|
168
|
-
}
|
|
169
|
-
for (const [k, min, max] of [
|
|
170
|
-
["temperature", 0, 2],
|
|
171
|
-
["topP", 0, 1],
|
|
172
|
-
["maxTokens", 64, 2000],
|
|
173
|
-
])
|
|
174
|
-
if (k in v) {
|
|
175
|
-
if (
|
|
176
|
-
!Number.isFinite(v[k]) ||
|
|
177
|
-
v[k] < min ||
|
|
178
|
-
v[k] > max ||
|
|
179
|
-
(k === "maxTokens" && !Number.isInteger(v[k]))
|
|
180
|
-
)
|
|
181
|
-
return res.status(400).json({ error: "数值配置超出范围" });
|
|
182
|
-
allowed[k] = v[k];
|
|
183
|
-
}
|
|
184
|
-
const retired = [
|
|
185
|
-
"probability",
|
|
186
|
-
"cooldown",
|
|
187
|
-
"voicePreset",
|
|
188
|
-
"slangLevel",
|
|
189
|
-
"allowMildProfanity",
|
|
190
|
-
"qualityRewrite",
|
|
191
|
-
"adaptGroupStyle",
|
|
192
|
-
"persona",
|
|
193
|
-
"name",
|
|
194
|
-
].filter((k) => k in v);
|
|
195
|
-
if (retired.length)
|
|
196
|
-
return res.status(400).json({
|
|
197
|
-
error: `这些设置已经不存在:${retired.join("、")}。名字和天性在「TA → 天性」里修改`,
|
|
198
|
-
});
|
|
199
|
-
if (
|
|
200
|
-
"providerPreset" in allowed &&
|
|
201
|
-
!Object.hasOwn(MODEL_PRESETS, allowed.providerPreset)
|
|
202
|
-
)
|
|
203
|
-
return res.status(400).json({ error: "模型服务商预设无效" });
|
|
204
|
-
if (
|
|
205
|
-
"reasoningEffort" in allowed &&
|
|
206
|
-
!REASONING_EFFORTS.some((item) => item.id === allowed.reasoningEffort)
|
|
207
|
-
)
|
|
208
|
-
return res.status(400).json({ error: "思考强度无效" });
|
|
209
|
-
if ("baseUrl" in allowed) {
|
|
210
|
-
try {
|
|
211
|
-
const u = new URL(allowed.baseUrl);
|
|
212
|
-
if (
|
|
213
|
-
!["http:", "https:"].includes(u.protocol) ||
|
|
214
|
-
u.username ||
|
|
215
|
-
u.password ||
|
|
216
|
-
u.search ||
|
|
217
|
-
u.hash
|
|
218
|
-
)
|
|
219
|
-
throw 0;
|
|
220
|
-
} catch {
|
|
221
|
-
return res.status(400).json({ error: "API 地址无效" });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if ("model" in allowed && !allowed.model.trim())
|
|
225
|
-
return res.status(400).json({ error: "模型名称不能为空" });
|
|
226
|
-
store.save(allowed);
|
|
227
|
-
// A new channel or new credentials take effect now, without a restart.
|
|
228
|
-
if (
|
|
229
|
-
"channel" in allowed ||
|
|
230
|
-
"qqbotAppId" in allowed ||
|
|
231
|
-
"qqbotSecret" in allowed
|
|
232
|
-
)
|
|
233
|
-
runtime.syncChannel?.();
|
|
234
|
-
res.json({ ok: true });
|
|
235
|
-
});
|
|
236
|
-
}
|
|
1
|
+
import { runtimePaths } from "../paths.js";
|
|
2
|
+
import { VERSION } from "../version.js";
|
|
3
|
+
import { publicSession } from "../channels/session-key.js";
|
|
4
|
+
import { CHANNEL_TYPES } from "../channels/hub.js";
|
|
5
|
+
import { readiness } from "./readiness.js";
|
|
6
|
+
import { FEEDBACK_LABELS } from "../core/feedback.js";
|
|
7
|
+
import {
|
|
8
|
+
EFFORT_LABELS,
|
|
9
|
+
MODEL_CATALOG,
|
|
10
|
+
MODEL_PRESETS,
|
|
11
|
+
REASONING_EFFORTS,
|
|
12
|
+
} from "../core/model-presets.js";
|
|
13
|
+
import { applySpeakerNames, speakerNames } from "../core/speaker-names.js";
|
|
14
|
+
import { displayNames, displaySession } from "./display-names.js";
|
|
15
|
+
|
|
16
|
+
export function mountStudio(app, store, runtime, chat) {
|
|
17
|
+
app.get("/api/storage/cleanup", (req, res) => {
|
|
18
|
+
if (!runtime.cleanup)
|
|
19
|
+
return res.status(501).json({ error: "备份整理不可用" });
|
|
20
|
+
res.json(runtime.cleanup.info());
|
|
21
|
+
});
|
|
22
|
+
app.patch("/api/storage/cleanup", (req, res) => {
|
|
23
|
+
if (!runtime.cleanup)
|
|
24
|
+
return res.status(501).json({ error: "备份整理不可用" });
|
|
25
|
+
try {
|
|
26
|
+
res.json(runtime.cleanup.save(req.body));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
res.status(400).json({ error: error.message });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
app.post("/api/storage/cleanup/manual", async (req, res) => {
|
|
32
|
+
if (!runtime.cleanup)
|
|
33
|
+
return res.status(501).json({ error: "备份整理不可用" });
|
|
34
|
+
try {
|
|
35
|
+
res.json(await runtime.cleanup.manual(req.body.items));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
res.status(409).json({ error: error.message });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
app.get("/api/service/status", (req, res) =>
|
|
41
|
+
res.json({
|
|
42
|
+
app: "luckybot",
|
|
43
|
+
workspace: runtimePaths().package,
|
|
44
|
+
home: runtimePaths().home,
|
|
45
|
+
version: VERSION,
|
|
46
|
+
pid: process.pid,
|
|
47
|
+
schemaVersion: store.db.prepare("PRAGMA user_version").get().user_version,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
app.post("/api/service/stop", (req, res) => {
|
|
51
|
+
res.json({ ok: true });
|
|
52
|
+
setTimeout(runtime.shutdown, 150);
|
|
53
|
+
});
|
|
54
|
+
app.get("/api/state", (req, res) => {
|
|
55
|
+
// Secrets are written from the studio and never read back into it.
|
|
56
|
+
const {
|
|
57
|
+
apiKey,
|
|
58
|
+
qqbotSecret,
|
|
59
|
+
onebotToken: _onebotToken,
|
|
60
|
+
...settings
|
|
61
|
+
} = store.settings();
|
|
62
|
+
const demo = Number(settings.demo);
|
|
63
|
+
const connection = runtime.connection();
|
|
64
|
+
const namesForDisplay = displayNames(store.db, chat?.now() ?? Date.now());
|
|
65
|
+
res.json({
|
|
66
|
+
settings: {
|
|
67
|
+
...settings,
|
|
68
|
+
hasApiKey: !!(apiKey || process.env.LLM_API_KEY),
|
|
69
|
+
hasQqbotSecret: !!(qqbotSecret || process.env.QQBOT_APP_SECRET),
|
|
70
|
+
},
|
|
71
|
+
readiness: readiness(store, {
|
|
72
|
+
online: connection.online,
|
|
73
|
+
tokenConfigured: connection.onebot?.tokenConfigured,
|
|
74
|
+
channel: connection.channel,
|
|
75
|
+
qqbot: connection.qqbot,
|
|
76
|
+
}),
|
|
77
|
+
feedbackLabels: FEEDBACK_LABELS,
|
|
78
|
+
modelPresets: MODEL_PRESETS,
|
|
79
|
+
modelCatalog: MODEL_CATALOG,
|
|
80
|
+
reasoningEfforts: REASONING_EFFORTS,
|
|
81
|
+
effortLabels: EFFORT_LABELS,
|
|
82
|
+
connection,
|
|
83
|
+
backup: runtime.backup?.() ?? null,
|
|
84
|
+
sessions: store.db
|
|
85
|
+
.prepare(
|
|
86
|
+
"SELECT s.*, (SELECT text FROM messages WHERE session_id=s.id AND is_demo=? ORDER BY id DESC LIMIT 1) preview,(SELECT COUNT(*) FROM messages WHERE session_id=s.id AND is_demo=?) message_count FROM sessions s ORDER BY s.id",
|
|
87
|
+
)
|
|
88
|
+
.all(demo, demo)
|
|
89
|
+
.map((session) => ({
|
|
90
|
+
...displaySession(publicSession(session), namesForDisplay),
|
|
91
|
+
style: store.groupStyle(session.id, demo),
|
|
92
|
+
})),
|
|
93
|
+
decisions: (() => {
|
|
94
|
+
const rows = store.db
|
|
95
|
+
.prepare(
|
|
96
|
+
"SELECT d.*,f.tag AS feedback FROM decisions d LEFT JOIN reply_feedback f ON f.decision_id=d.id WHERE d.is_demo=? ORDER BY d.id DESC LIMIT 80",
|
|
97
|
+
)
|
|
98
|
+
.all(demo);
|
|
99
|
+
const names = speakerNames(
|
|
100
|
+
store.db,
|
|
101
|
+
rows.map((row) => row.session_id),
|
|
102
|
+
);
|
|
103
|
+
return rows.map((row) => ({
|
|
104
|
+
...row,
|
|
105
|
+
reason: applySpeakerNames(row.reason, names),
|
|
106
|
+
}));
|
|
107
|
+
})(),
|
|
108
|
+
stats: {
|
|
109
|
+
messages: store.db
|
|
110
|
+
.prepare("SELECT COUNT(*) n FROM messages WHERE is_demo=?")
|
|
111
|
+
.get(demo).n,
|
|
112
|
+
replies: store.db
|
|
113
|
+
.prepare(
|
|
114
|
+
"SELECT COUNT(*) n FROM decisions WHERE reply!='' AND is_demo=?",
|
|
115
|
+
)
|
|
116
|
+
.get(demo).n,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
// Connection and runtime switches only. Who she is lives in her nature;
|
|
121
|
+
// whether she speaks is hers to decide.
|
|
122
|
+
app.patch("/api/settings", (req, res) => {
|
|
123
|
+
const v = req.body;
|
|
124
|
+
const allowed = {};
|
|
125
|
+
for (const k of [
|
|
126
|
+
"aliases",
|
|
127
|
+
"baseUrl",
|
|
128
|
+
"model",
|
|
129
|
+
"apiKey",
|
|
130
|
+
"providerPreset",
|
|
131
|
+
"reasoningEffort",
|
|
132
|
+
"channel",
|
|
133
|
+
"qqbotAppId",
|
|
134
|
+
"qqbotSecret",
|
|
135
|
+
])
|
|
136
|
+
if (k in v) {
|
|
137
|
+
if (typeof v[k] !== "string" || v[k].length > 4000)
|
|
138
|
+
return res.status(400).json({ error: "文本配置无效" });
|
|
139
|
+
allowed[k] = v[k];
|
|
140
|
+
}
|
|
141
|
+
if ("channel" in allowed) {
|
|
142
|
+
if (!CHANNEL_TYPES.includes(allowed.channel))
|
|
143
|
+
return res.status(400).json({ error: "接入方式无效" });
|
|
144
|
+
const forced = String(process.env.LUCKYTRI_CHANNEL || "").trim();
|
|
145
|
+
if (forced && forced !== allowed.channel)
|
|
146
|
+
return res
|
|
147
|
+
.status(409)
|
|
148
|
+
.json({ error: "接入方式已由环境变量 LUCKYTRI_CHANNEL 固定" });
|
|
149
|
+
}
|
|
150
|
+
if ("qqbotAppId" in allowed) {
|
|
151
|
+
allowed.qqbotAppId = allowed.qqbotAppId.trim();
|
|
152
|
+
if (
|
|
153
|
+
allowed.qqbotAppId &&
|
|
154
|
+
!/^[0-9A-Za-z_-]{1,40}$/.test(allowed.qqbotAppId)
|
|
155
|
+
)
|
|
156
|
+
return res.status(400).json({ error: "AppID 格式无效" });
|
|
157
|
+
}
|
|
158
|
+
if ("qqbotSecret" in allowed) {
|
|
159
|
+
allowed.qqbotSecret = allowed.qqbotSecret.trim();
|
|
160
|
+
if (allowed.qqbotSecret.length > 200)
|
|
161
|
+
return res.status(400).json({ error: "AppSecret 格式无效" });
|
|
162
|
+
}
|
|
163
|
+
for (const k of ["enabled", "demo", "memoryEnabled", "memoryCandidates"])
|
|
164
|
+
if (k in v) {
|
|
165
|
+
if (typeof v[k] !== "boolean")
|
|
166
|
+
return res.status(400).json({ error: "开关配置无效" });
|
|
167
|
+
allowed[k] = v[k];
|
|
168
|
+
}
|
|
169
|
+
for (const [k, min, max] of [
|
|
170
|
+
["temperature", 0, 2],
|
|
171
|
+
["topP", 0, 1],
|
|
172
|
+
["maxTokens", 64, 2000],
|
|
173
|
+
])
|
|
174
|
+
if (k in v) {
|
|
175
|
+
if (
|
|
176
|
+
!Number.isFinite(v[k]) ||
|
|
177
|
+
v[k] < min ||
|
|
178
|
+
v[k] > max ||
|
|
179
|
+
(k === "maxTokens" && !Number.isInteger(v[k]))
|
|
180
|
+
)
|
|
181
|
+
return res.status(400).json({ error: "数值配置超出范围" });
|
|
182
|
+
allowed[k] = v[k];
|
|
183
|
+
}
|
|
184
|
+
const retired = [
|
|
185
|
+
"probability",
|
|
186
|
+
"cooldown",
|
|
187
|
+
"voicePreset",
|
|
188
|
+
"slangLevel",
|
|
189
|
+
"allowMildProfanity",
|
|
190
|
+
"qualityRewrite",
|
|
191
|
+
"adaptGroupStyle",
|
|
192
|
+
"persona",
|
|
193
|
+
"name",
|
|
194
|
+
].filter((k) => k in v);
|
|
195
|
+
if (retired.length)
|
|
196
|
+
return res.status(400).json({
|
|
197
|
+
error: `这些设置已经不存在:${retired.join("、")}。名字和天性在「TA → 天性」里修改`,
|
|
198
|
+
});
|
|
199
|
+
if (
|
|
200
|
+
"providerPreset" in allowed &&
|
|
201
|
+
!Object.hasOwn(MODEL_PRESETS, allowed.providerPreset)
|
|
202
|
+
)
|
|
203
|
+
return res.status(400).json({ error: "模型服务商预设无效" });
|
|
204
|
+
if (
|
|
205
|
+
"reasoningEffort" in allowed &&
|
|
206
|
+
!REASONING_EFFORTS.some((item) => item.id === allowed.reasoningEffort)
|
|
207
|
+
)
|
|
208
|
+
return res.status(400).json({ error: "思考强度无效" });
|
|
209
|
+
if ("baseUrl" in allowed) {
|
|
210
|
+
try {
|
|
211
|
+
const u = new URL(allowed.baseUrl);
|
|
212
|
+
if (
|
|
213
|
+
!["http:", "https:"].includes(u.protocol) ||
|
|
214
|
+
u.username ||
|
|
215
|
+
u.password ||
|
|
216
|
+
u.search ||
|
|
217
|
+
u.hash
|
|
218
|
+
)
|
|
219
|
+
throw 0;
|
|
220
|
+
} catch {
|
|
221
|
+
return res.status(400).json({ error: "API 地址无效" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if ("model" in allowed && !allowed.model.trim())
|
|
225
|
+
return res.status(400).json({ error: "模型名称不能为空" });
|
|
226
|
+
store.save(allowed);
|
|
227
|
+
// A new channel or new credentials take effect now, without a restart.
|
|
228
|
+
if (
|
|
229
|
+
"channel" in allowed ||
|
|
230
|
+
"qqbotAppId" in allowed ||
|
|
231
|
+
"qqbotSecret" in allowed
|
|
232
|
+
)
|
|
233
|
+
runtime.syncChannel?.();
|
|
234
|
+
res.json({ ok: true });
|
|
235
|
+
});
|
|
236
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{B as e,D as t,F as n,H as r,I as i,K as a,L as o,M as s,P as c,T as l,U as u,V as d,W as f,X as p,Y as m,Z as h,_t as g,a as _,ct as v,dt as y,f as b,ft as x,gt as S,it as C,k as w,lt as T,m as E,mt as D,n as O,nt as k,o as A,q as j,rt as M,s as N,t as P,tt as ee,v as F,vt as I,w as L,y as R,yt as z}from"./Empty-CpP5kQHH.js";import{t as B}from"./formatters-ZmW_xFHw.js";import{F as te,c as V,f as H,h as U,j as W,m as G,n as K,o as ne,p as q,u as J}from"./index-BheRW-1t.js";import{t as re}from"./media-BzPDBm7e.js";import{t as Y}from"./Field-D4gY-INU.js";async function ie(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),E(`/core/events?`+n.toString())}async function ae(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),E(`/core/traces?`+n.toString())}async function X(e){return E(`/core/traces/`+e)}async function oe(e){return E(`/simulate`,`POST`,e)}async function se(e,t){return E(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function Z(e){return E(`/core/replay`,`POST`,e)}async function ce(e){return E(`/core/sessions`,`POST`,e)}async function le(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function ue(e,t){return E(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function de(e){return E(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function fe(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function pe(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function me(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var he={class:`session-list`},ge={class:`list-head`},Q=[`placeholder`,`aria-label`],_e={class:`rows scroll-pane`},ve=[`data-pick`,`aria-current`,`onClick`],ye={class:`who`},be=[`aria-label`],xe=[`title`],Se=[`checked`,`aria-label`,`onChange`],Ce={key:0,class:`muted empty-line`},we={key:1,class:`archive-list`},Te={class:`row`},Ee=[`data-restore-session`,`onClick`],De=[`data-delete-session`,`onClick`],Oe=O(m({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(t,{emit:n}){let r=t,i=n,s=D(``),l=e(()=>r.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(s.value.toLowerCase()))),p=e(()=>r.sessions.filter(e=>e.archived));return(e,n)=>(M(),f(`div`,he,[d(`div`,ge,[d(`h2`,null,[a(z(S(w)(`会话`))+` `,1),d(`small`,null,z(l.value.length),1)]),d(`button`,{class:`small primary`,onClick:n[0]||=e=>i(`add`)},z(S(w)(`+ 添加会话`)),1)]),x(d(`input`,{"onUpdate:modelValue":n[1]||=e=>s.value=e,type:`search`,placeholder:S(w)(`搜索名称或号码`),"aria-label":S(w)(`搜索会话`)},null,8,Q),[[c,s.value]]),d(`div`,_e,[(M(!0),f(o,null,C(l.value,e=>(M(),f(`div`,{key:e.id,class:g([`session-row`,{selected:e.id===t.selected,paused:!e.enabled}])},[d(`button`,{class:`row-main`,"data-pick":e.id,"aria-current":e.id===t.selected?`true`:void 0,onClick:t=>i(`select`,e.id)},[d(`span`,{class:`badge`,style:I({"--hue":S(H)(e.id)})},z(e.kind===`private`?S(w)(`私`):S(w)(`群`)),5),d(`span`,ye,[d(`b`,null,z(S(U)(e)),1),d(`small`,null,z(e.enabled?S(w)(`参与中`):S(w)(`已暂停`)),1)]),t.unread[e.id]?(M(),f(`span`,{key:0,class:`unread`,"aria-label":S(w)(`{v} 条没细看`,{v:t.unread[e.id]})},z(t.unread[e.id]),9,be)):u(``,!0)],8,ve),d(`label`,{class:`mini-switch`,title:e.enabled?S(w)(`暂停参与`):S(w)(`开启参与`)},[d(`input`,{type:`checkbox`,checked:e.enabled,"aria-label":S(w)(`参与:{v}`,{v:S(U)(e)}),onChange:t=>i(`toggle`,e)},null,40,Se)],8,xe)],2))),128)),l.value.length?u(``,!0):(M(),f(`p`,Ce,z(S(w)(`暂无匹配的会话。`)),1)),p.value.length?(M(),f(`details`,we,[d(`summary`,null,z(S(w)(`已归档 · {length}`,{length:p.value.length})),1),(M(!0),f(o,null,C(p.value,e=>(M(),f(`article`,{key:e.id},[d(`b`,null,z(S(U)(e)),1),d(`div`,Te,[d(`button`,{class:`small`,"data-restore-session":e.id,onClick:t=>i(`restore`,e.id)},z(S(w)(`恢复`)),9,Ee),d(`button`,{class:`small danger`,"data-delete-session":e.id,onClick:t=>i(`remove`,e.id)},z(S(w)(`永久删除`)),9,De)])]))),128))])):u(``,!0)])]))}}),[[`__scopeId`,`data-v-9b8dcbac`]]),ke={class:`muted`},Ae={key:0,class:`notice`,role:`status`},je=[`pattern`,`inputmode`],Me=[`disabled`],Ne=m({__name:`AddSession`,props:{open:{type:Boolean},channel:{},ready:{type:Boolean}},emits:[`close`,`add`],setup(t,{emit:n}){let a=t,o=D(`group`),s=n,c=e(()=>a.channel===`qqbot`),l=e(()=>c.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function p(e){let t=e.target;s(`add`,{...Object.fromEntries(new FormData(t)),channel:a.channel})}return(e,n)=>(M(),r(ne,{open:t.open,title:S(w)(`添加群聊 / 私聊`),eyebrow:S(w)(`NEW PLACE`),width:`440px`,onClose:n[1]||=e=>s(`close`)},{default:y(()=>[d(`form`,{id:`addSession`,class:`stack`,onSubmit:i(p,[`prevent`])},[d(`p`,ke,z(S(w)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),c.value&&!t.ready?(M(),f(`p`,Ae,z(S(w)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):u(``,!0),j(Y,{label:S(w)(`类型`)},{default:y(()=>[j(V,{modelValue:o.value,"onUpdate:modelValue":n[0]||=e=>o.value=e,name:`kind`,"aria-label":S(w)(`类型`),options:[{value:`group`,label:S(w)(`群聊`)},{value:`private`,label:S(w)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),j(Y,{label:c.value?S(w)(`群 openid / 用户 openid`):S(w)(`群号 / QQ 号`),hint:c.value?S(w)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):S(w)(`4–20 位数字`)},{default:y(()=>[d(`input`,{name:`id`,required:``,pattern:l.value,inputmode:c.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),j(Y,{label:S(w)(`显示名称`)},{default:y(()=>[...n[2]||=[d(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),d(`button`,{class:`primary`,disabled:c.value&&!t.ready},z(S(w)(`添加会话`)),9,Me)],32)]),_:1},8,[`open`,`title`,`eyebrow`]))}}),Pe={class:`transcript`},Fe={class:`t-head`},Ie=[`aria-label`,`title`],Le={class:`t-title`},Re={id:`liveStatus`,class:`faint`},ze={class:`t-tools`},Be=[`disabled`],Ve={id:`liveMessages`,class:`chat-messages scroll-pane`},He={key:0,class:`day-label`},Ue=[`data-status`],We={key:0},Ge={class:`body`},Ke={class:`message-meta`},qe={key:0,class:`chip`,"data-tone":`warn`},Je={class:`text`},Ye=[`aria-expanded`,`onClick`],Xe={key:0,class:`why`},Ze={key:0,class:`muted`},Qe={key:0},$e={key:1},et={key:2,class:`fb`},tt={class:`composer-note`},nt={class:`chip`,"data-tone":`warn`},rt={class:`faint`},it={class:`composer-row`},at={class:`uid`},ot={class:`sr-only`},st=[`title`],ct=[`placeholder`,`onKeydown`],lt={class:`check mention`},ut=[`disabled`],dt={key:1,class:`composer off`},ft={class:`faint`},pt=O(m({__name:`Transcript`,props:{session:{},sessionId:{},events:{},traces:{},decisions:{},status:{},demo:{type:Boolean},compact:{type:Boolean},sending:{type:Boolean}},emits:[`simulate`,`feedback`,`openList`,`openReplay`],setup(p,{expose:m,emit:_}){let v=p,y=_,b=t({glanced:L(`TA 扫了一眼`),silent:L(`TA 看了,没出声`),deferred:L(`TA 睡着了,醒来再看`)}),T=D(`10001`),E=D(``),O=D(!0),k=D(),A=D({}),ee=()=>B(l(),{month:`long`,day:`numeric`,weekday:`short`}),F=e(()=>{let e=v.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=v.events[0]?.time??2**53-1;for(let n of v.traces)b[n.status]&&n.time>=t&&e.push({type:`divider`,key:`t`+n.id,time:n.time,status:n.status,reasons:[n.reason].filter(Boolean),count:1});e.sort((e,t)=>e.time-t.time);let n=[],r=``;for(let t of e){let e=ee().format(t.time);e!==r&&(r=e,n.push({type:`day`,key:`d`+t.time,time:t.time,label:e}));let i=n[n.length-1];if(t.type===`divider`&&i?.type===`divider`&&i.status===t.status){i.count+=1,t.reasons[0]&&!i.reasons.includes(t.reasons[0])&&i.reasons.push(t.reasons[0]);continue}n.push(t)}return n});function R(e){let t=String(e.payload.text||``);return v.decisions.filter(n=>n.session_id===e.session_id&&n.reply&&n.reply.includes(t)&&Math.abs(n.time-e.time)<6e5).sort((t,n)=>Math.abs(t.time-e.time)-Math.abs(n.time-e.time))[0]}function te(e){return v.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function U(e){if(A.value[e.seq]){let{[e.seq]:t,...n}=A.value;A.value=n;return}A.value={...A.value,[e.seq]:{loading:!0,detail:null}};let t=te(e),n=null;try{n=t?await X(t.id):null}catch{n=null}A.value[e.seq]&&(A.value={...A.value,[e.seq]:{loading:!1,detail:n}})}async function W(){await h(),k.value?.focus()}function G(){E.value.trim()&&!v.sending&&(y(`simulate`,{userId:T.value,text:E.value,mentioned:O.value}),E.value=``)}return m({focusComposer:W}),(e,t)=>(M(),f(`section`,Pe,[d(`header`,Fe,[p.compact?(M(),f(`button`,{key:0,class:`icon-button`,"aria-label":S(w)(`全部会话`),title:S(w)(`全部会话`),onClick:t[0]||=e=>y(`openList`)},` ☰ `,8,Ie)):u(``,!0),p.session?(M(),f(`span`,{key:1,class:`badge`,style:I({"--hue":S(H)(p.session.id)})},z(p.session.kind===`private`?S(w)(`私`):S(w)(`群`)),5)):u(``,!0),d(`div`,Le,[d(`h2`,null,z(p.session?.name||S(w)(`选择一段对话`)),1),d(`span`,Re,z(p.status),1)]),d(`div`,ze,[d(`button`,{class:`small sim-button`,disabled:!p.sessionId,onClick:W},z(S(w)(`模拟消息`)),9,Be),d(`button`,{class:`small`,onClick:t[1]||=e=>y(`openReplay`)},z(S(w)(`回到那一刻`)),1)])]),d(`div`,Ve,[(M(!0),f(o,null,C(F.value,e=>(M(),f(o,{key:e.key},[e.type===`day`?(M(),f(`div`,He,[d(`span`,null,z(e.label),1)])):e.type===`divider`?(M(),f(`div`,{key:1,class:`divider`,"data-status":e.status},[d(`span`,null,z(S(b)[e.status])+z(e.count>1?` × ${e.count}`:``),1),e.reasons.length?(M(),f(`small`,We,z(e.reasons.slice(0,2).join(`;`)),1)):u(``,!0)],8,Ue)):(M(),f(`article`,{key:2,class:g([`message`,{bot:e.event.role===`assistant`,simulated:e.event.payload.simulated}])},[e.event.role===`assistant`?u(``,!0):(M(),f(`span`,{key:0,class:`avatar`,style:I({"--hue":S(H)(String(e.event.payload.userId||e.event.payload.name))}),"aria-hidden":`true`},z(S(q)(e.event.payload.name)),5)),d(`div`,Ge,[d(`div`,Ke,[d(`b`,null,z(e.event.payload.name),1),d(`time`,null,z(S(J)(e.event.time)),1),e.event.payload.simulated?(M(),f(`span`,qe,z(S(w)(`模拟`)),1)):u(``,!0)]),d(`p`,Je,z(e.event.payload.text),1),e.event.role===`assistant`?(M(),f(o,{key:0},[d(`button`,{class:`text-button why-toggle`,"aria-expanded":!!A.value[e.event.seq],onClick:t=>U(e.event)},z(A.value[e.event.seq]?S(w)(`收起`):S(w)(`为什么这么说`)),9,Ye),A.value[e.event.seq]?(M(),f(`div`,Xe,[A.value[e.event.seq].loading?(M(),f(`p`,Ze,z(S(w)(`正在翻当时的记录…`)),1)):(M(),f(o,{key:1},[A.value[e.event.seq].detail?.data?.decision?.appraisal?(M(),f(`p`,Qe,[d(`b`,null,z(S(w)(`TA 当时的理解:`)),1),a(z(A.value[e.event.seq].detail.data.decision.appraisal),1)])):u(``,!0),d(`p`,null,[d(`b`,null,z(S(w)(`理由:`)),1),a(z(A.value[e.event.seq].detail?.data?.reason||A.value[e.event.seq].detail?.data?.decision?.reason||R(e.event)?.reason||S(w)(`没有留下理由。`)),1)]),A.value[e.event.seq].detail?.data?.snapshot?.knowledge?.length?(M(),f(`p`,$e,[d(`b`,null,z(S(w)(`引用的资料:`)),1),a(z(A.value[e.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):u(``,!0),R(e.event)?(M(),f(`label`,et,[a(z(S(w)(`这句怎么样?`))+` `,1),j(V,{"model-value":R(e.event).feedback||``,"aria-label":S(w)(`这句怎么样?`),options:[{value:``,label:S(w)(`选择评价`)},...Object.entries(S(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":t=>y(`feedback`,R(e.event).id,t)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):u(``,!0)],64))])):u(``,!0)],64)):u(``,!0)])],2))],64))),128)),p.events.length?u(``,!0):(M(),r(P,{key:0,title:p.sessionId?S(w)(`等待新的消息`):S(w)(`选择一段对话`),text:S(w)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),p.demo?(M(),f(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:i(G,[`prevent`])},[d(`div`,tt,[d(`span`,nt,z(S(w)(`模拟消息`)),1),d(`span`,rt,z(S(w)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),d(`div`,it,[d(`label`,at,[d(`span`,ot,z(S(w)(`体验用户 ID`)),1),x(d(`input`,{"onUpdate:modelValue":t[2]||=e=>T.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:S(w)(`体验用户 ID`)},null,8,st),[[c,T.value]])]),x(d(`textarea`,{ref_key:`composer`,ref:k,"onUpdate:modelValue":t[3]||=e=>E.value=e,name:`text`,rows:`1`,placeholder:S(w)(`以这位群友的身份说一句…`),required:``,onKeydown:n(i(G,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[c,E.value]]),d(`label`,lt,[x(d(`input`,{"onUpdate:modelValue":t[4]||=e=>O.value=e,type:`checkbox`},null,512),[[s,O.value]]),a(z(S(w)(`视为 @`)),1)]),d(`button`,{class:`primary`,disabled:p.sending||!p.sessionId},z(p.sending?S(w)(`TA 在想…`):S(w)(`发送模拟消息`)),9,ut)])],32)):(M(),f(`div`,dt,[d(`span`,ft,z(S(w)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),d(`button`,{class:`small`,onClick:t[5]||=e=>S(N).chatOpen=!0},z(S(w)(`和 TA 聊聊`)),1)]))]))}}),[[`__scopeId`,`data-v-849aeb0f`]]),mt={class:`panel`},ht={key:0,class:`panel-body`},gt={key:1,class:`panel-body scroll-pane`},_t={class:`here`},vt={class:`facts`},yt={class:`eyebrow`},bt={key:1,class:`muted`},xt={key:2,class:`faint`},St={class:`eyebrow`},Ct={class:`faint`},wt={class:`row actions`},Tt=[`data-toggle`],Et=[`data-archive`],Dt=[`open`],Ot=[`data-session`],kt={class:`faint`},At=[`value`],jt={class:`faint`},Mt={class:`check`},Nt={class:`advanced-policy`},Pt={class:`stack tight`},Ft={class:`check`},It={class:`check`},Lt={class:`check`},Rt={class:`save-bar`},zt={class:`primary`,type:`submit`},Bt={class:`faint`},Vt={class:`fold summary-list`},Ht={key:0,class:`muted`},Ut={class:`faint`},Wt={key:0},Gt={key:2,class:`panel-body feedback-pane`},Kt={class:`faint`},qt={id:`feedbackList`,class:`scroll-pane`},Jt={class:`faint`},Yt={class:`reply-excerpt`},Xt={key:0,class:`muted`},Zt={class:`pagination`},Qt=[`disabled`],$t=[`disabled`],$=6,en=O(m({__name:`SessionPanel`,props:p({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:p([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(n,{emit:r}){let l=n,p=v(n,`settingsOpen`),m=v(n,`tab`),h=r,_=t([L(`近期细摘要`),L(`中期摘要`),L(`较早摘要`),L(`远期摘要`)]),y=D(0),b=e(()=>l.decisions.filter(e=>e.session_id===l.session?.id&&e.reply)),E=e(()=>Math.max(1,Math.ceil(b.value.length/$))),O=D(!1),k=D({});T(()=>l.session?.policy,e=>{k.value={...e||{}}},{immediate:!0,deep:!0}),T(()=>l.session?.id,()=>{y.value=0,O.value=!1});function A(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
|
|
2
|
-
|
|
3
|
-
`):``}function ee(e){let t=e.target,n=Object.fromEntries(new FormData(t));for(let e of[`aggregateMs`,`maxWaitMs`,`contextMessages`,`maxReply`])n[e]=Number(n[e]);for(let e of[`memory`,`selectiveVision`,`deepCheck`,`compaction`])n[e]=t.elements.namedItem(e).checked;h(`save`,n)}return(e,t)=>(M(),f(`section`,mt,[j(K,{modelValue:m.value,"onUpdate:modelValue":t[0]||=e=>m.value=e,label:S(w)(`这个会话`),items:[{key:`here`,label:S(w)(`这个会话`)},{key:`feedback`,label:S(w)(`反馈`)}]},null,8,[`modelValue`,`label`,`items`]),n.session?m.value===`here`?(M(),f(`div`,gt,[d(`div`,_t,[d(`div`,vt,[d(`div`,null,[d(`span`,yt,z(S(w)(`TA 在这里的样子`)),1),A(n.group)?(M(),f(o,{key:0},[d(`p`,{class:g([`face-body`,O.value?`open`:`clamp`])},z(A(n.group)),3),A(n.group).length>96?(M(),f(`button`,{key:0,type:`button`,class:`text-button`,onClick:t[1]||=e=>O.value=!O.value},z(O.value?S(w)(`收起`):S(w)(`展开完整内容`)),1)):u(``,!0)],64)):(M(),f(`p`,bt,z(S(w)(`还没有形成在这里的样子。`)),1)),n.group?.face?.aspiration?(M(),f(`small`,xt,z(S(w)(`想成为:{aspiration}`,{aspiration:n.group.face.aspiration})),1)):u(``,!0)]),d(`div`,null,[d(`span`,St,z(n.session.kind===`private`?S(w)(`这段私聊给 TA 的感觉`):S(w)(`这个群给 TA 的感觉`)),1),d(`p`,null,z(n.group?.bond?.feel||S(w)(`刚来这里不久`)),1)])]),d(`p`,Ct,z(S(w)(`在这里开不开口由 TA 自己决定:没有参与概率和冷却。TA 对这里每个人的感觉在「人际」里看。`)),1),d(`div`,wt,[d(`button`,{id:`toggleSession`,"data-toggle":n.session.id,class:g({primary:!n.session.enabled}),onClick:t[2]||=e=>h(`toggle`,n.session)},z(n.session.enabled?S(w)(`暂停参与`):S(w)(`开启参与`)),11,Tt),d(`button`,{"data-archive":n.session.id,onClick:t[3]||=e=>h(`archive`,n.session.id)},z(S(w)(`归档`)),9,Et),d(`button`,{id:`clearLiveContext`,onClick:t[4]||=e=>h(`clear`)},z(S(w)(`清空上下文`)),1)]),d(`details`,{class:`fold session-settings`,open:p.value,onToggle:t[19]||=e=>p.value=e.target.open},[d(`summary`,null,z(S(w)(`会话设置`)),1),(M(),f(`form`,{key:n.session.id,"data-session":n.session.id,class:`stack tight`,onSubmit:i(ee,[`prevent`]),onInput:t[17]||=e=>S(N).dirty=!0,onChange:t[18]||=e=>S(N).dirty=!0},[d(`p`,kt,z(S(w)(`所有会话和独处都用模型库里的默认模型;其余已启用的模型按列表顺序做备用。她在这个聊天窗里用什么样子,由她自己从经历里决定。`)),1),d(`label`,null,[a(z(S(w)(`会话名称`))+` `,1),d(`input`,{name:`name`,maxlength:`100`,autocomplete:`off`,value:n.session.name},null,8,At),d(`small`,jt,z(S(w)(`官方机器人平台不提供群名称,可以在这里自己起一个`)),1)]),d(`label`,Mt,[x(d(`input`,{name:`selectiveVision`,type:`checkbox`,"onUpdate:modelValue":t[5]||=e=>k.value.selectiveVision=e,onInput:t[6]||=i(()=>{},[`stop`])},null,544),[[s,k.value.selectiveVision]]),a(z(S(w)(`节能看图:只在被 @ 或明确要求时,看这一条和附近的图`)),1)]),d(`details`,Nt,[d(`summary`,null,z(S(w)(`高级策略`)),1),d(`div`,Pt,[d(`label`,null,[a(z(S(w)(`聚合窗口 ms`)),1),x(d(`input`,{name:`aggregateMs`,type:`number`,"onUpdate:modelValue":t[7]||=e=>k.value.aggregateMs=e},null,512),[[c,k.value.aggregateMs,void 0,{number:!0}]])]),d(`label`,null,[a(z(S(w)(`最长等待 ms`)),1),x(d(`input`,{name:`maxWaitMs`,type:`number`,"onUpdate:modelValue":t[8]||=e=>k.value.maxWaitMs=e},null,512),[[c,k.value.maxWaitMs,void 0,{number:!0}]])]),d(`label`,null,[a(z(S(w)(`近期原文条数(10–500)`)),1),x(d(`input`,{name:`contextMessages`,type:`number`,min:`10`,max:`500`,"onUpdate:modelValue":t[9]||=e=>k.value.contextMessages=e},null,512),[[c,k.value.contextMessages,void 0,{number:!0}]])]),d(`label`,null,[a(z(S(w)(`一轮总字数`)),1),x(d(`input`,{name:`maxReply`,type:`number`,"onUpdate:modelValue":t[10]||=e=>k.value.maxReply=e},null,512),[[c,k.value.maxReply,void 0,{number:!0}]])]),d(`label`,Ft,[x(d(`input`,{name:`compaction`,type:`checkbox`,"onUpdate:modelValue":t[11]||=e=>k.value.compaction=e,onInput:t[12]||=i(()=>{},[`stop`])},null,544),[[s,k.value.compaction]]),a(z(S(w)(`上下文压缩:更早的聊天定期整理成分层摘要,越近越详细`)),1)]),d(`label`,It,[x(d(`input`,{name:`memory`,type:`checkbox`,"onUpdate:modelValue":t[13]||=e=>k.value.memory=e,onInput:t[14]||=i(()=>{},[`stop`])},null,544),[[s,k.value.memory]]),a(z(S(w)(`长期记忆`)),1)]),d(`label`,Lt,[x(d(`input`,{name:`deepCheck`,type:`checkbox`,"onUpdate:modelValue":t[15]||=e=>k.value.deepCheck=e,onInput:t[16]||=i(()=>{},[`stop`])},null,544),[[s,k.value.deepCheck]]),a(z(S(w)(`倾诉、纠正和危机时额外复审一次回复`)),1)])])]),d(`div`,Rt,[d(`button`,zt,z(S(w)(`保存会话设置`)),1),d(`small`,Bt,z(S(N).dirty?S(w)(`有未保存的修改`):S(w)(`保存后下一轮生效`)),1)])],40,Ot))],40,Dt),d(`details`,Vt,[d(`summary`,null,z(S(w)(`语境摘要 · {length} 段`,{length:n.summaries.length})),1),n.summaries.length?u(``,!0):(M(),f(`p`,Ht,z(S(w)(`还没有摘要。聊天超过近期原文条数后,更早的内容会自动整理到这里。`)),1)),(M(!0),f(o,null,C(n.summaries,e=>(M(),f(`article`,{key:e.id,class:`summary`},[d(`header`,null,[d(`b`,null,z(S(_)[e.level]||S(w)(`摘要`)),1),d(`small`,Ut,z(S(w)(`{period} · {messages} 条消息`,{period:e.period,messages:e.messages})),1)]),d(`p`,null,z(e.summary),1),e.keyPoints?.length?(M(),f(`ul`,Wt,[(M(!0),f(o,null,C(e.keyPoints,(e,t)=>(M(),f(`li`,{key:t},z(e.open?S(w)(`未完:`):``)+z(e.text),1))),128))])):u(``,!0)]))),128))])])])):(M(),f(`div`,Gt,[d(`p`,Kt,z(S(w)(`针对具体回复评价口吻;这是 TA 在这个会话里听到的话,不会改写天性。`)),1),d(`div`,qt,[(M(!0),f(o,null,C(b.value.slice(y.value*$,y.value*$+$),e=>(M(),f(`article`,{key:e.id,class:`feedback-item`},[d(`time`,Jt,z(S(J)(e.time)),1),d(`p`,Yt,z(e.reply),1),d(`label`,null,[a(z(S(w)(`这句回复怎么样?`))+` `,1),j(V,{"data-feedback":e.id,"model-value":e.feedback||``,"aria-label":S(w)(`这句回复怎么样?`),options:[{value:``,label:S(w)(`选择评价`)},...Object.entries(S(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":t=>h(`feedback`,e.id,t)},null,8,[`data-feedback`,`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])]))),128)),b.value.length?u(``,!0):(M(),f(`p`,Xt,z(S(w)(`还没有可以评价的回复。`)),1))]),d(`div`,Zt,[d(`button`,{class:`small`,disabled:y.value===0,onClick:t[20]||=e=>y.value--},`←`,8,Qt),d(`span`,null,z(y.value+1)+` / `+z(E.value),1),d(`button`,{class:`small`,disabled:y.value+1>=E.value,onClick:t[21]||=e=>y.value++},` → `,8,$t)])])):(M(),f(`div`,ht,[j(P,{title:S(w)(`还没有选中会话`),text:S(w)(`从左边选一个群聊或私聊。`)},null,8,[`title`,`text`])]))]))}}),[[`__scopeId`,`data-v-96fbad3d`]]),tn={class:`replay`},nn={class:`col source`},rn={class:`col-head`},an={id:`events`,class:`scroll-pane`},on={key:0},sn={key:1,class:`muted`},cn=[`disabled`],ln={class:`faint`},un={class:`col`},dn={class:`col-head`},fn={id:`traces`,class:`scroll-pane`},pn=[`data-trace`,`onClick`],mn=[`data-tone`],hn={key:0,class:`muted`},gn={class:`col`},_n={class:`col-head`},vn={class:`scroll-pane inspect`},yn={key:0,class:`metrics`},bn={id:`traceTokens`},xn={key:1,class:`notice`},Sn={id:`traceDetail`},Cn=O(m({__name:`ReplaySheet`,props:{open:{type:Boolean},sessionId:{},events:{}},emits:[`close`,`load`],setup(e,{emit:n}){let s=e,l=n,p=t({sent:L(`开口了`),silent:L(`没出声`),glanced:L(`扫了一眼`),deferred:L(`睡着了`),running:L(`处理中`),error:L(`失败`),complete:L(`完成`),stale:L(`旧稿作废`),cancelled:L(`已取消`),interrupted:L(`已中断`)}),m=t({live:L(`真实聊天`),demo:L(`模拟预览`),replay:L(`测试回放`),memory:L(`记忆整理`),summary:L(`语境压缩`)}),h=D([]),_=D(null),v=D(``),b=D(``),E=D(!1),O=D(``);async function k(){s.sessionId&&(h.value=await ae(s.sessionId))}function A(){let e=s.events.map(e=>Number(e.seq));e.length&&(v.value||=String(Math.min(...e)),b.value||=String(Math.max(...e)))}async function j(){if(!E.value){E.value=!0;try{F(w(`回放正在运行`));let e=await Z({session:s.sessionId,from:Number(v.value),to:Number(b.value)});O.value=JSON.stringify(e,null,2),F(w(`回放结束`)),await k()}catch(e){F(e.message,!0)}finally{E.value=!1}}}async function N(e){_.value=await X(e),O.value=JSON.stringify(_.value,null,2)}function P(e){return e?w(`输入 {v}(缓存读 {v2} / 写 {v3})· 输出 {v4}`,{v:G(e.input),v2:G(e.cachedRead),v3:G(e.cacheWrite),v4:G(e.output)}):`—`}return T(()=>[s.open,s.sessionId],()=>{s.open&&(v.value=``,b.value=``,_.value=null,A(),k())},{immediate:!0}),T(()=>s.events.length,A),(t,n)=>(M(),r(ne,{open:e.open,title:S(w)(`回到那一刻`),eyebrow:`REPLAY`,width:`1180px`,onClose:n[3]||=e=>l(`close`)},{default:y(()=>[d(`div`,tn,[d(`section`,nn,[d(`div`,rn,[d(`h3`,null,z(S(w)(`历史消息`)),1),d(`button`,{id:`loadMessages`,class:`small`,onClick:n[0]||=e=>{l(`load`),k()}},z(S(w)(`↻ 加载`)),1)]),d(`div`,an,[e.events.length?(M(),f(`table`,on,[d(`thead`,null,[d(`tr`,null,[d(`th`,null,z(S(w)(`序号`)),1),d(`th`,null,z(S(w)(`发送者`)),1),d(`th`,null,z(S(w)(`消息`)),1)])]),d(`tbody`,null,[(M(!0),f(o,null,C([...e.events].reverse(),e=>(M(),f(`tr`,{key:e.seq},[d(`td`,null,z(e.seq),1),d(`td`,null,z(e.payload.name),1),d(`td`,null,z(e.payload.text),1)]))),128))])])):(M(),f(`p`,sn,z(S(w)(`当前会话暂无历史消息。`)),1))]),d(`form`,{class:`range`,onSubmit:i(j,[`prevent`])},[d(`label`,null,[a(z(S(w)(`起始序号`)),1),x(d(`input`,{"onUpdate:modelValue":n[1]||=e=>v.value=e,name:`from`,type:`number`,required:``},null,512),[[c,v.value]])]),d(`label`,null,[a(z(S(w)(`截止序号`)),1),x(d(`input`,{"onUpdate:modelValue":n[2]||=e=>b.value=e,name:`to`,type:`number`,required:``},null,512),[[c,b.value]])]),d(`button`,{id:`replay`,class:`primary`,disabled:E.value||!e.events.length},z(E.value?S(w)(`正在回放…`):S(w)(`回放这段对话`)),9,cn),d(`p`,ln,z(S(w)(`隔离回放:只测试回复,不发送 QQ,不修改真实记忆和 TA 的心智。`)),1)],32)]),d(`section`,un,[d(`div`,dn,[d(`h3`,null,z(S(w)(`处理记录`)),1)]),d(`div`,fn,[(M(!0),f(o,null,C(h.value,e=>(M(),f(`button`,{key:e.id,"data-trace":e.id,class:g([`trace-row`,{selected:_.value?.id===e.id}]),onClick:t=>N(e.id)},[d(`span`,null,[d(`time`,null,z(S(J)(e.time)),1),d(`small`,{class:`chip`,"data-tone":e.status===`error`?`danger`:`quiet`},z(S(p)[e.status]||e.status),9,mn)]),d(`p`,null,z(e.reason||e.error||S(w)(`查看处理详情`)),1)],10,pn))),128)),h.value.length?u(``,!0):(M(),f(`p`,hn,z(S(w)(`有新的消息处理记录后会显示在这里。`)),1))])]),d(`section`,gn,[d(`div`,_n,[d(`h3`,null,z(S(w)(`运行详情`)),1)]),d(`div`,vn,[_.value?(M(),f(`div`,yn,[d(`span`,null,[a(z(S(w)(`模式`)),1),d(`b`,null,z(S(m)[_.value.mode]||_.value.mode),1)]),d(`span`,null,[a(z(S(w)(`耗时`)),1),d(`b`,null,z(_.value.data?.elapsed??`—`)+` ms`,1)]),d(`span`,null,[a(z(S(w)(`模型调用`)),1),d(`b`,null,z(S(w)(`{v} 次`,{v:_.value.data?.calls?.length??0})),1)]),d(`span`,bn,[n[4]||=a(`Token`,-1),d(`b`,null,z(P(_.value.data?.tokens)),1)])])):u(``,!0),_.value?(M(),f(`p`,xn,z(_.value.reason||_.value.data?.decision?.reason||S(w)(`展开下方查看上下文、模型输入与原始输出。`)),1)):u(``,!0),d(`pre`,Sn,z(O.value||S(w)(`选择一条处理记录,查看最终上下文、Prompt、原始输出、Token 与耗时。`)),1)])])])]),_:1},8,[`open`,`title`]))}}),[[`__scopeId`,`data-v-839b3181`]]),wn={key:0,class:`card col list-col`},Tn={class:`card col talk-col`},En={class:`card col panel-col`},Dn={class:`sheet-list`},On=O(m({__name:`ChatsPage`,setup(t){let n=re(`(max-width: 1100px)`),i=e(()=>N.core?.sessions||[]),a=e(()=>i.value.filter(e=>!e.archived)),o=sessionStorage.activeSession,s=D(a.value.some(e=>e.id===o)?o:a.value[0]?.id||``),c=e(()=>a.value.find(e=>e.id===s.value)||null),p=D([]),m=D([]),v=D([]),x=D([]),C=D({}),E=D(``),O=D(!1),P=D(!1),I=D(!1),L=D(N.sub===`settings`),z=D(N.sub===`replay`),B=D(`here`),V=e(()=>N.health?.decisions||[]),H=e(()=>x.value.find(e=>e.session===s.value)||null),U=!1,G=!1,K=!1,q=``,J=0,Y=null,X=0;async function Z(e=!1){if(!s.value)return;if(U){G=!0,K||=e;return}let t=s.value,n=e||q!==t;U=!0;try{let[e,r]=await Promise.all([ie(t,n?void 0:J),ae(t,{limit:30,compact:!0})]);if(s.value!==t)return;let i=document.getElementById(`liveMessages`),a=q!==t||!i||i.scrollHeight-i.scrollTop-i.clientHeight<80;n?(p.value=e.reverse(),J=0):e.length&&(p.value=[...p.value,...e]);for(let t of e)J=Math.max(J,Number(t.seq)||0);!n&&e.length===100&&(G=!0),m.value=r,q=t,await h();let o=document.getElementById(`liveMessages`);o&&a&&(o.scrollTop=o.scrollHeight),E.value=w(`实时同步 · `)+new Date().toLocaleTimeString(l())}catch(e){E.value=w(`同步失败:`)+e.message}finally{if(U=!1,s.value!==t)G=!1,Z();else if(G){let e=K;G=!1,K=!1,Z(e)}}}async function he(){try{let[e,t]=await Promise.all([W.bonds(),W.overview()]);x.value=e.groups,C.value=Object.fromEntries(t.attention.map(e=>[e.session,e.unread]))}catch{}}T(s,e=>{if(Y?.abort(),e&&(sessionStorage.activeSession=e),p.value=[],m.value=[],v.value=[],q=``,J=0,G=!1,K=!1,I.value=!1,Z(),!e)return;pe(e).then(t=>{s.value===e&&(v.value=[...t].reverse())}).catch(()=>{});let t=new AbortController;Y=t;let n=``;te(e=>{let t=Number(e?.messages);Number.isSafeInteger(t)&&t<J&&(q=``,J=0,p.value=[]);let r=JSON.stringify(e),i=n!==``&&r!==n;n=r,i&&Z()},{session:e,signal:t.signal,reconnectMs:1e3})},{immediate:!0}),T(a,e=>{e.some(e=>e.id===s.value)||(s.value=e[0]?.id||``)}),T(B,async e=>{if(e===`feedback`)try{N.health=await b()}catch{}}),T(z,e=>A(e?`replay`:L.value?`settings`:``)),T(()=>N.sub,e=>{e===`replay`&&(z.value=!0),e===`settings`&&(L.value=!0)}),T(()=>N.pulse,he),ee(()=>{he(),X=window.setInterval(()=>Z(!0),15e3)}),k(()=>{clearInterval(X),Y?.abort()});async function ge(e){try{let t=await ce(e);await _(),s.value=t?.sessionId||`${e.kind}:${e.id}`,L.value=!0,B.value=`here`,P.value=!1,F(w(`会话已添加`))}catch(e){F(e.message,!0)}}async function Q(e){await le(e.id,!e.enabled),await _()}async function _e(e){await ue(e,!0),await _()}async function ve(e){await ue(e,!1),await _()}async function ye(e){await R(w(`永久删除这个会话?它的消息和设置都会消失。`),{title:w(`永久删除`),confirmText:w(`删除`),danger:!0})&&(await de(e),await _())}async function be(e){if(c.value)try{await fe(c.value.id,{...e,name:String(e.name||``).trim()||c.value.name}),N.dirty=!1,F(w(`会话配置已保存`)),await _()}catch(e){F(e.message,!0)}}async function xe(){s.value&&await R(w(`确定清空这个会话的消息上下文吗?长期记忆不会删除。`),{title:w(`清空上下文`),confirmText:w(`清空`),danger:!0})&&(await me(s.value),p.value=[],m.value=[],q=``,J=0,await Z(!0))}async function Se(e){if(!O.value&&s.value){O.value=!0;try{let t=await oe({sessionId:s.value,...e});F(t.reason||w(`已发送`)),N.health=await b(),await Z()}catch(e){F(e.message,!0)}finally{O.value=!1}}}async function Ce(e,t){try{await se(e,t),F(w(`反馈已保存`)),N.health=await b()}catch(e){F(e.message,!0)}}return(e,t)=>(M(),f(`div`,{class:g([`page chats`,{compact:S(n)}])},[S(n)?u(``,!0):(M(),f(`aside`,wn,[j(Oe,{sessions:i.value,selected:s.value,unread:C.value,onSelect:t[0]||=e=>s.value=e,onAdd:t[1]||=e=>P.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])),d(`div`,Tn,[j(pt,{"session-id":s.value,session:c.value,events:p.value,traces:m.value,decisions:V.value,status:E.value||S(w)(`请选择会话`),demo:!!S(N).health.settings.demo,compact:S(n),sending:O.value,onSimulate:Se,onFeedback:Ce,onOpenList:t[2]||=e=>I.value=!0,onOpenReplay:t[3]||=e=>z.value=!0},null,8,[`session-id`,`session`,`events`,`traces`,`decisions`,`status`,`demo`,`compact`,`sending`])]),d(`aside`,En,[j(en,{"settings-open":L.value,"onUpdate:settingsOpen":t[4]||=e=>L.value=e,tab:B.value,"onUpdate:tab":t[5]||=e=>B.value=e,session:c.value,group:H.value,summaries:v.value,decisions:V.value,onToggle:Q,onArchive:_e,onClear:xe,onSave:be,onFeedback:Ce},null,8,[`settings-open`,`tab`,`session`,`group`,`summaries`,`decisions`])]),S(n)?(M(),r(ne,{key:1,open:I.value,title:S(w)(`全部会话`),eyebrow:`CHATS`,width:`400px`,onClose:t[8]||=e=>I.value=!1},{default:y(()=>[d(`div`,Dn,[j(Oe,{sessions:i.value,selected:s.value,unread:C.value,onSelect:t[6]||=e=>s.value=e,onAdd:t[7]||=e=>P.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])]),_:1},8,[`open`,`title`])):u(``,!0),j(Ne,{open:P.value,channel:S(N).health?.connection?.channel===`qqbot`?`qqbot`:`onebot`,ready:!!S(N).health?.connection?.qqbot?.appId,onClose:t[9]||=e=>P.value=!1,onAdd:ge},null,8,[`open`,`channel`,`ready`]),j(Cn,{open:z.value,"session-id":s.value,events:p.value,onClose:t[10]||=e=>z.value=!1,onLoad:t[11]||=e=>Z(!0)},null,8,[`open`,`session-id`,`events`])],2))}}),[[`__scopeId`,`data-v-c328f9fe`]]);export{On as default};
|