luckytri 1.2.4 → 1.3.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/README.en.md +44 -1
- package/README.md +44 -1
- package/docs/en/CHANGELOG.md +20 -0
- package/docs/zh/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/public/app/assets/ChatsPage-C5-w410L.js +3 -0
- package/public/app/assets/{Empty-y1vXDIjQ.js → Empty-DSurzLFZ.js} +1 -1
- package/public/app/assets/Field-GuXyA_aS.js +1 -0
- package/public/app/assets/HeartPage-BGmIrxCz.js +1 -0
- package/public/app/assets/LifePage-Dzln-0Y3.js +1 -0
- package/public/app/assets/MemoryPage-QsthmMZ9.js +1 -0
- package/public/app/assets/NaturePage-CIvHjgJa.js +3 -0
- package/public/app/assets/PeoplePage-bIoX24pj.js +1 -0
- package/public/app/assets/PluginsPage-EuJhCovw.js +2 -0
- package/public/app/assets/SystemPage-7t42B-Q3.js +1 -0
- package/public/app/assets/{SystemPage-DNTD2_nj.css → SystemPage-BLMlD1M6.css} +1 -1
- package/public/app/assets/TimePage-CY-SwBq3.js +3 -0
- package/public/app/assets/index-Cn6TppEV.js +4 -0
- package/public/app/assets/{index-cDDC-1dI.css → index-JRbQoSJr.css} +1 -1
- package/public/app/assets/{knowledge-BjD9PkXe.js → knowledge-sUjcjbfI.js} +1 -1
- package/public/app/assets/media-Dh7c41PQ.js +1 -0
- package/public/app/assets/{mind-KkinyNC6.js → mind-BMcoPQNd.js} +1 -1
- package/public/app/index.html +4 -4
- package/public/guide.en.html +3 -3
- package/public/guide.html +3 -3
- package/server/actions/index.js +268 -0
- package/server/app.js +3 -1
- package/server/channels/contract.js +3 -0
- package/server/channels/hub.js +15 -0
- package/server/channels/onebot/adapter.js +162 -21
- package/server/channels/onebot/enrich.js +132 -0
- package/server/channels/onebot/index.js +53 -3
- package/server/channels/onebot/music.js +43 -0
- package/server/core/conversation-grounding.js +1 -1
- package/server/core/dialogue-context.js +158 -1
- package/server/core/dialogue-contract.js +32 -33
- package/server/core/message-scheduler.js +13 -10
- package/server/core/model-manager.js +35 -20
- package/server/core/model-presets.js +154 -1
- package/server/core/orchestrator.js +18 -0
- package/server/core/persona-manager.js +20 -2
- package/server/core/reply-delivery.js +360 -60
- package/server/core/response-generator.js +1 -1
- package/server/core/response-validator.js +85 -17
- package/server/core/turn-processor.js +63 -16
- package/server/core/turn.js +59 -1
- package/server/i18n/decisions.js +10 -5
- package/server/i18n/errors.js +25 -2
- package/server/index.js +30 -0
- package/server/mind/continuity.js +2 -4
- package/server/mind/discretion.js +16 -5
- package/server/mind/guard.js +88 -68
- package/server/mind/memory.js +7 -2
- package/server/mind/recall/index.js +2 -0
- package/server/mind/relationship-context.js +110 -71
- package/server/plugins/zip-file.js +180 -0
- package/server/runtime/update-manager.js +34 -2
- package/server/stickers/api.js +86 -19
- package/server/stickers/library.js +8 -2
- package/server/stickers/offer.js +48 -16
- package/server/stickers/zip-import.js +62 -2
- package/server/studio/auto-update.js +224 -0
- package/server/studio/updates.js +154 -144
- package/server/voice/index.js +255 -0
- package/server/voice/sauc.js +152 -0
- package/server/web/link-reader.js +379 -0
- package/public/app/assets/ChatsPage-C_gKz4mJ.js +0 -3
- package/public/app/assets/Field-CJG5lERx.js +0 -1
- package/public/app/assets/HeartPage-5y3avjSy.js +0 -1
- package/public/app/assets/LifePage-BtwRMDAM.js +0 -1
- package/public/app/assets/MemoryPage-54noDhWz.js +0 -1
- package/public/app/assets/NaturePage-DHK5X2Qw.js +0 -3
- package/public/app/assets/PeoplePage-ZsA7gyxP.js +0 -1
- package/public/app/assets/PluginsPage-BCRRIAhq.js +0 -2
- package/public/app/assets/SystemPage-DWr1TG49.js +0 -1
- package/public/app/assets/TimePage-6MUmtSOT.js +0 -3
- package/public/app/assets/index-BokqwNXF.js +0 -4
- package/public/app/assets/media-Ciozo_yb.js +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Volcengine streaming ASR (sauc, "nostream" endpoint of Agent Plan): the
|
|
2
|
+
// whole clip is sent in a few binary frames and one final text comes back.
|
|
3
|
+
// Audio is first converted to 16 kHz mono WAV with ffmpeg.
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { gzipSync, gunzipSync } from "node:zlib";
|
|
7
|
+
import WebSocket from "ws";
|
|
8
|
+
|
|
9
|
+
function frame(type, flags, serialization, payload) {
|
|
10
|
+
const body = gzipSync(payload);
|
|
11
|
+
const head = Buffer.from([
|
|
12
|
+
0x11,
|
|
13
|
+
(type << 4) | flags,
|
|
14
|
+
(serialization << 4) | 0x01,
|
|
15
|
+
0,
|
|
16
|
+
]);
|
|
17
|
+
const size = Buffer.alloc(4);
|
|
18
|
+
size.writeUInt32BE(body.length);
|
|
19
|
+
return Buffer.concat([head, size, body]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseFrame(data) {
|
|
23
|
+
const buf = Buffer.from(data);
|
|
24
|
+
const headerSize = (buf[0] & 0x0f) * 4;
|
|
25
|
+
const type = buf[1] >> 4;
|
|
26
|
+
const flags = buf[1] & 0x0f;
|
|
27
|
+
const gz = (buf[2] & 0x0f) === 1;
|
|
28
|
+
let at = headerSize;
|
|
29
|
+
if (type === 0x0f) {
|
|
30
|
+
const code = buf.readUInt32BE(at);
|
|
31
|
+
const size = buf.readUInt32BE(at + 4);
|
|
32
|
+
return {
|
|
33
|
+
error: code,
|
|
34
|
+
message: buf.subarray(at + 8, at + 8 + size).toString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (flags & 0x01) at += 4; // sequence number
|
|
38
|
+
const size = buf.readUInt32BE(at);
|
|
39
|
+
let payload = buf.subarray(at + 4, at + 4 + size);
|
|
40
|
+
if (gz && payload.length) payload = gunzipSync(payload);
|
|
41
|
+
let json = {};
|
|
42
|
+
try {
|
|
43
|
+
json = JSON.parse(payload.toString() || "{}");
|
|
44
|
+
} catch {
|
|
45
|
+
/* Not JSON: ignore. */
|
|
46
|
+
}
|
|
47
|
+
return { type, last: (flags & 0x02) === 0x02, json };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function toWav(audio) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const ff = spawn("ffmpeg", [
|
|
53
|
+
"-hide_banner",
|
|
54
|
+
"-loglevel",
|
|
55
|
+
"error",
|
|
56
|
+
"-i",
|
|
57
|
+
"pipe:0",
|
|
58
|
+
"-ar",
|
|
59
|
+
"16000",
|
|
60
|
+
"-ac",
|
|
61
|
+
"1",
|
|
62
|
+
"-f",
|
|
63
|
+
"wav",
|
|
64
|
+
"pipe:1",
|
|
65
|
+
]);
|
|
66
|
+
const out = [];
|
|
67
|
+
ff.stdout.on("data", (d) => out.push(d));
|
|
68
|
+
ff.on("error", () => reject(new Error("没有找到 ffmpeg,听不了语音")));
|
|
69
|
+
ff.on("close", (code) =>
|
|
70
|
+
code === 0
|
|
71
|
+
? resolve(Buffer.concat(out))
|
|
72
|
+
: reject(new Error("语音转换失败")),
|
|
73
|
+
);
|
|
74
|
+
ff.stdin.on("error", () => {});
|
|
75
|
+
ff.stdin.end(audio);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function saucTranscribe(
|
|
80
|
+
{ url, key, resourceId },
|
|
81
|
+
audio,
|
|
82
|
+
{ timeout = 20000 } = {},
|
|
83
|
+
) {
|
|
84
|
+
const wav = await toWav(audio);
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const ws = new WebSocket(url, {
|
|
87
|
+
headers: {
|
|
88
|
+
"X-Api-Key": key,
|
|
89
|
+
"X-Api-Resource-Id": resourceId || "volc.seedasr.sauc.duration",
|
|
90
|
+
"X-Api-Request-Id": randomUUID(),
|
|
91
|
+
"X-Api-Connect-Id": randomUUID(),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
let text = "";
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
ws.terminate();
|
|
97
|
+
reject(new Error("语音识别超时"));
|
|
98
|
+
}, timeout);
|
|
99
|
+
const done = (fn, v) => {
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
try {
|
|
102
|
+
ws.close();
|
|
103
|
+
} catch {
|
|
104
|
+
/* closing */
|
|
105
|
+
}
|
|
106
|
+
fn(v);
|
|
107
|
+
};
|
|
108
|
+
ws.on("unexpected-response", (_req, res) =>
|
|
109
|
+
done(
|
|
110
|
+
reject,
|
|
111
|
+
Object.assign(new Error("语音识别失败"), { status: res.statusCode }),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
ws.on("error", () => done(reject, new Error("语音识别失败")));
|
|
115
|
+
ws.on("open", () => {
|
|
116
|
+
ws.send(
|
|
117
|
+
frame(
|
|
118
|
+
0x1,
|
|
119
|
+
0x0,
|
|
120
|
+
0x1,
|
|
121
|
+
Buffer.from(
|
|
122
|
+
JSON.stringify({
|
|
123
|
+
user: { uid: "luckytri" },
|
|
124
|
+
audio: { format: "wav", rate: 16000, bits: 16, channel: 1 },
|
|
125
|
+
request: {
|
|
126
|
+
model_name: "bigmodel",
|
|
127
|
+
enable_punc: true,
|
|
128
|
+
enable_itn: true,
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
const chunk = 32000; // about one second of 16 kHz 16-bit audio
|
|
135
|
+
for (let i = 0; i < wav.length; i += chunk) {
|
|
136
|
+
const last = i + chunk >= wav.length;
|
|
137
|
+
ws.send(frame(0x2, last ? 0x2 : 0x0, 0x0, wav.subarray(i, i + chunk)));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
ws.on("message", (data) => {
|
|
141
|
+
const f = parseFrame(data);
|
|
142
|
+
if (f.error)
|
|
143
|
+
return done(
|
|
144
|
+
reject,
|
|
145
|
+
Object.assign(new Error("语音识别失败"), { code: f.error }),
|
|
146
|
+
);
|
|
147
|
+
if (f.json?.result?.text) text = f.json.result.text;
|
|
148
|
+
if (f.last) done(resolve, text);
|
|
149
|
+
});
|
|
150
|
+
ws.on("close", () => done(resolve, text));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// Reading a link someone sent: fetch the public page, keep the readable text,
|
|
2
|
+
// and give her a short note of what it is. "I can't open links" was one of the
|
|
3
|
+
// most robotic things she said (round 24 real logs). Only public http(s)
|
|
4
|
+
// pages are read: private, loopback and link-local addresses are refused at
|
|
5
|
+
// every redirect hop, bodies are capped, and nothing is sent with cookies.
|
|
6
|
+
import { lookup } from "node:dns/promises";
|
|
7
|
+
import { isIP } from "node:net";
|
|
8
|
+
|
|
9
|
+
export const LINK_LIMITS = {
|
|
10
|
+
timeoutMs: 8000,
|
|
11
|
+
maxBytes: 2 * 1024 * 1024,
|
|
12
|
+
maxRedirects: 3,
|
|
13
|
+
perTurn: 2,
|
|
14
|
+
textChars: 6000,
|
|
15
|
+
ttlMs: 6 * 3600 * 1000,
|
|
16
|
+
};
|
|
17
|
+
const USER_AGENT = "LuckyTri-LinkReader/1.0 (+https://github.com/LuckyTri)";
|
|
18
|
+
const TEXT_TYPES =
|
|
19
|
+
/^(?:text\/html|application\/xhtml\+xml|text\/plain|application\/json|text\/markdown|application\/vnd\.github[\w.+-]*)/i;
|
|
20
|
+
|
|
21
|
+
export function linksIn(text) {
|
|
22
|
+
const found = [];
|
|
23
|
+
for (const match of String(text || "").matchAll(
|
|
24
|
+
/https?:\/\/[^\s<>"'()「」,。!?、]+/gi,
|
|
25
|
+
)) {
|
|
26
|
+
const url = match[0].replace(/[),.;:!?]+$/, "");
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(url);
|
|
29
|
+
if (!["http:", "https:"].includes(parsed.protocol)) continue;
|
|
30
|
+
parsed.hash = "";
|
|
31
|
+
const clean = parsed.toString();
|
|
32
|
+
if (!found.includes(clean)) found.push(clean);
|
|
33
|
+
} catch {
|
|
34
|
+
// Not a URL after all.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Addresses that must never be reached from a chat message. 198.18.0.0/15
|
|
41
|
+
// stays allowed: fake-IP proxies (Clash and the like, common on the owner's
|
|
42
|
+
// machine) answer every public name from that range.
|
|
43
|
+
export function privateAddress(address) {
|
|
44
|
+
const kind = isIP(address);
|
|
45
|
+
if (kind === 4) {
|
|
46
|
+
const [a, b] = address.split(".").map(Number);
|
|
47
|
+
return (
|
|
48
|
+
a === 0 ||
|
|
49
|
+
a === 10 ||
|
|
50
|
+
a === 127 ||
|
|
51
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
52
|
+
(a === 169 && b === 254) ||
|
|
53
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
54
|
+
(a === 192 && b === 168) ||
|
|
55
|
+
a >= 224
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (kind === 6) {
|
|
59
|
+
const v = address.toLowerCase();
|
|
60
|
+
if (v.startsWith("::ffff:")) return privateAddress(v.slice(7));
|
|
61
|
+
return (
|
|
62
|
+
v === "::" ||
|
|
63
|
+
v === "::1" ||
|
|
64
|
+
v.startsWith("fc") ||
|
|
65
|
+
v.startsWith("fd") ||
|
|
66
|
+
v.startsWith("fe8") ||
|
|
67
|
+
v.startsWith("fe9") ||
|
|
68
|
+
v.startsWith("fea") ||
|
|
69
|
+
v.startsWith("feb") ||
|
|
70
|
+
v.startsWith("ff")
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function safeHost(hostname, resolve) {
|
|
77
|
+
const host = hostname.replace(/^\[|\]$/g, "");
|
|
78
|
+
if (/^localhost$|\.localhost$|\.local$|\.internal$/i.test(host)) return false;
|
|
79
|
+
if (isIP(host)) return !privateAddress(host);
|
|
80
|
+
let addresses;
|
|
81
|
+
try {
|
|
82
|
+
addresses = await resolve(host);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return (
|
|
87
|
+
addresses.length > 0 && addresses.every((a) => !privateAddress(a.address))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const decode = (text) =>
|
|
92
|
+
text
|
|
93
|
+
.replace(/ /g, " ")
|
|
94
|
+
.replace(/</g, "<")
|
|
95
|
+
.replace(/>/g, ">")
|
|
96
|
+
.replace(/"/g, '"')
|
|
97
|
+
.replace(/'|'/g, "'")
|
|
98
|
+
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
|
|
99
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, n) =>
|
|
100
|
+
String.fromCodePoint(parseInt(n, 16)),
|
|
101
|
+
)
|
|
102
|
+
.replace(/&/g, "&");
|
|
103
|
+
|
|
104
|
+
// Readable text from a page: title, description and the body without
|
|
105
|
+
// scripts, styles, navigation, headers and footers.
|
|
106
|
+
export function readable(html) {
|
|
107
|
+
const meta = (name) => {
|
|
108
|
+
const re = new RegExp(
|
|
109
|
+
`<meta[^>]+(?:name|property)=["']${name}["'][^>]*content=["']([^"']*)["']|<meta[^>]+content=["']([^"']*)["'][^>]*(?:name|property)=["']${name}["']`,
|
|
110
|
+
"i",
|
|
111
|
+
);
|
|
112
|
+
const m = html.match(re);
|
|
113
|
+
return m ? decode(m[1] || m[2] || "").trim() : "";
|
|
114
|
+
};
|
|
115
|
+
const title =
|
|
116
|
+
meta("og:title") ||
|
|
117
|
+
decode((html.match(/<title[^>]*>([^]*?)<\/title>/i) || [])[1] || "").trim();
|
|
118
|
+
const description = meta("og:description") || meta("description");
|
|
119
|
+
const main =
|
|
120
|
+
(html.match(/<(?:main|article)[^>]*>([^]*?)<\/(?:main|article)>/i) ||
|
|
121
|
+
[])[1] ||
|
|
122
|
+
(html.match(/<body[^>]*>([^]*?)<\/body>/i) || [])[1] ||
|
|
123
|
+
html;
|
|
124
|
+
const text = decode(
|
|
125
|
+
main
|
|
126
|
+
.replace(
|
|
127
|
+
/<(script|style|noscript|svg|nav|header|footer|form|template)[^>]*>[^]*?<\/\1>/gi,
|
|
128
|
+
" ",
|
|
129
|
+
)
|
|
130
|
+
.replace(/<!--[^]*?-->/g, " ")
|
|
131
|
+
.replace(/<br\s*\/?>|<\/(?:p|div|li|h[1-6]|tr|section)>/gi, "\n")
|
|
132
|
+
.replace(/<[^>]+>/g, " "),
|
|
133
|
+
)
|
|
134
|
+
.replace(/[ \t\f\v]+/g, " ")
|
|
135
|
+
.replace(/\s*\n\s*/g, "\n")
|
|
136
|
+
.replace(/\n{2,}/g, "\n")
|
|
137
|
+
.trim();
|
|
138
|
+
return {
|
|
139
|
+
title: title.slice(0, 200),
|
|
140
|
+
description: description.slice(0, 400),
|
|
141
|
+
text,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function fetchCapped(url, { fetcher, resolve, limits }) {
|
|
146
|
+
let current = url;
|
|
147
|
+
for (let hop = 0; hop <= limits.maxRedirects; hop++) {
|
|
148
|
+
const parsed = new URL(current);
|
|
149
|
+
if (!["http:", "https:"].includes(parsed.protocol))
|
|
150
|
+
throw Error("只读取 http/https 链接");
|
|
151
|
+
if (!(await safeHost(parsed.hostname, resolve)))
|
|
152
|
+
throw Error("这个地址不是公开网页");
|
|
153
|
+
const controller = new AbortController();
|
|
154
|
+
const timer = setTimeout(() => controller.abort(), limits.timeoutMs);
|
|
155
|
+
timer.unref?.();
|
|
156
|
+
let response;
|
|
157
|
+
try {
|
|
158
|
+
response = await fetcher(current, {
|
|
159
|
+
redirect: "manual",
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
headers: {
|
|
162
|
+
"User-Agent": USER_AGENT,
|
|
163
|
+
Accept:
|
|
164
|
+
"text/html,application/xhtml+xml,text/plain,application/json;q=0.9,*/*;q=0.1",
|
|
165
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
169
|
+
const next = response.headers.get("location");
|
|
170
|
+
if (!next) throw Error("跳转缺少目标");
|
|
171
|
+
current = new URL(next, current).toString();
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (!response.ok) throw Error("网页没有正常返回");
|
|
175
|
+
const type = response.headers.get("content-type") || "";
|
|
176
|
+
if (type && !TEXT_TYPES.test(type)) throw Error("不是网页文字内容");
|
|
177
|
+
const declared = Number(response.headers.get("content-length") || 0);
|
|
178
|
+
if (declared > limits.maxBytes) throw Error("网页太大");
|
|
179
|
+
const reader = response.body?.getReader?.();
|
|
180
|
+
if (!reader) {
|
|
181
|
+
const text = await response.text();
|
|
182
|
+
if (Buffer.byteLength(text) > limits.maxBytes) throw Error("网页太大");
|
|
183
|
+
return { url: current, type, body: text };
|
|
184
|
+
}
|
|
185
|
+
const chunks = [];
|
|
186
|
+
let size = 0;
|
|
187
|
+
for (;;) {
|
|
188
|
+
const { done, value } = await reader.read();
|
|
189
|
+
if (done) break;
|
|
190
|
+
size += value.byteLength;
|
|
191
|
+
if (size > limits.maxBytes) {
|
|
192
|
+
await reader.cancel().catch(() => {});
|
|
193
|
+
throw Error("网页太大");
|
|
194
|
+
}
|
|
195
|
+
chunks.push(value);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
url: current,
|
|
199
|
+
type,
|
|
200
|
+
body: Buffer.concat(chunks.map((c) => Buffer.from(c))).toString("utf8"),
|
|
201
|
+
};
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error.name === "AbortError") throw Error("网页读取超时");
|
|
204
|
+
throw error;
|
|
205
|
+
} finally {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
throw Error("跳转次数太多");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const GITHUB_REPO =
|
|
213
|
+
/^https:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/i;
|
|
214
|
+
|
|
215
|
+
export class LinkReader {
|
|
216
|
+
constructor({
|
|
217
|
+
db = null,
|
|
218
|
+
fetcher = fetch,
|
|
219
|
+
resolve = (host) => lookup(host, { all: true, verbatim: true }),
|
|
220
|
+
limits = {},
|
|
221
|
+
now = () => Date.now(),
|
|
222
|
+
} = {}) {
|
|
223
|
+
this.db = db;
|
|
224
|
+
this.fetcher = fetcher;
|
|
225
|
+
this.resolve = resolve;
|
|
226
|
+
this.limits = { ...LINK_LIMITS, ...limits };
|
|
227
|
+
this.now = now;
|
|
228
|
+
this.memory = new Map();
|
|
229
|
+
if (db)
|
|
230
|
+
db.exec(
|
|
231
|
+
"CREATE TABLE IF NOT EXISTS core_link_cache (url TEXT PRIMARY KEY, ok INTEGER NOT NULL, title TEXT, site TEXT, description TEXT, text TEXT, error TEXT, fetched INTEGER NOT NULL)",
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
cached(url) {
|
|
235
|
+
const fresh = (row) => row && this.now() - row.fetched < this.limits.ttlMs;
|
|
236
|
+
const hit = this.memory.get(url);
|
|
237
|
+
if (fresh(hit)) return hit;
|
|
238
|
+
const row = this.db
|
|
239
|
+
?.prepare("SELECT * FROM core_link_cache WHERE url=?")
|
|
240
|
+
.get(url);
|
|
241
|
+
if (fresh(row)) {
|
|
242
|
+
const value = { ...row, ok: !!row.ok };
|
|
243
|
+
this.memory.set(url, value);
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
remember(value) {
|
|
249
|
+
this.memory.set(value.url, value);
|
|
250
|
+
if (this.memory.size > 200)
|
|
251
|
+
this.memory.delete(this.memory.keys().next().value);
|
|
252
|
+
this.db
|
|
253
|
+
?.prepare(
|
|
254
|
+
"INSERT INTO core_link_cache(url,ok,title,site,description,text,error,fetched) VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(url) DO UPDATE SET ok=excluded.ok,title=excluded.title,site=excluded.site,description=excluded.description,text=excluded.text,error=excluded.error,fetched=excluded.fetched",
|
|
255
|
+
)
|
|
256
|
+
.run(
|
|
257
|
+
value.url,
|
|
258
|
+
value.ok ? 1 : 0,
|
|
259
|
+
value.title || "",
|
|
260
|
+
value.site || "",
|
|
261
|
+
value.description || "",
|
|
262
|
+
value.text || "",
|
|
263
|
+
value.error || "",
|
|
264
|
+
value.fetched,
|
|
265
|
+
);
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
async read(url) {
|
|
269
|
+
const hit = this.cached(url);
|
|
270
|
+
if (hit) return hit;
|
|
271
|
+
const site = (() => {
|
|
272
|
+
try {
|
|
273
|
+
return new URL(url).hostname;
|
|
274
|
+
} catch {
|
|
275
|
+
return "";
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
try {
|
|
279
|
+
const repo = url.match(GITHUB_REPO);
|
|
280
|
+
if (repo) return this.remember(await this.github(url, repo[1], repo[2]));
|
|
281
|
+
const page = await fetchCapped(url, {
|
|
282
|
+
fetcher: this.fetcher,
|
|
283
|
+
resolve: this.resolve,
|
|
284
|
+
limits: this.limits,
|
|
285
|
+
});
|
|
286
|
+
const html = /html/i.test(page.type) || /^\s*</.test(page.body);
|
|
287
|
+
const parts = html
|
|
288
|
+
? readable(page.body)
|
|
289
|
+
: { title: "", description: "", text: page.body.trim() };
|
|
290
|
+
return this.remember({
|
|
291
|
+
url,
|
|
292
|
+
ok: true,
|
|
293
|
+
site,
|
|
294
|
+
title: parts.title,
|
|
295
|
+
description: parts.description,
|
|
296
|
+
text: parts.text.slice(0, this.limits.textChars),
|
|
297
|
+
fetched: this.now(),
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return this.remember({
|
|
301
|
+
url,
|
|
302
|
+
ok: false,
|
|
303
|
+
site,
|
|
304
|
+
error: String(error.message || error).slice(0, 120),
|
|
305
|
+
fetched: this.now(),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async github(url, owner, name) {
|
|
310
|
+
const api = `https://api.github.com/repos/${owner}/${name}`;
|
|
311
|
+
const get = async (target, accept) =>
|
|
312
|
+
fetchCapped(target, {
|
|
313
|
+
fetcher: (u, o) =>
|
|
314
|
+
this.fetcher(u, { ...o, headers: { ...o.headers, Accept: accept } }),
|
|
315
|
+
resolve: this.resolve,
|
|
316
|
+
limits: this.limits,
|
|
317
|
+
});
|
|
318
|
+
const info = JSON.parse(
|
|
319
|
+
(await get(api, "application/vnd.github+json")).body || "{}",
|
|
320
|
+
);
|
|
321
|
+
let readme = "";
|
|
322
|
+
try {
|
|
323
|
+
readme = (await get(`${api}/readme`, "application/vnd.github.raw+json"))
|
|
324
|
+
.body;
|
|
325
|
+
} catch {
|
|
326
|
+
// A repository without a README still has its description.
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
url,
|
|
330
|
+
ok: true,
|
|
331
|
+
site: "github.com",
|
|
332
|
+
title: info.full_name || `${owner}/${name}`,
|
|
333
|
+
description: [
|
|
334
|
+
info.description,
|
|
335
|
+
info.language && `主要语言 ${info.language}`,
|
|
336
|
+
Number.isFinite(info.stargazers_count) &&
|
|
337
|
+
`${info.stargazers_count} star`,
|
|
338
|
+
]
|
|
339
|
+
.filter(Boolean)
|
|
340
|
+
.join(";"),
|
|
341
|
+
text: readme
|
|
342
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, "")
|
|
343
|
+
.replace(/<[^>]+>/g, " ")
|
|
344
|
+
.slice(0, this.limits.textChars),
|
|
345
|
+
fetched: this.now(),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
// Links in the messages she is reading now: what each page is, in its own
|
|
349
|
+
// words. No extra model call: the title, description and opening of the
|
|
350
|
+
// page are enough for a chat, and keep the turn fast.
|
|
351
|
+
async forMessages(messages) {
|
|
352
|
+
const wanted = [];
|
|
353
|
+
for (const m of messages || [])
|
|
354
|
+
for (const url of linksIn(m.text))
|
|
355
|
+
if (!wanted.some((w) => w.url === url))
|
|
356
|
+
wanted.push({ url, messageId: m.id });
|
|
357
|
+
const picked = wanted.slice(-this.limits.perTurn);
|
|
358
|
+
return Promise.all(
|
|
359
|
+
picked.map(async ({ url, messageId }) => {
|
|
360
|
+
const page = await this.read(url);
|
|
361
|
+
if (!page.ok)
|
|
362
|
+
return { messageId, url, site: page.site, fetched: false };
|
|
363
|
+
// A glance, not a briefing: title + a short opening is enough to
|
|
364
|
+
// talk; a long excerpt made her read out feature lists.
|
|
365
|
+
return {
|
|
366
|
+
messageId,
|
|
367
|
+
url,
|
|
368
|
+
site: page.site,
|
|
369
|
+
fetched: true,
|
|
370
|
+
title: page.title,
|
|
371
|
+
...(page.description
|
|
372
|
+
? { description: String(page.description).slice(0, 160) }
|
|
373
|
+
: {}),
|
|
374
|
+
excerpt: String(page.text || "").slice(0, 360),
|
|
375
|
+
};
|
|
376
|
+
}),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{Dt as e,E as t,Et as n,G as r,H as i,I as a,J as o,M as s,P as c,Q as l,St as u,Tt as d,U as f,W as p,X as m,Y as h,Z as g,_t as _,a as v,at as y,bt as b,dt as x,et as S,f as C,ft as w,gt as T,it as E,j as D,lt as O,m as k,n as A,o as j,rt as M,s as N,t as P,tt as F,ut as ee,v as I,wt as L,yt as R,z}from"./Empty-y1vXDIjQ.js";import{g as te,y as B}from"./mind-KkinyNC6.js";import{c as V,f as H,h as U,m as W,n as G,o as ne,p as K,u as q,y as re}from"./index-BokqwNXF.js";import{t as ie}from"./media-Ciozo_yb.js";import{t as J}from"./Field-CJG5lERx.js";async function ae(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),k(`/core/events?`+n.toString())}async function oe(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),k(`/core/traces?`+n.toString())}async function Y(e){return k(`/core/traces/`+e)}async function se(e){return k(`/simulate`,`POST`,e)}async function ce(e,t){return k(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function X(e){return k(`/core/replay`,`POST`,e)}async function le(e){return k(`/core/sessions`,`POST`,e)}async function ue(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function de(e,t){return k(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function fe(e){return k(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function pe(e,t){return k(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function me(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function he(e){return k(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var ge={class:`session-list`},_e={class:`list-head`},Z=[`placeholder`,`aria-label`],ve={class:`rows scroll-pane`},Q=[`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=A(M({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(t,{emit:s}){let c=t,f=s,p=u(``),m=o(()=>c.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(p.value.toLowerCase()))),_=o(()=>c.sessions.filter(e=>e.archived));return(o,s)=>(x(),l(`div`,ge,[h(`div`,_e,[h(`h2`,null,[S(e(L(a)(`会话`))+` `,1),h(`small`,null,e(m.value.length),1)]),h(`button`,{class:`small primary`,onClick:s[0]||=e=>f(`add`)},e(L(a)(`+ 添加会话`)),1)]),b(h(`input`,{"onUpdate:modelValue":s[1]||=e=>p.value=e,type:`search`,placeholder:L(a)(`搜索名称或号码`),"aria-label":L(a)(`搜索会话`)},null,8,Z),[[i,p.value]]),h(`div`,ve,[(x(!0),l(r,null,w(m.value,r=>(x(),l(`div`,{key:r.id,class:d([`session-row`,{selected:r.id===t.selected,paused:!r.enabled}])},[h(`button`,{class:`row-main`,"data-pick":r.id,"aria-current":r.id===t.selected?`true`:void 0,onClick:e=>f(`select`,r.id)},[h(`span`,{class:`badge`,style:n({"--hue":L(H)(r.id)})},e(r.kind===`private`?L(a)(`私`):L(a)(`群`)),5),h(`span`,ye,[h(`b`,null,e(L(U)(r)),1),h(`small`,null,e(r.enabled?L(a)(`参与中`):L(a)(`已暂停`)),1)]),t.unread[r.id]?(x(),l(`span`,{key:0,class:`unread`,"aria-label":L(a)(`{v} 条没细看`,{v:t.unread[r.id]})},e(t.unread[r.id]),9,be)):g(``,!0)],8,Q),h(`label`,{class:`mini-switch`,title:r.enabled?L(a)(`暂停参与`):L(a)(`开启参与`)},[h(`input`,{type:`checkbox`,checked:r.enabled,"aria-label":L(a)(`参与:{v}`,{v:L(U)(r)}),onChange:e=>f(`toggle`,r)},null,40,Se)],8,xe)],2))),128)),m.value.length?g(``,!0):(x(),l(`p`,Ce,e(L(a)(`暂无匹配的会话。`)),1)),_.value.length?(x(),l(`details`,we,[h(`summary`,null,e(L(a)(`已归档 · {length}`,{length:_.value.length})),1),(x(!0),l(r,null,w(_.value,t=>(x(),l(`article`,{key:t.id},[h(`b`,null,e(L(U)(t)),1),h(`div`,Te,[h(`button`,{class:`small`,"data-restore-session":t.id,onClick:e=>f(`restore`,t.id)},e(L(a)(`恢复`)),9,Ee),h(`button`,{class:`small danger`,"data-delete-session":t.id,onClick:e=>f(`remove`,t.id)},e(L(a)(`永久删除`)),9,De)])]))),128))])):g(``,!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 r=t,i=u(`group`),s=n,c=o(()=>r.channel===`qqbot`),d=o(()=>c.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function f(e){let t=e.target;s(`add`,{...Object.fromEntries(new FormData(t)),channel:r.channel})}return(n,r)=>(x(),m(ne,{open:t.open,title:L(a)(`添加群聊 / 私聊`),eyebrow:L(a)(`NEW PLACE`),width:`440px`,onClose:r[1]||=e=>s(`close`)},{default:R(()=>[h(`form`,{id:`addSession`,class:`stack`,onSubmit:p(f,[`prevent`])},[h(`p`,ke,e(L(a)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),c.value&&!t.ready?(x(),l(`p`,Ae,e(L(a)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):g(``,!0),F(J,{label:L(a)(`类型`)},{default:R(()=>[F(V,{modelValue:i.value,"onUpdate:modelValue":r[0]||=e=>i.value=e,name:`kind`,"aria-label":L(a)(`类型`),options:[{value:`group`,label:L(a)(`群聊`)},{value:`private`,label:L(a)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),F(J,{label:c.value?L(a)(`群 openid / 用户 openid`):L(a)(`群号 / QQ 号`),hint:c.value?L(a)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):L(a)(`4–20 位数字`)},{default:R(()=>[h(`input`,{name:`id`,required:``,pattern:d.value,inputmode:c.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),F(J,{label:L(a)(`显示名称`)},{default:R(()=>[...r[2]||=[h(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),h(`button`,{class:`primary`,disabled:c.value&&!t.ready},e(L(a)(`添加会话`)),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=A(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(t,{expose:_,emit:v}){let C=t,T=v,E=c({glanced:D(`TA 扫了一眼`),silent:D(`TA 看了,没出声`),deferred:D(`TA 睡着了,醒来再看`)}),O=u(`10001`),k=u(``),A=u(!0),j=u(),M=u({}),ee=()=>B(s(),{month:`long`,day:`numeric`,weekday:`short`}),I=o(()=>{let e=C.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=C.events[0]?.time??2**53-1;for(let n of C.traces)E[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 C.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 C.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function U(e){if(M.value[e.seq]){let{[e.seq]:t,...n}=M.value;M.value=n;return}M.value={...M.value,[e.seq]:{loading:!0,detail:null}};let t=te(e),n=null;try{n=t?await Y(t.id):null}catch{n=null}M.value[e.seq]&&(M.value={...M.value,[e.seq]:{loading:!1,detail:n}})}async function W(){await y(),j.value?.focus()}function G(){k.value.trim()&&!C.sending&&(T(`simulate`,{userId:O.value,text:k.value,mentioned:A.value}),k.value=``)}return _({focusComposer:W}),(o,s)=>(x(),l(`section`,Pe,[h(`header`,Fe,[t.compact?(x(),l(`button`,{key:0,class:`icon-button`,"aria-label":L(a)(`全部会话`),title:L(a)(`全部会话`),onClick:s[0]||=e=>T(`openList`)},` ☰ `,8,Ie)):g(``,!0),t.session?(x(),l(`span`,{key:1,class:`badge`,style:n({"--hue":L(H)(t.session.id)})},e(t.session.kind===`private`?L(a)(`私`):L(a)(`群`)),5)):g(``,!0),h(`div`,Le,[h(`h2`,null,e(t.session?.name||L(a)(`选择一段对话`)),1),h(`span`,Re,e(t.status),1)]),h(`div`,ze,[h(`button`,{class:`small sim-button`,disabled:!t.sessionId,onClick:W},e(L(a)(`模拟消息`)),9,Be),h(`button`,{class:`small`,onClick:s[1]||=e=>T(`openReplay`)},e(L(a)(`回到那一刻`)),1)])]),h(`div`,Ve,[(x(!0),l(r,null,w(I.value,t=>(x(),l(r,{key:t.key},[t.type===`day`?(x(),l(`div`,He,[h(`span`,null,e(t.label),1)])):t.type===`divider`?(x(),l(`div`,{key:1,class:`divider`,"data-status":t.status},[h(`span`,null,e(L(E)[t.status])+e(t.count>1?` × ${t.count}`:``),1),t.reasons.length?(x(),l(`small`,We,e(t.reasons.slice(0,2).join(`;`)),1)):g(``,!0)],8,Ue)):(x(),l(`article`,{key:2,class:d([`message`,{bot:t.event.role===`assistant`,simulated:t.event.payload.simulated}])},[t.event.role===`assistant`?g(``,!0):(x(),l(`span`,{key:0,class:`avatar`,style:n({"--hue":L(H)(String(t.event.payload.userId||t.event.payload.name))}),"aria-hidden":`true`},e(L(K)(t.event.payload.name)),5)),h(`div`,Ge,[h(`div`,Ke,[h(`b`,null,e(t.event.payload.name),1),h(`time`,null,e(L(q)(t.event.time)),1),t.event.payload.simulated?(x(),l(`span`,qe,e(L(a)(`模拟`)),1)):g(``,!0)]),h(`p`,Je,e(t.event.payload.text),1),t.event.role===`assistant`?(x(),l(r,{key:0},[h(`button`,{class:`text-button why-toggle`,"aria-expanded":!!M.value[t.event.seq],onClick:e=>U(t.event)},e(M.value[t.event.seq]?L(a)(`收起`):L(a)(`为什么这么说`)),9,Ye),M.value[t.event.seq]?(x(),l(`div`,Xe,[M.value[t.event.seq].loading?(x(),l(`p`,Ze,e(L(a)(`正在翻当时的记录…`)),1)):(x(),l(r,{key:1},[M.value[t.event.seq].detail?.data?.decision?.appraisal?(x(),l(`p`,Qe,[h(`b`,null,e(L(a)(`TA 当时的理解:`)),1),S(e(M.value[t.event.seq].detail.data.decision.appraisal),1)])):g(``,!0),h(`p`,null,[h(`b`,null,e(L(a)(`理由:`)),1),S(e(M.value[t.event.seq].detail?.data?.reason||M.value[t.event.seq].detail?.data?.decision?.reason||R(t.event)?.reason||L(a)(`没有留下理由。`)),1)]),M.value[t.event.seq].detail?.data?.snapshot?.knowledge?.length?(x(),l(`p`,$e,[h(`b`,null,e(L(a)(`引用的资料:`)),1),S(e(M.value[t.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):g(``,!0),R(t.event)?(x(),l(`label`,et,[S(e(L(a)(`这句怎么样?`))+` `,1),F(V,{"model-value":R(t.event).feedback||``,"aria-label":L(a)(`这句怎么样?`),options:[{value:``,label:L(a)(`选择评价`)},...Object.entries(L(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>T(`feedback`,R(t.event).id,e)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):g(``,!0)],64))])):g(``,!0)],64)):g(``,!0)])],2))],64))),128)),t.events.length?g(``,!0):(x(),m(P,{key:0,title:t.sessionId?L(a)(`等待新的消息`):L(a)(`选择一段对话`),text:L(a)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),t.demo?(x(),l(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:p(G,[`prevent`])},[h(`div`,tt,[h(`span`,nt,e(L(a)(`模拟消息`)),1),h(`span`,rt,e(L(a)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),h(`div`,it,[h(`label`,at,[h(`span`,ot,e(L(a)(`体验用户 ID`)),1),b(h(`input`,{"onUpdate:modelValue":s[2]||=e=>O.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:L(a)(`体验用户 ID`)},null,8,st),[[i,O.value]])]),b(h(`textarea`,{ref_key:`composer`,ref:j,"onUpdate:modelValue":s[3]||=e=>k.value=e,name:`text`,rows:`1`,placeholder:L(a)(`以这位群友的身份说一句…`),required:``,onKeydown:f(p(G,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[i,k.value]]),h(`label`,lt,[b(h(`input`,{"onUpdate:modelValue":s[4]||=e=>A.value=e,type:`checkbox`},null,512),[[z,A.value]]),S(e(L(a)(`视为 @`)),1)]),h(`button`,{class:`primary`,disabled:t.sending||!t.sessionId},e(t.sending?L(a)(`TA 在想…`):L(a)(`发送模拟消息`)),9,ut)])],32)):(x(),l(`div`,dt,[h(`span`,ft,e(L(a)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),h(`button`,{class:`small`,onClick:s[5]||=e=>L(N).chatOpen=!0},e(L(a)(`和 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=A(M({__name:`SessionPanel`,props:E({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:E([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(t,{emit:n}){let s=t,f=T(t,`settingsOpen`),m=T(t,`tab`),v=n,y=c([D(`近期细摘要`),D(`中期摘要`),D(`较早摘要`),D(`远期摘要`)]),C=u(0),E=o(()=>s.decisions.filter(e=>e.session_id===s.session?.id&&e.reply)),O=o(()=>Math.max(1,Math.ceil(E.value.length/$))),k=u(!1),A=u({});_(()=>s.session?.policy,e=>{A.value={...e||{}}},{immediate:!0,deep:!0}),_(()=>s.session?.id,()=>{C.value=0,k.value=!1});function j(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
|
|
2
|
-
|
|
3
|
-
`):``}function M(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;v(`save`,n)}return(n,o)=>(x(),l(`section`,mt,[F(G,{modelValue:m.value,"onUpdate:modelValue":o[0]||=e=>m.value=e,label:L(a)(`这个会话`),items:[{key:`here`,label:L(a)(`这个会话`)},{key:`feedback`,label:L(a)(`反馈`)}]},null,8,[`modelValue`,`label`,`items`]),t.session?m.value===`here`?(x(),l(`div`,gt,[h(`div`,_t,[h(`div`,vt,[h(`div`,null,[h(`span`,yt,e(L(a)(`TA 在这里的样子`)),1),j(t.group)?(x(),l(r,{key:0},[h(`p`,{class:d([`face-body`,k.value?`open`:`clamp`])},e(j(t.group)),3),j(t.group).length>96?(x(),l(`button`,{key:0,type:`button`,class:`text-button`,onClick:o[1]||=e=>k.value=!k.value},e(k.value?L(a)(`收起`):L(a)(`展开完整内容`)),1)):g(``,!0)],64)):(x(),l(`p`,bt,e(L(a)(`还没有形成在这里的样子。`)),1)),t.group?.face?.aspiration?(x(),l(`small`,xt,e(L(a)(`想成为:{aspiration}`,{aspiration:t.group.face.aspiration})),1)):g(``,!0)]),h(`div`,null,[h(`span`,St,e(t.session.kind===`private`?L(a)(`这段私聊给 TA 的感觉`):L(a)(`这个群给 TA 的感觉`)),1),h(`p`,null,e(t.group?.bond?.feel||L(a)(`刚来这里不久`)),1)])]),h(`p`,Ct,e(L(a)(`在这里开不开口由 TA 自己决定:没有参与概率和冷却。TA 对这里每个人的感觉在「人际」里看。`)),1),h(`div`,wt,[h(`button`,{id:`toggleSession`,"data-toggle":t.session.id,class:d({primary:!t.session.enabled}),onClick:o[2]||=e=>v(`toggle`,t.session)},e(t.session.enabled?L(a)(`暂停参与`):L(a)(`开启参与`)),11,Tt),h(`button`,{"data-archive":t.session.id,onClick:o[3]||=e=>v(`archive`,t.session.id)},e(L(a)(`归档`)),9,Et),h(`button`,{id:`clearLiveContext`,onClick:o[4]||=e=>v(`clear`)},e(L(a)(`清空上下文`)),1)]),h(`details`,{class:`fold session-settings`,open:f.value,onToggle:o[19]||=e=>f.value=e.target.open},[h(`summary`,null,e(L(a)(`会话设置`)),1),(x(),l(`form`,{key:t.session.id,"data-session":t.session.id,class:`stack tight`,onSubmit:p(M,[`prevent`]),onInput:o[17]||=e=>L(N).dirty=!0,onChange:o[18]||=e=>L(N).dirty=!0},[h(`p`,kt,e(L(a)(`所有会话和独处都用模型库里的默认模型;其余已启用的模型按列表顺序做备用。她在这个聊天窗里用什么样子,由她自己从经历里决定。`)),1),h(`label`,null,[S(e(L(a)(`会话名称`))+` `,1),h(`input`,{name:`name`,maxlength:`100`,autocomplete:`off`,value:t.session.name},null,8,At),h(`small`,jt,e(L(a)(`官方机器人平台不提供群名称,可以在这里自己起一个`)),1)]),h(`label`,Mt,[b(h(`input`,{name:`selectiveVision`,type:`checkbox`,"onUpdate:modelValue":o[5]||=e=>A.value.selectiveVision=e,onInput:o[6]||=p(()=>{},[`stop`])},null,544),[[z,A.value.selectiveVision]]),S(e(L(a)(`节能看图:只在被 @ 或明确要求时,看这一条和附近的图`)),1)]),h(`details`,Nt,[h(`summary`,null,e(L(a)(`高级策略`)),1),h(`div`,Pt,[h(`label`,null,[S(e(L(a)(`聚合窗口 ms`)),1),b(h(`input`,{name:`aggregateMs`,type:`number`,"onUpdate:modelValue":o[7]||=e=>A.value.aggregateMs=e},null,512),[[i,A.value.aggregateMs,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`最长等待 ms`)),1),b(h(`input`,{name:`maxWaitMs`,type:`number`,"onUpdate:modelValue":o[8]||=e=>A.value.maxWaitMs=e},null,512),[[i,A.value.maxWaitMs,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`近期原文条数(10–500)`)),1),b(h(`input`,{name:`contextMessages`,type:`number`,min:`10`,max:`500`,"onUpdate:modelValue":o[9]||=e=>A.value.contextMessages=e},null,512),[[i,A.value.contextMessages,void 0,{number:!0}]])]),h(`label`,null,[S(e(L(a)(`一轮总字数`)),1),b(h(`input`,{name:`maxReply`,type:`number`,"onUpdate:modelValue":o[10]||=e=>A.value.maxReply=e},null,512),[[i,A.value.maxReply,void 0,{number:!0}]])]),h(`label`,Ft,[b(h(`input`,{name:`compaction`,type:`checkbox`,"onUpdate:modelValue":o[11]||=e=>A.value.compaction=e,onInput:o[12]||=p(()=>{},[`stop`])},null,544),[[z,A.value.compaction]]),S(e(L(a)(`上下文压缩:更早的聊天定期整理成分层摘要,越近越详细`)),1)]),h(`label`,It,[b(h(`input`,{name:`memory`,type:`checkbox`,"onUpdate:modelValue":o[13]||=e=>A.value.memory=e,onInput:o[14]||=p(()=>{},[`stop`])},null,544),[[z,A.value.memory]]),S(e(L(a)(`长期记忆`)),1)]),h(`label`,Lt,[b(h(`input`,{name:`deepCheck`,type:`checkbox`,"onUpdate:modelValue":o[15]||=e=>A.value.deepCheck=e,onInput:o[16]||=p(()=>{},[`stop`])},null,544),[[z,A.value.deepCheck]]),S(e(L(a)(`倾诉、纠正和危机时额外复审一次回复`)),1)])])]),h(`div`,Rt,[h(`button`,zt,e(L(a)(`保存会话设置`)),1),h(`small`,Bt,e(L(N).dirty?L(a)(`有未保存的修改`):L(a)(`保存后下一轮生效`)),1)])],40,Ot))],40,Dt),h(`details`,Vt,[h(`summary`,null,e(L(a)(`语境摘要 · {length} 段`,{length:t.summaries.length})),1),t.summaries.length?g(``,!0):(x(),l(`p`,Ht,e(L(a)(`还没有摘要。聊天超过近期原文条数后,更早的内容会自动整理到这里。`)),1)),(x(!0),l(r,null,w(t.summaries,t=>(x(),l(`article`,{key:t.id,class:`summary`},[h(`header`,null,[h(`b`,null,e(L(y)[t.level]||L(a)(`摘要`)),1),h(`small`,Ut,e(L(a)(`{period} · {messages} 条消息`,{period:t.period,messages:t.messages})),1)]),h(`p`,null,e(t.summary),1),t.keyPoints?.length?(x(),l(`ul`,Wt,[(x(!0),l(r,null,w(t.keyPoints,(t,n)=>(x(),l(`li`,{key:n},e(t.open?L(a)(`未完:`):``)+e(t.text),1))),128))])):g(``,!0)]))),128))])])])):(x(),l(`div`,Gt,[h(`p`,Kt,e(L(a)(`针对具体回复评价口吻;这是 TA 在这个会话里听到的话,不会改写天性。`)),1),h(`div`,qt,[(x(!0),l(r,null,w(E.value.slice(C.value*$,C.value*$+$),t=>(x(),l(`article`,{key:t.id,class:`feedback-item`},[h(`time`,Jt,e(L(q)(t.time)),1),h(`p`,Yt,e(t.reply),1),h(`label`,null,[S(e(L(a)(`这句回复怎么样?`))+` `,1),F(V,{"data-feedback":t.id,"model-value":t.feedback||``,"aria-label":L(a)(`这句回复怎么样?`),options:[{value:``,label:L(a)(`选择评价`)},...Object.entries(L(N).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>v(`feedback`,t.id,e)},null,8,[`data-feedback`,`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])]))),128)),E.value.length?g(``,!0):(x(),l(`p`,Xt,e(L(a)(`还没有可以评价的回复。`)),1))]),h(`div`,Zt,[h(`button`,{class:`small`,disabled:C.value===0,onClick:o[20]||=e=>C.value--},`←`,8,Qt),h(`span`,null,e(C.value+1)+` / `+e(O.value),1),h(`button`,{class:`small`,disabled:C.value+1>=O.value,onClick:o[21]||=e=>C.value++},` → `,8,$t)])])):(x(),l(`div`,ht,[F(P,{title:L(a)(`还没有选中会话`),text:L(a)(`从左边选一个群聊或私聊。`)},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=A(M({__name:`ReplaySheet`,props:{open:{type:Boolean},sessionId:{},events:{}},emits:[`close`,`load`],setup(t,{emit:n}){let o=t,s=n,f=c({sent:D(`开口了`),silent:D(`没出声`),glanced:D(`扫了一眼`),deferred:D(`睡着了`),running:D(`处理中`),error:D(`失败`),complete:D(`完成`),stale:D(`旧稿作废`),cancelled:D(`已取消`),interrupted:D(`已中断`)}),v=c({live:D(`真实聊天`),demo:D(`模拟预览`),replay:D(`测试回放`),memory:D(`记忆整理`),summary:D(`语境压缩`)}),y=u([]),C=u(null),T=u(``),E=u(``),O=u(!1),k=u(``);async function A(){o.sessionId&&(y.value=await oe(o.sessionId))}function j(){let e=o.events.map(e=>Number(e.seq));e.length&&(T.value||=String(Math.min(...e)),E.value||=String(Math.max(...e)))}async function M(){if(!O.value){O.value=!0;try{I(a(`回放正在运行`));let e=await X({session:o.sessionId,from:Number(T.value),to:Number(E.value)});k.value=JSON.stringify(e,null,2),I(a(`回放结束`)),await A()}catch(e){I(e.message,!0)}finally{O.value=!1}}}async function N(e){C.value=await Y(e),k.value=JSON.stringify(C.value,null,2)}function P(e){return e?a(`输入 {v}(缓存读 {v2} / 写 {v3})· 输出 {v4}`,{v:W(e.input),v2:W(e.cachedRead),v3:W(e.cacheWrite),v4:W(e.output)}):`—`}return _(()=>[o.open,o.sessionId],()=>{o.open&&(T.value=``,E.value=``,C.value=null,j(),A())},{immediate:!0}),_(()=>o.events.length,j),(n,o)=>(x(),m(ne,{open:t.open,title:L(a)(`回到那一刻`),eyebrow:`REPLAY`,width:`1180px`,onClose:o[3]||=e=>s(`close`)},{default:R(()=>[h(`div`,tn,[h(`section`,nn,[h(`div`,rn,[h(`h3`,null,e(L(a)(`历史消息`)),1),h(`button`,{id:`loadMessages`,class:`small`,onClick:o[0]||=e=>{s(`load`),A()}},e(L(a)(`↻ 加载`)),1)]),h(`div`,an,[t.events.length?(x(),l(`table`,on,[h(`thead`,null,[h(`tr`,null,[h(`th`,null,e(L(a)(`序号`)),1),h(`th`,null,e(L(a)(`发送者`)),1),h(`th`,null,e(L(a)(`消息`)),1)])]),h(`tbody`,null,[(x(!0),l(r,null,w([...t.events].reverse(),t=>(x(),l(`tr`,{key:t.seq},[h(`td`,null,e(t.seq),1),h(`td`,null,e(t.payload.name),1),h(`td`,null,e(t.payload.text),1)]))),128))])])):(x(),l(`p`,sn,e(L(a)(`当前会话暂无历史消息。`)),1))]),h(`form`,{class:`range`,onSubmit:p(M,[`prevent`])},[h(`label`,null,[S(e(L(a)(`起始序号`)),1),b(h(`input`,{"onUpdate:modelValue":o[1]||=e=>T.value=e,name:`from`,type:`number`,required:``},null,512),[[i,T.value]])]),h(`label`,null,[S(e(L(a)(`截止序号`)),1),b(h(`input`,{"onUpdate:modelValue":o[2]||=e=>E.value=e,name:`to`,type:`number`,required:``},null,512),[[i,E.value]])]),h(`button`,{id:`replay`,class:`primary`,disabled:O.value||!t.events.length},e(O.value?L(a)(`正在回放…`):L(a)(`回放这段对话`)),9,cn),h(`p`,ln,e(L(a)(`隔离回放:只测试回复,不发送 QQ,不修改真实记忆和 TA 的心智。`)),1)],32)]),h(`section`,un,[h(`div`,dn,[h(`h3`,null,e(L(a)(`处理记录`)),1)]),h(`div`,fn,[(x(!0),l(r,null,w(y.value,t=>(x(),l(`button`,{key:t.id,"data-trace":t.id,class:d([`trace-row`,{selected:C.value?.id===t.id}]),onClick:e=>N(t.id)},[h(`span`,null,[h(`time`,null,e(L(q)(t.time)),1),h(`small`,{class:`chip`,"data-tone":t.status===`error`?`danger`:`quiet`},e(L(f)[t.status]||t.status),9,mn)]),h(`p`,null,e(t.reason||t.error||L(a)(`查看处理详情`)),1)],10,pn))),128)),y.value.length?g(``,!0):(x(),l(`p`,hn,e(L(a)(`有新的消息处理记录后会显示在这里。`)),1))])]),h(`section`,gn,[h(`div`,_n,[h(`h3`,null,e(L(a)(`运行详情`)),1)]),h(`div`,vn,[C.value?(x(),l(`div`,yn,[h(`span`,null,[S(e(L(a)(`模式`)),1),h(`b`,null,e(L(v)[C.value.mode]||C.value.mode),1)]),h(`span`,null,[S(e(L(a)(`耗时`)),1),h(`b`,null,e(C.value.data?.elapsed??`—`)+` ms`,1)]),h(`span`,null,[S(e(L(a)(`模型调用`)),1),h(`b`,null,e(L(a)(`{v} 次`,{v:C.value.data?.calls?.length??0})),1)]),h(`span`,bn,[o[4]||=S(`Token`,-1),h(`b`,null,e(P(C.value.data?.tokens)),1)])])):g(``,!0),C.value?(x(),l(`p`,xn,e(C.value.reason||C.value.data?.decision?.reason||L(a)(`展开下方查看上下文、模型输入与原始输出。`)),1)):g(``,!0),h(`pre`,Sn,e(k.value||L(a)(`选择一条处理记录,查看最终上下文、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=A(M({__name:`ChatsPage`,setup(e){let n=ie(`(max-width: 1100px)`),r=o(()=>N.core?.sessions||[]),i=o(()=>r.value.filter(e=>!e.archived)),c=sessionStorage.activeSession,f=u(i.value.some(e=>e.id===c)?c:i.value[0]?.id||``),p=o(()=>i.value.find(e=>e.id===f.value)||null),b=u([]),S=u([]),w=u([]),T=u([]),E=u({}),D=u(``),k=u(!1),A=u(!1),M=u(!1),P=u(N.sub===`settings`),z=u(N.sub===`replay`),B=u(`here`),V=o(()=>N.health?.decisions||[]),H=o(()=>T.value.find(e=>e.session===f.value)||null),U=!1,W=!1,G=!1,K=``,q=0,J=null,Y=0;async function X(e=!1){if(!f.value)return;if(U){W=!0,G||=e;return}let t=f.value,n=e||K!==t;U=!0;try{let[e,r]=await Promise.all([ae(t,n?void 0:q),oe(t,{limit:30,compact:!0})]);if(f.value!==t)return;let i=document.getElementById(`liveMessages`),o=K!==t||!i||i.scrollHeight-i.scrollTop-i.clientHeight<80;n?(b.value=e.reverse(),q=0):e.length&&(b.value=[...b.value,...e]);for(let t of e)q=Math.max(q,Number(t.seq)||0);!n&&e.length===100&&(W=!0),S.value=r,K=t,await y();let c=document.getElementById(`liveMessages`);c&&o&&(c.scrollTop=c.scrollHeight),D.value=a(`实时同步 · `)+new Date().toLocaleTimeString(s())}catch(e){D.value=a(`同步失败:`)+e.message}finally{if(U=!1,f.value!==t)W=!1,X();else if(W){let e=G;W=!1,G=!1,X(e)}}}async function ge(){try{let[e,t]=await Promise.all([te.bonds(),te.overview()]);T.value=e.groups,E.value=Object.fromEntries(t.attention.map(e=>[e.session,e.unread]))}catch{}}_(f,e=>{if(J?.abort(),e&&(sessionStorage.activeSession=e),b.value=[],S.value=[],w.value=[],K=``,q=0,W=!1,G=!1,M.value=!1,X(),!e)return;me(e).then(t=>{f.value===e&&(w.value=[...t].reverse())}).catch(()=>{});let t=new AbortController;J=t;let n=``;re(e=>{let t=Number(e?.messages);Number.isSafeInteger(t)&&t<q&&(K=``,q=0,b.value=[]);let r=JSON.stringify(e),i=n!==``&&r!==n;n=r,i&&X()},{session:e,signal:t.signal,reconnectMs:1e3})},{immediate:!0}),_(i,e=>{e.some(e=>e.id===f.value)||(f.value=e[0]?.id||``)}),_(B,async e=>{if(e===`feedback`)try{N.health=await C()}catch{}}),_(z,e=>j(e?`replay`:P.value?`settings`:``)),_(()=>N.sub,e=>{e===`replay`&&(z.value=!0),e===`settings`&&(P.value=!0)}),_(()=>N.pulse,ge),O(()=>{ge(),Y=window.setInterval(()=>X(!0),15e3)}),ee(()=>{clearInterval(Y),J?.abort()});async function _e(e){try{let t=await le(e);await v(),f.value=t?.sessionId||`${e.kind}:${e.id}`,P.value=!0,B.value=`here`,A.value=!1,I(a(`会话已添加`))}catch(e){I(e.message,!0)}}async function Z(e){await ue(e.id,!e.enabled),await v()}async function ve(e){await de(e,!0),await v()}async function Q(e){await de(e,!1),await v()}async function ye(e){await t(a(`永久删除这个会话?它的消息和设置都会消失。`),{title:a(`永久删除`),confirmText:a(`删除`),danger:!0})&&(await fe(e),await v())}async function be(e){if(p.value)try{await pe(p.value.id,{...e,name:String(e.name||``).trim()||p.value.name}),N.dirty=!1,I(a(`会话配置已保存`)),await v()}catch(e){I(e.message,!0)}}async function xe(){f.value&&await t(a(`确定清空这个会话的消息上下文吗?长期记忆不会删除。`),{title:a(`清空上下文`),confirmText:a(`清空`),danger:!0})&&(await he(f.value),b.value=[],S.value=[],K=``,q=0,await X(!0))}async function Se(e){if(!k.value&&f.value){k.value=!0;try{let t=await se({sessionId:f.value,...e});I(t.reason||a(`已发送`)),N.health=await C(),await X()}catch(e){I(e.message,!0)}finally{k.value=!1}}}async function Ce(e,t){try{await ce(e,t),I(a(`反馈已保存`)),N.health=await C()}catch(e){I(e.message,!0)}}return(e,t)=>(x(),l(`div`,{class:d([`page chats`,{compact:L(n)}])},[L(n)?g(``,!0):(x(),l(`aside`,wn,[F(Oe,{sessions:r.value,selected:f.value,unread:E.value,onSelect:t[0]||=e=>f.value=e,onAdd:t[1]||=e=>A.value=!0,onToggle:Z,onRestore:Q,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])),h(`div`,Tn,[F(pt,{"session-id":f.value,session:p.value,events:b.value,traces:S.value,decisions:V.value,status:D.value||L(a)(`请选择会话`),demo:!!L(N).health.settings.demo,compact:L(n),sending:k.value,onSimulate:Se,onFeedback:Ce,onOpenList:t[2]||=e=>M.value=!0,onOpenReplay:t[3]||=e=>z.value=!0},null,8,[`session-id`,`session`,`events`,`traces`,`decisions`,`status`,`demo`,`compact`,`sending`])]),h(`aside`,En,[F(en,{"settings-open":P.value,"onUpdate:settingsOpen":t[4]||=e=>P.value=e,tab:B.value,"onUpdate:tab":t[5]||=e=>B.value=e,session:p.value,group:H.value,summaries:w.value,decisions:V.value,onToggle:Z,onArchive:ve,onClear:xe,onSave:be,onFeedback:Ce},null,8,[`settings-open`,`tab`,`session`,`group`,`summaries`,`decisions`])]),L(n)?(x(),m(ne,{key:1,open:M.value,title:L(a)(`全部会话`),eyebrow:`CHATS`,width:`400px`,onClose:t[8]||=e=>M.value=!1},{default:R(()=>[h(`div`,Dn,[F(Oe,{sessions:r.value,selected:f.value,unread:E.value,onSelect:t[6]||=e=>f.value=e,onAdd:t[7]||=e=>A.value=!0,onToggle:Z,onRestore:Q,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])]),_:1},8,[`open`,`title`])):g(``,!0),F(Ne,{open:A.value,channel:L(N).health?.connection?.channel===`qqbot`?`qqbot`:`onebot`,ready:!!L(N).health?.connection?.qqbot?.appId,onClose:t[9]||=e=>A.value=!1,onAdd:_e},null,8,[`open`,`channel`,`ready`]),F(Cn,{open:z.value,"session-id":f.value,events:b.value,onClose:t[10]||=e=>z.value=!1,onLoad:t[11]||=e=>X(!0)},null,8,[`open`,`session-id`,`events`])],2))}}),[[`__scopeId`,`data-v-c328f9fe`]]);export{On as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Dt as e,Q as t,Tt as n,Y as r,Z as i,dt as a,pt as o,rt as s}from"./Empty-y1vXDIjQ.js";var c={key:0},l=s({__name:`Field`,props:{label:{},hint:{},wide:{type:Boolean}},setup(s){return(l,u)=>(a(),t(`label`,{class:n({wide:s.wide})},[r(`span`,null,e(s.label),1),o(l.$slots,`default`),s.hint?(a(),t(`small`,c,e(s.hint),1)):i(``,!0)],2))}});export{l as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{$ as e,Ct as t,D as n,Dt as r,E as i,Et as a,G as o,H as s,I as c,J as l,Q as u,St as d,Tt as f,W as p,X as m,Y as h,Z as g,_,_t as v,bt as y,dt as b,et as x,ft as S,lt as C,n as w,o as ee,rt as T,s as E,st as D,t as O,tt as k,v as A,wt as j,yt as te}from"./Empty-y1vXDIjQ.js";import{_ as M,c as N,d as P,g as F,h as ne,u as I,v as L}from"./mind-KkinyNC6.js";import{a as re,b as ie,n as ae,o as oe,r as R,u as z,v as B,x as V}from"./index-BokqwNXF.js";import{t as se}from"./media-Ciozo_yb.js";var H={class:`ribbon`},U={class:`ribbon-head`},W={class:`ribbon-title`},G={class:`eyebrow`},K={key:0,class:`latest-mood`},q=[`viewBox`,`aria-label`],J=[`x2`,`y1`,`y2`],Y=[`d`],ce=[`d`],le=[`d`],ue=[`cx`,`cy`,`r`],de={key:1,class:`muted empty-line`},fe=[`aria-label`],X=150,Z=58,pe=w(T({__name:`MoodRibbon`,props:{moods:{},baseline:{},timeZone:{}},setup(e){let t=e,n=X/2,i=d(),a=d(1e3),s=null;C(()=>{s=new ResizeObserver(([e])=>{a.value=Math.max(240,Math.round(e.contentRect.width))}),i.value&&s.observe(i.value)}),D(()=>s?.disconnect());let p=l(()=>{let e=[...t.moods].sort((e,t)=>e.created-t.created),r=e.length>1?(a.value-60)/(e.length-1):0;return e.map((e,t)=>({...e,x:30+t*r,y:n-Math.max(-1,Math.min(1,e.valence))*Z}))}),m=l(()=>{let e=p.value;if(e.length<2)return``;let t=`M${e[0].x.toFixed(1)},${e[0].y.toFixed(1)}`;for(let n=0;n<e.length-1;n++){let r=e[n-1]||e[n],i=e[n],a=e[n+1],o=e[n+2]||a;t+=`C${(i.x+(a.x-r.x)/6).toFixed(1)},${(i.y+(a.y-r.y)/6).toFixed(1)} ${(a.x-(o.x-i.x)/6).toFixed(1)},${(a.y-(o.y-i.y)/6).toFixed(1)} ${a.x.toFixed(1)},${a.y.toFixed(1)}`}return t}),_=l(()=>{let e=p.value;return m.value?`${m.value}L${e[e.length-1].x},${X}L${e[0].x},${X}Z`:``}),v=l(()=>n-(t.baseline??.15)*Z),y=l(()=>[...t.moods].slice(0,6)),x=l(()=>y.value[0]);return(t,n)=>(b(),u(`section`,H,[h(`div`,U,[h(`div`,W,[h(`span`,G,r(j(c)(`MOOD / 此刻的天气`)),1),h(`h2`,null,r(j(c)(`心情有自己的潮汐。`)),1),h(`p`,null,r(j(c)(`最近 {length} 次被经历牵动;有起伏,也会慢慢平静。`,{length:e.moods.length})),1)]),x.value?(b(),u(`div`,K,[h(`span`,null,r(j(c)(`刚刚留下的心情`)),1),h(`b`,null,r(x.value.feeling),1),h(`small`,null,r(x.value.cause||j(c)(`暂时说不清缘由`)),1)])):g(``,!0)]),h(`div`,{ref_key:`box`,ref:i,class:`ribbon-box`},[m.value?(b(),u(`svg`,{key:0,class:`ribbon-chart`,viewBox:`0 0 ${a.value} ${X}`,role:`img`,"aria-label":j(c)(`最近的心情曲线`)},[h(`defs`,null,[h(`linearGradient`,{id:`ribbon-stroke`,x1:`0`,y1:`0`,x2:`0`,y2:X,gradientUnits:`userSpaceOnUse`},[...n[0]||=[h(`stop`,{offset:`0%`,style:{"stop-color":`var(--cheek)`}},null,-1),h(`stop`,{offset:`50%`,style:{"stop-color":`var(--accent)`}},null,-1),h(`stop`,{offset:`100%`,style:{"stop-color":`var(--orb-c)`}},null,-1)]]),n[1]||=h(`linearGradient`,{id:`ribbon-fill`,x1:`0`,y1:`0`,x2:`0`,y2:`1`},[h(`stop`,{offset:`0%`,style:{"stop-color":`var(--accent)`,"stop-opacity":`0.22`}}),h(`stop`,{offset:`100%`,style:{"stop-color":`var(--accent)`,"stop-opacity":`0`}})],-1)]),h(`line`,{class:`base`,x1:`0`,x2:a.value,y1:v.value,y2:v.value},null,8,J),h(`path`,{class:`area`,d:_.value},null,8,Y),h(`path`,{class:`line-glow`,d:m.value},null,8,ce),h(`path`,{class:`line`,d:m.value},null,8,le),(b(!0),u(o,null,S(p.value,t=>(b(),u(`g`,{key:t.id,class:f([`pt`,t.valence>=0?`warm`:`cool`])},[h(`circle`,{cx:t.x,cy:t.y,r:4+t.intensity*6},[h(`title`,null,r(j(z)(t.created,e.timeZone))+` `+r(t.feeling)+`:`+r(t.cause||j(c)(`说不清为什么`)),1)],8,ue)],2))),128))],8,q)):(b(),u(`p`,de,r(j(c)(`心情还没有被什么事牵动过。每一次经历都可能推动 TA 的心情,随后又会慢慢回到平常。`)),1))],512),y.value.length?(b(),u(`ul`,{key:0,class:`recent`,"aria-label":j(c)(`最近的心情`)},[(b(!0),u(o,null,S(y.value,t=>(b(),u(`li`,{key:t.id,class:f(t.valence>=0?`warm`:`cool`)},[h(`b`,null,r(t.feeling),1),h(`span`,null,r(t.cause||j(c)(`说不清为什么`)),1),h(`small`,null,r(j(z)(t.created,e.timeZone))+` · `+r(j(I)[t.origin]||t.origin),1)],2))),128))],8,fe)):g(``,!0)]))}}),[[`__scopeId`,`data-v-fef06576`]]),me=[`aria-label`],he={class:`self-scene`},ge={class:`scene-copy`},_e={class:`eyebrow`},ve={class:`scene-stats`},ye={class:`self-lens`,"aria-hidden":`true`},be={class:`orb-seat`},xe={class:`currents-head`},Se={class:`current-grid`},Ce={class:`current-head`},we={class:`current-number`},Te={class:`flecks`},Ee=[`data-thread`,`onClick`],De={class:`fleck-copy`},Oe=[`aria-expanded`,`onClick`],ke={"aria-hidden":`true`},Q=6,Ae=w(T({__name:`StarMap`,props:{threads:{},kinds:{},activity:{}},emits:[`open`],setup(t,{emit:n}){let i=t,s=n,p=d({}),m=l(()=>[...new Set(i.threads.map(e=>e.kind))].map(e=>({kind:e,label:i.kinds[e]||e,color:N[e]||`var(--accent)`,items:i.threads.filter(t=>t.kind===e).sort((e,t)=>(t.salience??0)-(e.salience??0))}))),_=l(()=>i.threads.filter(e=>!e.faded&&(e.salience??0)>=.5).length);function v(e){return e.faded?c(`暂时放远`):e.core?c(`慢慢笃定`):c(`还在生长`)}function y(e){return p.value[e.kind]?e.items:e.items.slice(0,Q)}function C(e){p.value={...p.value,[e]:!p.value[e]}}return(n,i)=>(b(),u(`section`,{class:`starmap`,"aria-label":j(c)(`心灵星图:TA 关于自己的线索`)},[h(`div`,he,[h(`div`,ge,[h(`span`,_e,r(j(c)(`SELF / 正在形成的自己`)),1),h(`h2`,null,[x(r(j(c)(`不是一张固定的画像,`)),1),i[0]||=h(`br`,null,null,-1),h(`em`,null,r(j(c)(`而是一直在生长。`)),1)]),h(`p`,null,r(j(c)(`一件经历可能留下一点痕迹。点开一枚光片,可以看到它从哪里来、后来又怎么改变。`)),1),h(`div`,ve,[h(`span`,null,[h(`b`,null,r(t.threads.length),1),x(` `+r(j(c)(`枚线索`)),1)]),h(`span`,null,[h(`b`,null,r(_.value),1),x(` `+r(j(c)(`枚此刻贴近`)),1)])])]),h(`div`,ye,[i[1]||=e(`<span class="lens-halo halo-one" data-v-f3560fca></span><span class="lens-halo halo-two" data-v-f3560fca></span><span class="lens-droplet droplet-one" data-v-f3560fca></span><span class="lens-droplet droplet-two" data-v-f3560fca></span><span class="lens-droplet droplet-three" data-v-f3560fca></span>`,5),h(`div`,be,[k(B,{mood:j(V),activity:t.activity||`idle`,size:106},null,8,[`mood`,`activity`])])])]),h(`div`,xe,[h(`div`,null,[i[2]||=h(`span`,{class:`eyebrow`},`INNER CURRENTS`,-1),h(`h3`,null,r(j(c)(`她留下的光片`)),1)]),h(`p`,null,r(j(c)(`越亮的线索,越贴近此刻。淡去的仍然保留在这里。`)),1)]),h(`div`,Se,[(b(!0),u(o,null,S(m.value,(e,t)=>(b(),u(`section`,{key:e.kind,class:`current`,style:a({"--tone":e.color,"--order":t})},[h(`header`,Ce,[i[3]||=h(`span`,{class:`current-icon`,"aria-hidden":`true`},[h(`i`)],-1),h(`div`,null,[h(`h4`,null,r(e.label),1),h(`small`,null,r(j(c)(`{length} 枚正在留存`,{length:e.items.length})),1)]),h(`span`,we,r(String(t+1).padStart(2,`0`)),1)]),h(`div`,Te,[(b(!0),u(o,null,S(y(e),e=>(b(),u(`button`,{key:e.thread,type:`button`,class:f([`fleck star`,{faded:e.faded,core:e.core}]),"data-thread":e.thread,onClick:t=>s(`open`,e)},[i[4]||=h(`span`,{class:`fleck-light`,"aria-hidden":`true`},null,-1),h(`span`,De,[h(`small`,null,r(v(e)),1),h(`b`,null,r(e.content),1)]),i[5]||=h(`span`,{class:`fleck-arrow`,"aria-hidden":`true`},`↗`,-1)],10,Ee))),128)),e.items.length>Q?(b(),u(`button`,{key:0,type:`button`,class:`fleck-more`,"aria-expanded":!!p.value[e.kind],onClick:t=>C(e.kind)},[x(r(p.value[e.kind]?j(c)(`收起线索`):j(c)(`再看 {v} 枚光片`,{v:e.items.length-Q}))+` `,1),h(`span`,ke,r(p.value[e.kind]?`↑`:`↓`),1)],8,Oe)):g(``,!0)])],4))),128))])],8,me))}}),[[`__scopeId`,`data-v-f3560fca`]]),je={class:`notes-wall`},Me={class:`wall-head`},Ne={class:`eyebrow`},Pe={class:`muted`},Fe=[`placeholder`,`aria-label`],Ie={key:0,class:`wall`},Le=[`data-kind`],Re={key:0,class:`stamp`},ze={key:1,class:`outreach`},Be=[`onClick`],Ve=[`onClick`],He=[`onClick`],Ue=w(T({__name:`NotesWall`,props:{thoughts:{},kinds:{},query:{}},emits:[`changed`,`search`],setup(e,{emit:t}){let n=e,a=t,l=d(n.query);async function _(e,t){try{await F.thought(e.id,t),a(`changed`)}catch(e){A(e.message,!0)}}async function v(e){if(await i(c(`删除这张便签?有后续修正的便签只能隐藏。`),{title:c(`删除便签`),confirmText:c(`删除`),danger:!0}))try{await F.removeThought(e.id),a(`changed`)}catch(e){A(e.message,!0)}}return(t,n)=>(b(),u(`section`,je,[h(`div`,Me,[h(`div`,null,[h(`span`,Ne,r(j(c)(`THOUGHTS / 留给自己的话`)),1),h(`h2`,null,r(j(c)(`有些念头,先放在心上。`)),1),h(`p`,Pe,r(j(c)(`有些是聊天之后的理解,有些是还没想好要告诉谁的念头。可以分享,也可以先留给自己。`)),1)]),h(`form`,{class:`wall-search`,role:`search`,onSubmit:n[2]||=p(e=>a(`search`,l.value),[`prevent`])},[y(h(`input`,{id:`noteSearch`,"onUpdate:modelValue":n[0]||=e=>l.value=e,placeholder:j(c)(`搜索某件事、某个人`),"aria-label":j(c)(`搜索便签`),onChange:n[1]||=e=>a(`search`,l.value)},null,40,Fe),[[s,l.value]])],32)]),e.thoughts.length?(b(),u(`div`,Ie,[(b(!0),u(o,null,S(e.thoughts,t=>(b(),u(`article`,{key:t.id,class:f([`note-card`,{muted:t.hidden,resolved:t.status===`resolved`}]),"data-kind":t.kind},[h(`header`,null,[h(`b`,null,[x(r(e.kinds[t.kind]||t.kind),1),t.parent_id?(b(),u(o,{key:0},[x(r(j(c)(`· 修正了以前的想法`)),1)],64)):g(``,!0)]),h(`time`,null,r(j(L)(t.created)),1)]),h(`p`,null,r(t.content),1),t.status===`resolved`?(b(),u(`p`,Re,r(t.resolution?j(c)(`放下了:{resolution}`,{resolution:t.resolution}):j(c)(`放下了`)),1)):g(``,!0),t.outreach?(b(),u(`p`,ze,[x(r(j(c)(`想主动说:{outreach} · {v}`,{outreach:t.outreach,v:j(P)[t.outreach_status]||t.outreach_status}))+` `,1),t.outreach_reason?(b(),u(o,{key:0},[n[3]||=h(`br`,null,null,-1),x(r(j(c)(`因为:{outreach_reason}`,{outreach_reason:t.outreach_reason})),1)],64)):g(``,!0),t.outreach_wait_reason?(b(),u(o,{key:1},[n[4]||=h(`br`,null,null,-1),x(r(j(c)(`{outreach_wait_reason};稍后重新决定。`,{outreach_wait_reason:t.outreach_wait_reason})),1)],64)):g(``,!0)])):g(``,!0),h(`footer`,null,[h(`button`,{class:`small`,onClick:e=>_(t,{status:t.status===`open`?`resolved`:`open`})},r(t.status===`open`?j(c)(`放下这件事`):j(c)(`重新关注`)),9,Be),h(`button`,{class:`small`,onClick:e=>_(t,{hidden:!t.hidden})},r(t.hidden?j(c)(`恢复`):j(c)(`不再参与 TA 的思考`)),9,Ve),h(`button`,{class:`text-button`,onClick:e=>v(t)},r(j(c)(`删除`)),9,He)])],10,Le))),128))])):(b(),m(O,{key:1,title:j(c)(`这面墙还空着`),text:j(c)(`独处时有了新的理解、放不下的事或久别想起的人,TA 会写一张便签贴在这里。`)},null,8,[`title`,`text`]))]))}}),[[`__scopeId`,`data-v-a56a9d02`]]),We={class:`page heart`},Ge={class:`heart-bar`},Ke={key:0,class:`muted`},qe={key:1,class:`thread-groups`},Je=[`data-thread-kind`],Ye=[`data-thread`,`onClick`],Xe=[`data-tone`],Ze={class:`thread-text`},Qe=[`aria-expanded`,`onClick`],$e={"aria-hidden":`true`},et={key:3,class:`grid-2 set-aside`},tt={class:`card fold`},nt={class:`eyebrow`},rt={key:0,class:`list`},it={class:`chip`,"data-tone":`quiet`},at={class:`grow`},ot={class:`faint`},st={key:1,class:`muted`},ct={class:`card fold`},lt={class:`eyebrow`},ut={key:0,class:`list`},dt={class:`chip`,"data-tone":`danger`},ft={class:`grow`},pt={key:0,class:`faint`},mt={class:`faint`},ht={key:1,class:`muted`},gt={key:0,class:`thread-sheet stack`},_t={class:`thread-content`},vt={class:`row`},yt={class:`chip`},bt={class:`chip`,"data-tone":`quiet`},xt={class:`stack tight`},St={class:`kv`},Ct={class:`sheet-title`},wt={class:`versions`},Tt={class:`faint`},$=4,Et=w(T({__name:`HeartPage`,setup(e){let i=se(`(max-width: 760px)`),s=t(_(`/mind/self`)||null),p=t(_(`/mind/mood`)||null),y=t(_(`/mind/thoughts?q=`)||[]),C=d(``),w=d(E.sub===`notes`?`notes`:E.sub===`list`||i.value?`list`:`stars`),T=d(null),D=d([]),P=l(()=>(s.value?.threads||[]).filter(e=>e.status!==`closed`)),z=d({}),B=l(()=>(s.value?.threads||[]).filter(e=>e.status===`closed`)),V=l(()=>{let e=s.value?.kinds||{};return Object.entries(e).map(([e,t])=>({kind:e,label:t,threads:P.value.filter(t=>t.kind===e)})).filter(e=>e.threads.length)});function H(e){return e.core?c(`TA 最核心的部分`):e.faded?c(`在远方:很久没被触及`):e.fading?c(`慢慢淡出`):ne[e.status]||e.status}function U(e){return z.value[e.kind]?e.threads:e.threads.slice(0,$)}function W(e){z.value={...z.value,[e]:!z.value[e]}}let G=0;async function K(){let e=++G,[t,n,r]=await Promise.all([F.self(),F.mood(),F.notes(C.value)]);e===G&&(s.value=t,p.value=n,y.value=r,T.value&&=t.threads.find(e=>e.thread===T.value.thread)||null)}async function q(e){let t=++G;C.value=e;let n=await F.notes(e);t===G&&(y.value=n)}async function J(e){T.value=e,D.value=[],D.value=await F.thread(e.thread)}async function Y(e){let t=await n(c(`撤销「{content}」?撤销后 TA 不会再这样认为,之后的独处也不会把它写回来。可以写下原因(可不填):`,{content:e.content}),{title:c(`撤销这条线索`),confirmText:c(`撤销`),danger:!0,placeholder:c(`原因`)});if(t!==null)try{await F.revoke(`self`,e.thread,t),A(c(`已撤销`)),T.value=null,await K()}catch(e){A(e.message,!0)}}return v(w,e=>{E.page===`heart`&&ee(e===`stars`?``:e)}),v(()=>[E.page,E.sub],([e,t])=>{e===`heart`&&(t===`notes`||t===`list`?w.value=t:!t&&w.value===`notes`&&(w.value=`stars`))}),re(`heart`,K),(e,t)=>(b(),u(`div`,We,[p.value?(b(),m(pe,{key:0,moods:p.value.moods,baseline:p.value.affect.baseline,"time-zone":p.value.clock.timeZone},null,8,[`moods`,`baseline`,`time-zone`])):g(``,!0),h(`div`,Ge,[k(ae,{modelValue:w.value,"onUpdate:modelValue":t[0]||=e=>w.value=e,label:j(c)(`内心的分区`),items:[{key:`stars`,label:j(c)(`心灵星图`),count:P.value.length},{key:`list`,label:j(c)(`线索清单`)},{key:`notes`,label:j(c)(`放在心上`),count:y.value.length}]},null,8,[`modelValue`,`label`,`items`]),w.value===`notes`?g(``,!0):(b(),u(`p`,Ke,r(j(c)(`她从真实经历里慢慢认识自己。点开一条线索,可以看到它如何变化。`)),1))]),s.value?(b(),u(o,{key:1},[w.value===`stars`?(b(),u(o,{key:0},[P.value.length?(b(),m(Ae,{key:0,threads:P.value,kinds:s.value.kinds,activity:j(ie).data?.activity?.kind,onOpen:J},null,8,[`threads`,`kinds`,`activity`])):(b(),m(O,{key:1,title:j(c)(`TA 还没有长出关于自己的东西`),text:j(c)(`聊得多了、独处过、写过日记,TA 会慢慢发现自己喜欢什么、怎么看事情、想做什么。天性只是种子。`)},null,8,[`title`,`text`]))],64)):w.value===`list`?(b(),u(`section`,qe,[(b(!0),u(o,null,S(V.value,e=>(b(),u(`div`,{key:e.kind,class:`card thread-group`,"data-thread-kind":e.kind},[h(`h2`,null,[h(`i`,{style:a({background:j(N)[e.kind]})},null,4),x(r(e.label)+` · `+r(e.threads.length),1)]),(b(!0),u(o,null,S(U(e),e=>(b(),u(`button`,{key:e.thread,class:f([`thread`,{faded:e.faded,core:e.core}]),"data-thread":e.thread,onClick:t=>J(e)},[h(`span`,{class:`chip`,"data-tone":e.faded||e.fading?`quiet`:void 0},r(H(e)),9,Xe),h(`span`,Ze,r(e.content),1),h(`small`,null,[x(r(j(c)(`强度 {v}`,{v:j(M)(e.strength)})),1),e.salience===null?g(``,!0):(b(),u(o,{key:0},[x(r(j(c)(`· 此刻的分量 {v}`,{v:j(M)(e.salience)})),1)],64))])],10,Ye))),128)),e.threads.length>$?(b(),u(`button`,{key:0,type:`button`,class:`thread-more`,"aria-expanded":!!z.value[e.kind],onClick:t=>W(e.kind)},[x(r(z.value[e.kind]?j(c)(`收起线索`):j(c)(`再看 {v} 条线索`,{v:e.threads.length-$}))+` `,1),h(`span`,$e,r(z.value[e.kind]?`↑`:`↓`),1)],8,Qe)):g(``,!0)],8,Je))),128)),V.value.length?g(``,!0):(b(),m(O,{key:0,title:j(c)(`TA 还没有长出关于自己的东西`),text:j(c)(`天性只是种子,其余的都从经历里来。`)},null,8,[`title`,`text`]))])):(b(),m(Ue,{key:2,thoughts:y.value,kinds:p.value?.kinds?.thoughts||{},query:C.value,onChanged:K,onSearch:q},null,8,[`thoughts`,`kinds`,`query`])),w.value===`notes`?g(``,!0):(b(),u(`div`,et,[h(`details`,tt,[h(`summary`,null,[h(`span`,nt,r(j(c)(`放下`)),1),h(`b`,null,r(j(c)(`TA 自己放下的 · {length}`,{length:B.value.length})),1)]),B.value.length?(b(),u(`ul`,rt,[(b(!0),u(o,null,S(B.value,e=>(b(),u(`li`,{key:e.thread,class:`list-row`},[h(`span`,it,r(j(c)(`放下`)),1),h(`span`,at,r(e.content),1),h(`time`,ot,r(j(L)(e.created)),1)]))),128))])):(b(),u(`p`,st,r(j(c)(`没有。`)),1))]),h(`details`,ct,[h(`summary`,null,[h(`span`,lt,r(j(c)(`撤销`)),1),h(`b`,null,r(j(c)(`你撤销过的 · {length}`,{length:s.value.revoked.length})),1)]),s.value.revoked.length?(b(),u(`ul`,ut,[(b(!0),u(o,null,S(s.value.revoked,e=>(b(),u(`li`,{key:e.id,class:`list-row`},[h(`span`,dt,r(j(c)(`撤销`)),1),h(`span`,ft,[x(r(e.content),1),e.reason?(b(),u(`small`,pt,` · `+r(e.reason),1)):g(``,!0)]),h(`time`,mt,r(j(L)(e.created)),1)]))),128))])):(b(),u(`p`,ht,r(j(c)(`没有。`)),1))])]))],64)):g(``,!0),k(oe,{open:!!T.value,title:T.value?s.value?.kinds?.[T.value.kind]||T.value.kind:``,eyebrow:j(c)(`一颗星`),onClose:t[2]||=e=>T.value=null},{default:te(()=>[T.value?(b(),u(`div`,gt,[h(`p`,_t,r(T.value.content),1),h(`div`,vt,[h(`span`,yt,r(H(T.value)),1),h(`span`,bt,r(j(I)[T.value.origin]||T.value.origin),1)]),h(`div`,xt,[k(R,{label:j(c)(`强度`),value:T.value.strength},null,8,[`label`,`value`]),T.value.salience===null?g(``,!0):(b(),m(R,{key:0,label:j(c)(`此刻的分量`),value:T.value.salience},null,8,[`label`,`value`]))]),h(`dl`,St,[h(`dt`,null,r(j(c)(`经历的天数`)),1),h(`dd`,null,r(j(c)(`{length} 天`,{length:T.value.days.length})),1),h(`dt`,null,r(j(c)(`来源`)),1),h(`dd`,null,r(j(c)(`{length} 处`,{length:T.value.sources.length})),1),h(`dt`,null,r(j(c)(`最近一次被触及`)),1),h(`dd`,null,r(j(L)(T.value.created)),1)]),h(`section`,null,[h(`h3`,Ct,r(j(c)(`怎么变成现在这样的 · {versions} 个版本`,{versions:T.value.versions})),1),h(`ol`,wt,[(b(!0),u(o,null,S(D.value,e=>(b(),u(`li`,{key:e.id},[h(`time`,null,r(j(L)(e.created)),1),h(`b`,null,r(j(M)(e.strength)),1),h(`span`,null,r(e.content),1)]))),128))])]),h(`button`,{class:`danger`,"data-revoke-thread":``,onClick:t[1]||=e=>Y(T.value)},r(j(c)(`撤销这条线索`)),1),h(`p`,Tt,r(j(c)(`撤销后 TA 不会再这样认为;这只是拿走一段理解,不会改写发生过的事。`)),1)])):g(``,!0)]),_:1},8,[`open`,`title`,`eyebrow`])]))}}),[[`__scopeId`,`data-v-f49fd383`]]);export{Et as default};
|