luckytri 1.0.3 → 1.0.4
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/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +7 -0
- package/docs/en/guide.md +3 -1
- package/docs/en/webui.md +3 -1
- package/docs/zh/CHANGELOG.md +7 -0
- package/docs/zh/guide.md +3 -1
- package/docs/zh/webui.md +3 -1
- package/package.json +4 -3
- package/public/app/assets/{ChatsPage-B83Evc79.js → ChatsPage-CshdtTzV.js} +1 -1
- package/public/app/assets/{Empty-ddEfu0w8.js → Empty-C7yzOBzy.js} +1 -1
- package/public/app/assets/{Field-mIhV5YBA.js → Field-BjFv4Rfn.js} +1 -1
- package/public/app/assets/{HeartPage-BGEn24rc.js → HeartPage-eqo6ru16.js} +1 -1
- package/public/app/assets/{LifePage-BleqPj4O.js → LifePage-CN8GE5NK.js} +1 -1
- package/public/app/assets/{MemoryPage-BM3-0_lF.js → MemoryPage-CCpMN4Wg.js} +1 -1
- package/public/app/assets/{NaturePage-CxxMnQIc.js → NaturePage-C0JqdBTR.js} +2 -2
- package/public/app/assets/{PeoplePage-DDDHESQp.css → PeoplePage-BTU0H1pM.css} +1 -1
- package/public/app/assets/PeoplePage-BXGBbO4u.js +1 -0
- package/public/app/assets/{PluginsPage-DE_-Q3ra.js → PluginsPage-Dksp5AV5.js} +1 -1
- package/public/app/assets/{SystemPage-Be1a6UDP.css → SystemPage-BenIyTvk.css} +1 -1
- package/public/app/assets/{SystemPage-BWQKEQRU.js → SystemPage-Cd7aa8eE.js} +1 -1
- package/public/app/assets/{TimePage-Dzt8t8kL.js → TimePage-Bs_zImb4.js} +1 -1
- package/public/app/assets/{index-DY4e15Kh.css → index-BHZcHUMh.css} +1 -1
- package/public/app/assets/{index-oLIXWxsn.js → index-CyRtYxP3.js} +3 -3
- package/public/app/assets/{knowledge-Dvtmpd45.js → knowledge-BJOIyuJo.js} +1 -1
- package/public/app/assets/{media-keE52cIX.js → media-Bwtx8blz.js} +1 -1
- package/public/app/assets/mind-DtTVrW6Z.js +1 -0
- package/public/app/index.html +4 -4
- package/public/guide.en.html +23 -8
- package/public/guide.html +11 -5
- package/public/tutorial.en.md +3 -1
- package/public/tutorial.md +3 -1
- package/scripts/launch.js +11 -4
- package/scripts/service.js +57 -7
- package/scripts/start-server.js +4 -1
- package/scripts/update-instance.js +201 -0
- package/server/app.js +1 -1
- package/server/core/context-builder.js +2 -2
- package/server/core/context-compactor.js +2 -2
- package/server/core/orchestrator.js +1 -0
- package/server/core/persona-manager.js +10 -0
- package/server/core/reply-delivery.js +10 -0
- package/server/core/response-validator.js +20 -0
- package/server/i18n/errors.js +25 -0
- package/server/index.js +45 -2
- package/server/mind/api.js +25 -0
- package/server/mind/bonds.js +9 -2
- package/server/mind/index.js +2 -0
- package/server/mind/initiative.js +6 -1
- package/server/mind/life/context.js +14 -3
- package/server/mind/life/growth.js +2 -1
- package/server/mind/nature.js +23 -2
- package/server/mind/relationships.js +279 -0
- package/server/mind/schema.js +2 -0
- package/server/mind/time/tasks.js +2 -1
- package/server/mind/view.js +2 -0
- package/server/runtime/installation.js +116 -0
- package/server/runtime/update-execution.js +127 -0
- package/server/runtime/update-manager.js +140 -0
- package/server/startup-lock.js +12 -12
- package/server/storage/archive.js +10 -2
- package/server/storage/backup-lock.js +23 -5
- package/server/storage/data-transfer.js +2 -1
- package/server/studio/display-names.js +1 -0
- package/server/studio/settings.js +1 -0
- package/server/studio/updates.js +14 -3
- package/public/app/assets/PeoplePage-D12cvuy7.js +0 -1
- package/public/app/assets/mind-DYRfFMwh.js +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, renameSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
readActive,
|
|
4
|
+
writeActive,
|
|
5
|
+
installedRelease,
|
|
6
|
+
updatePath,
|
|
7
|
+
pruneInstallations,
|
|
8
|
+
} from "./installation.js";
|
|
9
|
+
import {
|
|
10
|
+
readUpdateJob,
|
|
11
|
+
writeUpdateJob,
|
|
12
|
+
UPDATE_FAILED,
|
|
13
|
+
UPDATE_RECOVERY_FAILED,
|
|
14
|
+
} from "./update-manager.js";
|
|
15
|
+
import { backupDatabase } from "../../scripts/backup.js";
|
|
16
|
+
import { verifyArchive } from "../storage/archive.js";
|
|
17
|
+
|
|
18
|
+
// Executed outside the server so a large verified snapshot does not stall the
|
|
19
|
+
// WebUI. Dependencies are explicit for failure, restart and rollback tests.
|
|
20
|
+
export async function performUpdate(
|
|
21
|
+
paths,
|
|
22
|
+
id,
|
|
23
|
+
{
|
|
24
|
+
install,
|
|
25
|
+
check,
|
|
26
|
+
stop,
|
|
27
|
+
claim,
|
|
28
|
+
start,
|
|
29
|
+
health,
|
|
30
|
+
release,
|
|
31
|
+
snapshot = backupDatabase,
|
|
32
|
+
} = {},
|
|
33
|
+
) {
|
|
34
|
+
let job = readUpdateJob(paths.home, id),
|
|
35
|
+
stopped = false,
|
|
36
|
+
changed = false,
|
|
37
|
+
child = null,
|
|
38
|
+
claimed = false,
|
|
39
|
+
backup = null;
|
|
40
|
+
const previous = readActive(paths.home);
|
|
41
|
+
const save = (status, fields = {}) => {
|
|
42
|
+
job = writeUpdateJob(paths.home, { ...job, ...fields, status });
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
if (job.status !== "queued") throw Error("更新任务已经处理");
|
|
46
|
+
save("downloading");
|
|
47
|
+
const slot = updatePath(paths.home, id);
|
|
48
|
+
mkdirSync(slot, { recursive: true, mode: 0o700 });
|
|
49
|
+
await install(slot, job.version);
|
|
50
|
+
const candidate = installedRelease(paths.home, id, job.version);
|
|
51
|
+
save("checking");
|
|
52
|
+
await check(candidate, job.version);
|
|
53
|
+
save("stopping");
|
|
54
|
+
await stop(job.parent);
|
|
55
|
+
stopped = true;
|
|
56
|
+
claimed = !!claim();
|
|
57
|
+
if (!claimed) throw Error("另一次启动正在进行");
|
|
58
|
+
save("backup");
|
|
59
|
+
backup = snapshot(paths.database, paths.backups);
|
|
60
|
+
verifyArchive(backup);
|
|
61
|
+
save("restarting");
|
|
62
|
+
writeActive(paths.home, { id, version: job.version });
|
|
63
|
+
changed = true;
|
|
64
|
+
child = await start(candidate);
|
|
65
|
+
save("verifying");
|
|
66
|
+
await health(child, job.version);
|
|
67
|
+
save("done", { finished: Date.now() });
|
|
68
|
+
try {
|
|
69
|
+
pruneInstallations(paths.home, [id, previous?.id]);
|
|
70
|
+
} catch {
|
|
71
|
+
/* A locked obsolete program can be removed after the next update. */
|
|
72
|
+
}
|
|
73
|
+
return job;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const failedStage = job.status;
|
|
76
|
+
job = { ...job, failedStage };
|
|
77
|
+
console.error(
|
|
78
|
+
"更新步骤未完成:",
|
|
79
|
+
failedStage,
|
|
80
|
+
error.code || error.cause?.code || "STEP_FAILED",
|
|
81
|
+
);
|
|
82
|
+
if (!stopped) {
|
|
83
|
+
save("error", { error: UPDATE_FAILED, finished: Date.now() });
|
|
84
|
+
return job;
|
|
85
|
+
}
|
|
86
|
+
if (!claimed) {
|
|
87
|
+
save("error", { error: UPDATE_RECOVERY_FAILED, finished: Date.now() });
|
|
88
|
+
return job;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
save("restoring");
|
|
92
|
+
if (child) await stop(child);
|
|
93
|
+
// The child may have claimed startup.json. Reacquire only after its exit.
|
|
94
|
+
if (child) {
|
|
95
|
+
release();
|
|
96
|
+
if (!claim()) throw Error("启动锁尚未释放");
|
|
97
|
+
}
|
|
98
|
+
if (changed) {
|
|
99
|
+
verifyArchive(backup);
|
|
100
|
+
const temporary = `${paths.database}.${id}.restore`;
|
|
101
|
+
copyFileSync(backup, temporary);
|
|
102
|
+
verifyArchive(temporary);
|
|
103
|
+
// Retain the attempted database and every sidecar; no life is erased.
|
|
104
|
+
for (const suffix of ["", "-wal", "-shm"])
|
|
105
|
+
if (existsSync(paths.database + suffix))
|
|
106
|
+
renameSync(
|
|
107
|
+
paths.database + suffix,
|
|
108
|
+
`${paths.database}.update-failed-${id}${suffix}`,
|
|
109
|
+
);
|
|
110
|
+
renameSync(temporary, paths.database);
|
|
111
|
+
writeActive(paths.home, previous);
|
|
112
|
+
}
|
|
113
|
+
const restored = await start(paths.package);
|
|
114
|
+
await health(restored, job.previous);
|
|
115
|
+
save("error", {
|
|
116
|
+
error: UPDATE_FAILED,
|
|
117
|
+
restored: true,
|
|
118
|
+
finished: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
save("error", { error: UPDATE_RECOVERY_FAILED, finished: Date.now() });
|
|
122
|
+
}
|
|
123
|
+
return job;
|
|
124
|
+
} finally {
|
|
125
|
+
if (claimed) release();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
atomicJson,
|
|
6
|
+
UPDATE_ID,
|
|
7
|
+
updatePath,
|
|
8
|
+
updateRoot,
|
|
9
|
+
} from "./installation.js";
|
|
10
|
+
export const UPDATE_FAILED =
|
|
11
|
+
"更新未完成,原程序和数据已保留,请查看运行日志后重试。";
|
|
12
|
+
export const UPDATE_RECOVERY_FAILED =
|
|
13
|
+
"自动恢复未完成,更新前的校验备份已保留,请查看运行日志并手动恢复。";
|
|
14
|
+
const ACTIVE = new Set([
|
|
15
|
+
"queued",
|
|
16
|
+
"downloading",
|
|
17
|
+
"checking",
|
|
18
|
+
"stopping",
|
|
19
|
+
"backup",
|
|
20
|
+
"restarting",
|
|
21
|
+
"verifying",
|
|
22
|
+
"restoring",
|
|
23
|
+
]);
|
|
24
|
+
export const processAlive = (pid) => {
|
|
25
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return e.code !== "ESRCH";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export function readUpdateJob(home, id) {
|
|
34
|
+
updatePath(home, id);
|
|
35
|
+
const job = JSON.parse(
|
|
36
|
+
readFileSync(join(updateRoot(home), "job.json"), "utf8"),
|
|
37
|
+
);
|
|
38
|
+
if (job.id !== id || !UPDATE_ID.test(job.id)) throw Error("更新任务无效");
|
|
39
|
+
return job;
|
|
40
|
+
}
|
|
41
|
+
export const writeUpdateJob = (home, job) => {
|
|
42
|
+
updatePath(home, job.id);
|
|
43
|
+
atomicJson(join(updateRoot(home), "job.json"), job);
|
|
44
|
+
return job;
|
|
45
|
+
};
|
|
46
|
+
export class InstanceUpdater {
|
|
47
|
+
constructor({
|
|
48
|
+
paths,
|
|
49
|
+
checker,
|
|
50
|
+
launch,
|
|
51
|
+
occupied = () => false,
|
|
52
|
+
now = Date.now,
|
|
53
|
+
}) {
|
|
54
|
+
this.paths = paths;
|
|
55
|
+
this.checker = checker;
|
|
56
|
+
this.launch = launch;
|
|
57
|
+
this.occupied = occupied;
|
|
58
|
+
this.now = now;
|
|
59
|
+
this.starting = false;
|
|
60
|
+
}
|
|
61
|
+
status() {
|
|
62
|
+
let job;
|
|
63
|
+
try {
|
|
64
|
+
job = JSON.parse(
|
|
65
|
+
readFileSync(join(updateRoot(this.paths.home), "job.json"), "utf8"),
|
|
66
|
+
);
|
|
67
|
+
if (!UPDATE_ID.test(job.id)) return null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
ACTIVE.has(job.status) &&
|
|
73
|
+
job.workerPid &&
|
|
74
|
+
!processAlive(job.workerPid)
|
|
75
|
+
) {
|
|
76
|
+
job = writeUpdateJob(this.paths.home, {
|
|
77
|
+
...job,
|
|
78
|
+
status: "error",
|
|
79
|
+
error: ["queued", "downloading", "checking"].includes(job.status)
|
|
80
|
+
? UPDATE_FAILED
|
|
81
|
+
: UPDATE_RECOVERY_FAILED,
|
|
82
|
+
finished: this.now(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Public progress is deliberately free of filesystem paths and process data.
|
|
86
|
+
return {
|
|
87
|
+
id: job.id,
|
|
88
|
+
version: job.version,
|
|
89
|
+
previous: job.previous,
|
|
90
|
+
status: job.status,
|
|
91
|
+
started: job.started,
|
|
92
|
+
finished: job.finished,
|
|
93
|
+
error: job.error || "",
|
|
94
|
+
restored: !!job.restored,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
get busy() {
|
|
98
|
+
return this.starting || ACTIVE.has(this.status()?.status);
|
|
99
|
+
}
|
|
100
|
+
async install(version) {
|
|
101
|
+
if (this.busy) return this.status();
|
|
102
|
+
if (this.occupied()) throw Error("数据处理或备份正在进行,请稍后再试");
|
|
103
|
+
if (!this.launch || this.paths.database === ":memory:")
|
|
104
|
+
throw Error("当前实例不支持自动更新");
|
|
105
|
+
this.starting = true;
|
|
106
|
+
try {
|
|
107
|
+
const info = await this.checker.check();
|
|
108
|
+
if (info.status !== "available" || version !== info.latest)
|
|
109
|
+
throw Error("请重新检查并选择可用的新版本");
|
|
110
|
+
updatePath(this.paths.home, randomUUID());
|
|
111
|
+
mkdirSync(updateRoot(this.paths.home), { recursive: true, mode: 0o700 });
|
|
112
|
+
const job = {
|
|
113
|
+
id: randomUUID(),
|
|
114
|
+
version,
|
|
115
|
+
previous: info.current,
|
|
116
|
+
status: "queued",
|
|
117
|
+
started: this.now(),
|
|
118
|
+
parent: process.pid,
|
|
119
|
+
};
|
|
120
|
+
writeUpdateJob(this.paths.home, job);
|
|
121
|
+
try {
|
|
122
|
+
const pid = await this.launch(job.id);
|
|
123
|
+
writeUpdateJob(this.paths.home, {
|
|
124
|
+
...readUpdateJob(this.paths.home, job.id),
|
|
125
|
+
workerPid: pid,
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
writeUpdateJob(this.paths.home, {
|
|
129
|
+
...job,
|
|
130
|
+
status: "error",
|
|
131
|
+
error: UPDATE_FAILED,
|
|
132
|
+
});
|
|
133
|
+
throw Error(UPDATE_FAILED);
|
|
134
|
+
}
|
|
135
|
+
return this.status();
|
|
136
|
+
} finally {
|
|
137
|
+
this.starting = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
package/server/startup-lock.js
CHANGED
|
@@ -2,7 +2,8 @@ import {
|
|
|
2
2
|
mkdirSync,
|
|
3
3
|
readFileSync,
|
|
4
4
|
rmdirSync,
|
|
5
|
-
|
|
5
|
+
unlinkSync,
|
|
6
|
+
existsSync,
|
|
6
7
|
statSync,
|
|
7
8
|
writeFileSync,
|
|
8
9
|
} from "node:fs";
|
|
@@ -11,6 +12,14 @@ import { runtimePaths } from "./paths.js";
|
|
|
11
12
|
|
|
12
13
|
const file = () => join(runtimePaths().data, "startup.json");
|
|
13
14
|
const lock = () => join(runtimePaths().data, "startup.lock");
|
|
15
|
+
function alive(pid) {
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
|
|
15
24
|
function read() {
|
|
16
25
|
try {
|
|
@@ -22,15 +31,6 @@ function read() {
|
|
|
22
31
|
}
|
|
23
32
|
}
|
|
24
33
|
|
|
25
|
-
function alive(pid) {
|
|
26
|
-
try {
|
|
27
|
-
process.kill(pid, 0);
|
|
28
|
-
return true;
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
34
|
function write(role) {
|
|
35
35
|
mkdirSync(runtimePaths().data, { recursive: true, mode: 0o700 });
|
|
36
36
|
writeFileSync(
|
|
@@ -61,7 +61,7 @@ function claim(role) {
|
|
|
61
61
|
if (busy()) return false;
|
|
62
62
|
// Reclaim a dead owner's claim; the normal concurrent-launch path above
|
|
63
63
|
// never removes a directory just acquired by another process.
|
|
64
|
-
|
|
64
|
+
if (existsSync(file())) unlinkSync(file());
|
|
65
65
|
try {
|
|
66
66
|
rmdirSync(lock());
|
|
67
67
|
} catch (error) {
|
|
@@ -108,7 +108,7 @@ export function starting() {
|
|
|
108
108
|
export function clearStarting() {
|
|
109
109
|
const info = read();
|
|
110
110
|
if (info?.pid === process.pid) {
|
|
111
|
-
|
|
111
|
+
if (existsSync(file())) unlinkSync(file());
|
|
112
112
|
try {
|
|
113
113
|
rmdirSync(lock());
|
|
114
114
|
} catch (error) {
|
|
@@ -142,10 +142,18 @@ export function writeArchiveManifest(
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
export function verifyArchive(file) {
|
|
145
|
-
const inspected = inspectDatabase(file);
|
|
146
145
|
const sidecar = `${file}.json`;
|
|
147
|
-
if (!existsSync(sidecar))
|
|
146
|
+
if (!existsSync(sidecar))
|
|
147
|
+
return { ...inspectDatabase(file), manifest: false }; // Readable legacy backups remain usable.
|
|
148
148
|
const manifest = JSON.parse(readFileSync(sidecar, "utf8"));
|
|
149
|
+
// Reject changed bytes before SQLite attempts to parse a corrupted schema.
|
|
150
|
+
if (
|
|
151
|
+
manifest.format !== 1 ||
|
|
152
|
+
manifest.app !== "luckytri" ||
|
|
153
|
+
manifest.sha256 !== fileHash(file)
|
|
154
|
+
)
|
|
155
|
+
throw Error("备份校验清单不匹配");
|
|
156
|
+
const inspected = inspectDatabase(file);
|
|
149
157
|
if (
|
|
150
158
|
manifest.format !== 1 ||
|
|
151
159
|
manifest.app !== "luckytri" ||
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
mkdirSync,
|
|
5
5
|
openSync,
|
|
6
6
|
readFileSync,
|
|
7
|
-
|
|
7
|
+
unlinkSync,
|
|
8
8
|
statSync,
|
|
9
9
|
writeFileSync,
|
|
10
10
|
} from "node:fs";
|
|
@@ -12,7 +12,6 @@ import { join, resolve } from "node:path";
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
|
|
14
14
|
const BUSY = "备份目录正在被使用,请稍后再试";
|
|
15
|
-
|
|
16
15
|
function alive(pid) {
|
|
17
16
|
if (!Number.isInteger(pid) || pid < 1) return false;
|
|
18
17
|
try {
|
|
@@ -28,8 +27,27 @@ export function withBackupLock(directory, run) {
|
|
|
28
27
|
mkdirSync(root, { recursive: true });
|
|
29
28
|
const file = join(root, ".backup-operation.lock");
|
|
30
29
|
const token = randomUUID();
|
|
30
|
+
const reclaim = () => {
|
|
31
|
+
if (!existsSync(file)) return;
|
|
32
|
+
const stat = statSync(file);
|
|
33
|
+
let owner;
|
|
34
|
+
try {
|
|
35
|
+
owner = JSON.parse(readFileSync(file, "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
/* A fresh incomplete claim belongs to its writer. */
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
(owner && !alive(owner.pid)) ||
|
|
41
|
+
(!owner && Date.now() - stat.mtimeMs > 6 * 3600000)
|
|
42
|
+
) {
|
|
43
|
+
const current = statSync(file);
|
|
44
|
+
if (current.ino === stat.ino && current.mtimeMs === stat.mtimeMs)
|
|
45
|
+
unlinkSync(file);
|
|
46
|
+
} else throw Error(BUSY);
|
|
47
|
+
};
|
|
31
48
|
let acquired = false;
|
|
32
49
|
for (let attempt = 0; attempt < 2 && !acquired; attempt++) {
|
|
50
|
+
reclaim();
|
|
33
51
|
try {
|
|
34
52
|
const fd = openSync(file, "wx");
|
|
35
53
|
let written = false;
|
|
@@ -38,7 +56,7 @@ export function withBackupLock(directory, run) {
|
|
|
38
56
|
written = true;
|
|
39
57
|
} finally {
|
|
40
58
|
closeSync(fd);
|
|
41
|
-
if (!written)
|
|
59
|
+
if (!written && existsSync(file)) unlinkSync(file);
|
|
42
60
|
}
|
|
43
61
|
acquired = true;
|
|
44
62
|
} catch (error) {
|
|
@@ -56,7 +74,7 @@ export function withBackupLock(directory, run) {
|
|
|
56
74
|
) {
|
|
57
75
|
const current = statSync(file);
|
|
58
76
|
if (current.ino === stat.ino && current.mtimeMs === stat.mtimeMs)
|
|
59
|
-
|
|
77
|
+
unlinkSync(file);
|
|
60
78
|
} else throw Error(BUSY);
|
|
61
79
|
}
|
|
62
80
|
}
|
|
@@ -67,7 +85,7 @@ export function withBackupLock(directory, run) {
|
|
|
67
85
|
if (existsSync(file)) {
|
|
68
86
|
try {
|
|
69
87
|
const owner = JSON.parse(readFileSync(file, "utf8"));
|
|
70
|
-
if (owner.token === token)
|
|
88
|
+
if (owner.token === token) unlinkSync(file);
|
|
71
89
|
} catch {
|
|
72
90
|
// Leave a lock changed by another process alone.
|
|
73
91
|
}
|
|
@@ -77,6 +77,7 @@ export function createDataTransfer({
|
|
|
77
77
|
run = work,
|
|
78
78
|
now = Date.now,
|
|
79
79
|
maxUploadBytes,
|
|
80
|
+
occupied = () => false,
|
|
80
81
|
} = {}) {
|
|
81
82
|
const root = resolve(directory);
|
|
82
83
|
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
@@ -105,7 +106,7 @@ export function createDataTransfer({
|
|
|
105
106
|
return rest;
|
|
106
107
|
};
|
|
107
108
|
const check = () => {
|
|
108
|
-
if (active || backup?.running || cleanup?.running)
|
|
109
|
+
if (active || backup?.running || cleanup?.running || occupied())
|
|
109
110
|
throw Error("数据处理或备份正在进行,请稍后再试");
|
|
110
111
|
if (source === ":memory:") throw Error("当前实例不支持文件导入导出");
|
|
111
112
|
};
|
|
@@ -32,6 +32,7 @@ export function mountStudio(app, store, runtime, chat) {
|
|
|
32
32
|
if (!runtime.cleanup)
|
|
33
33
|
return res.status(501).json({ error: "备份整理不可用" });
|
|
34
34
|
try {
|
|
35
|
+
if (runtime.updater?.busy) throw Error("版本更新正在进行,请稍后再试");
|
|
35
36
|
res.json(await runtime.cleanup.manual(req.body.items));
|
|
36
37
|
} catch (error) {
|
|
37
38
|
res.status(409).json({ error: error.message });
|
package/server/studio/updates.js
CHANGED
|
@@ -125,9 +125,20 @@ export class UpdateChecker {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export function mountUpdates(app, checker) {
|
|
129
|
-
|
|
128
|
+
export function mountUpdates(app, checker, updater) {
|
|
129
|
+
const view = (info) => ({ ...info, operation: updater?.status() || null });
|
|
130
|
+
app.get("/api/system/update", (_req, res) =>
|
|
131
|
+
res.json(view(checker.status())),
|
|
132
|
+
);
|
|
130
133
|
app.post("/api/system/update/check", async (_req, res) =>
|
|
131
|
-
res.json(await checker.check()),
|
|
134
|
+
res.json(view(await checker.check())),
|
|
132
135
|
);
|
|
136
|
+
app.post("/api/system/update/install", async (req, res) => {
|
|
137
|
+
try {
|
|
138
|
+
if (!updater) throw Error("当前实例不支持自动更新");
|
|
139
|
+
res.json(await updater.install(req.body.version));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
res.status(400).json({ error: error.message });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
133
144
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{$ as e,Ct as t,D as n,E as r,Et as i,I as a,J as o,P as s,Q as c,St as l,Tt as u,V as d,W as f,X as p,Y as m,Z as h,_ as g,dt as _,et as v,gt as y,j as b,n as x,nt as S,o as C,q as w,s as T,t as E,ut as D,v as O,vt as k,wt as A,xt as j,yt as M}from"./Empty-ddEfu0w8.js";import{_ as N,a as ee,h as P,l as F,n as I,o as L,r as R,s as z,t as B,u as V}from"./mind-DYRfFMwh.js";import{a as H,b as U,c as te,f as W,h as G,l as K,n as q,o as J,p as Y,r as X,v as Z,x as Q}from"./index-oLIXWxsn.js";import{l as ne}from"./knowledge-Dvtmpd45.js";var re=[`aria-label`],ie={class:`encounter-hero`},ae={class:`encounter-copy`},oe={class:`eyebrow`},se={class:`encounter-count`},ce={class:`encounter-lens`,"aria-hidden":`true`},le={class:`orb`},ue={class:`people-heading`},de={class:`encounter-grid`},fe=[`data-person`,`aria-label`,`onClick`],pe={class:`avatar-wrap`},me={class:`avatar`},he={class:`person-copy`},ge=x(S({__name:`Galaxy`,props:{people:{},activity:{}},emits:[`open`,`list`],setup(n,{emit:r}){let s=n,l=r,d=w(()=>[...s.people].sort((e,t)=>(t.familiarity??0)-(e.familiarity??0))),m=w(()=>d.value.slice(0,12)),g=w(()=>Math.max(0,d.value.length-m.value.length));return(r,s)=>(D(),h(`section`,{class:`galaxy`,"aria-label":t(a)(`相遇之间:TA 认识的人`)},[o(`div`,ie,[o(`div`,ae,[o(`span`,oe,i(t(a)(`PEOPLE / 相遇之间`)),1),o(`h2`,null,[e(i(t(a)(`每一次相遇,`)),1),s[1]||=o(`br`,null,null,-1),o(`em`,null,i(t(a)(`都留下一点温度。`)),1)]),o(`p`,null,i(t(a)(`越熟悉离 TA 越近。这里先放着最近亲近的 {length} 个人,其他人也仍然在名单里。`,{length:m.value.length})),1),o(`div`,se,[o(`b`,null,i(n.people.length),1),o(`span`,null,i(t(a)(`个相遇过的人`)),1)])]),o(`div`,ce,[s[2]||=c(`<div class="lens-ring ring-a" data-v-c60042d4></div><div class="lens-ring ring-b" data-v-c60042d4></div><span class="lens-dot dot-a" data-v-c60042d4></span><span class="lens-dot dot-b" data-v-c60042d4></span><span class="lens-dot dot-c" data-v-c60042d4></span>`,5),o(`div`,le,[v(Z,{mood:t(Q),activity:n.activity||`idle`,size:100},null,8,[`mood`,`activity`])])])]),o(`div`,ue,[o(`div`,null,[s[3]||=o(`span`,{class:`eyebrow`},`CLOSER TO HER`,-1),o(`h3`,null,i(t(a)(`最近靠近的人`)),1)]),o(`span`,null,i(t(a)(`点开一张光片,看看她记得什么`)),1)]),o(`div`,de,[(D(!0),h(f,null,_(m.value,(e,n)=>(D(),h(`button`,{key:e.userId,type:`button`,class:A([`person-node`,{away:(e.awayDays??0)>=14,tense:e.tension>=.3}]),"data-person":e.userId,style:u({"--hue":t(W)(e.name||e.userId),"--order":n}),"aria-label":`${e.name}:${e.feel||t(a)(`还在慢慢认识`)}`,onClick:t=>l(`open`,e.userId)},[o(`span`,pe,[o(`span`,me,i(t(Y)(e.name)),1)]),o(`span`,he,[o(`b`,null,i(e.name),1),o(`small`,null,`QQ `+i(e.userId),1),o(`small`,null,i(e.feel||t(a)(`还在慢慢认识`)),1)]),s[5]||=o(`span`,{class:`person-arrow`,"aria-hidden":`true`},`↗`,-1),o(`span`,{class:`familiarity`,style:u({"--amount":Math.round(Math.max(0,Math.min(1,e.familiarity??0))*100)+`%`}),"aria-hidden":`true`},[...s[4]||=[o(`i`,null,null,-1)]],4)],14,fe))),128))]),g.value?(D(),h(`button`,{key:0,type:`button`,class:`more-people`,onClick:s[0]||=e=>l(`list`)},[e(i(t(a)(`在名单里看看另外 {hidden} 个人`,{hidden:g.value}))+` `,1),s[6]||=o(`span`,{"aria-hidden":`true`},`↗`,-1)])):p(``,!0)],8,re))}}),[[`__scopeId`,`data-v-c60042d4`]]),_e=[`width`,`height`],ve=[`stroke-dasharray`,`transform`],ye={x:`22`,y:`25.5`,"text-anchor":`middle`},be={key:0},xe=x(S({__name:`Ring`,props:{value:{},label:{},caption:{default:``},signed:{type:Boolean,default:!1},size:{default:64}},setup(e){let t=e,n=2*Math.PI*18,r=w(()=>{let e=Number(t.value)||0;return t.signed?Math.max(-1,Math.min(1,e)):Math.max(0,Math.min(1,e))}),a=w(()=>Math.abs(r.value)*n),s=w(()=>r.value<0);return(t,c)=>(D(),h(`figure`,{class:`ring`,style:u({width:e.size+`px`})},[(D(),h(`svg`,{width:e.size,height:e.size,viewBox:`0 0 44 44`,"aria-hidden":`true`},[c[0]||=o(`circle`,{class:`track`,cx:`22`,cy:`22`,r:`18`},null,-1),o(`circle`,{class:A([`arc`,{negative:s.value}]),cx:`22`,cy:`22`,r:`18`,"stroke-dasharray":`${a.value.toFixed(2)} ${n.toFixed(2)}`,transform:s.value?`translate(44 0) scale(-1 1) rotate(-90 22 22)`:`rotate(-90 22 22)`},null,10,ve),o(`text`,ye,i(Math.round(r.value*100)),1)],8,_e)),o(`figcaption`,null,[o(`b`,null,i(e.label),1),e.caption?(D(),h(`small`,be,i(e.caption),1)):p(``,!0)])],4))}}),[[`__scopeId`,`data-v-bbca6071`]]),Se={key:0,class:`error-text`},Ce={key:1,class:`person-sheet stack`},we={class:`who`},Te={class:`feel`},Ee={class:`qq`},De={class:`muted`},Oe={class:`rings`},ke={key:0,class:`impression`},Ae={key:1,class:`row`},je={class:`faint`},Me={class:`sheet-title`},Ne={key:0,class:`list`},Pe=[`data-tone`],Fe={class:`grow`},Ie={class:`faint`},Le={key:0,class:`chip`,"data-tone":`quiet`},Re=[`onClick`],ze={key:1,class:`muted`},Be={key:2},Ve={class:`sheet-title`},He={class:`list`},Ue=[`data-tone`],We={class:`grow`},Ge={key:0,class:`faint`},Ke={class:`faint`},qe=[`onClick`],Je={class:`sheet-title`},Ye={key:0,class:`list`},Xe={class:`faint`},Ze={key:0,class:`instant`},Qe={class:`faint`},$e=[`onClick`],et=[`onClick`],tt={key:1,class:`muted`},nt={class:`sheet-title`},rt={key:0,class:`list`},it=[`data-tone`],at={class:`grow`},ot={class:`faint`},st=[`onClick`],ct={key:1,class:`muted`},lt=x(S({__name:`PersonSheet`,props:{id:{},now:{}},emits:[`close`,`changed`],setup(e,{emit:s}){let c=e,l=s,d=j(null),b=j(``),x=w(()=>d.value?.person),S=w(()=>{let e=x.value?.firstMetAt;return e?a(`认识 {v} 天`,{v:Math.max(1,Math.round(((c.now||Date.now())-e)/864e5))}):``});async function C(){if(!c.id)return;let e=c.id;d.value?.person?.userId!==e&&(d.value=g(`/mind/people/`+encodeURIComponent(e))||null),b.value=``;try{let t=await P.person(e);c.id===e&&(d.value=t)}catch(t){if(c.id!==e)return;d.value=null,b.value=t.message}}async function T(e){if(await r(a(`撤销这次「{v}」?TA 对这个人的感觉会重新计算。`,{v:R[e.change]||e.change}),{title:a(`撤销一次关系变化`),confirmText:a(`撤销`),danger:!0}))try{await P.revoke(`bond`,e.id),O(a(`已撤销`)),await C(),l(`changed`)}catch(e){O(e.message,!0)}}async function E(e){let t=await n(a(`撤销「{content}」?TA 之后不会再这样认为,整理记忆时也不会把它写回来。可以写下原因(可不填):`,{content:e.content}),{title:a(`撤销这条记忆`),confirmText:a(`撤销`),danger:!0,placeholder:a(`原因`)});if(t!==null)try{await P.revoke(`memory`,e.id,t),O(a(`已撤销`)),await C()}catch(e){O(e.message,!0)}}async function M(e,t){try{await ne(e.id,t),await C()}catch(e){O(e.message,!0)}}async function H(e){if(await r(a(`撤销这次相遇留下的意思?「{meant}」之后不会再回到 TA 心里,也不会再被当成变化的来源。`,{meant:e.meant}),{title:a(`撤销这次相遇`),confirmText:a(`撤销`),danger:!0}))try{await P.revoke(`meeting`,e.id),O(a(`已撤销`)),await C(),l(`changed`)}catch(e){O(e.message,!0)}}async function U(e){let t=await n(a(`撤销「{content}」?TA 不再惦记这件事,之后整理记忆时也不会把它写回来。可以写下原因(可不填):`,{content:e.content}),{title:a(`撤销这个约定`),confirmText:a(`撤销`),danger:!0,placeholder:a(`原因`)});if(t!==null)try{await P.revoke(`anticipation`,e.id,t),O(a(`已撤销`)),await C()}catch(e){O(e.message,!0)}}return y(()=>c.id,e=>{d.value=null,e&&C()},{immediate:!0}),(n,r)=>(D(),m(J,{open:!!e.id,title:x.value?.name||t(a)(`正在想起…`),eyebrow:t(a)(`TA 认识的人`),width:`560px`,onClose:r[0]||=e=>l(`close`)},{default:k(()=>[b.value?(D(),h(`p`,Se,i(b.value),1)):x.value?(D(),h(`div`,Ce,[o(`header`,we,[o(`span`,{class:`avatar`,style:u({"--hue":t(W)(x.value.name||x.value.userId)})},i(t(Y)(x.value.name)),5),o(`div`,null,[o(`p`,Te,i(x.value.feel),1),o(`small`,Ee,`QQ `+i(x.value.userId),1),o(`small`,De,i([S.value,x.value.lastTalkedAt?t(a)(`上次说上话 {v}`,{v:t(K)(x.value.lastTalkedAt,e.now)}):t(a)(`还没说上过话`),x.value.seenAt?t(a)(`上次见到 {v}`,{v:t(K)(x.value.seenAt,e.now)}):``].filter(Boolean).join(` · `)),1)])]),o(`div`,Oe,[(D(!0),h(f,null,_(t(L),([e,t])=>(D(),m(xe,{key:e,value:x.value[e],label:t},null,8,[`value`,`label`]))),128))]),x.value.impression?(D(),h(`p`,ke,i(t(a)(`印象:{impression}`,{impression:x.value.impression})),1)):p(``,!0),x.value.places?.length?(D(),h(`div`,Ae,[o(`span`,je,i(t(a)(`在这些地方见过:`)),1),(D(!0),h(f,null,_(x.value.places,e=>(D(),h(`span`,{key:e.id,class:`chip`,"data-tone":`quiet`},i(e.name),1))),128))])):p(``,!0),o(`section`,null,[o(`h3`,Me,i(t(a)(`这份感觉是怎么来的`)),1),d.value.changes.length?(D(),h(`ul`,Ne,[(D(!0),h(f,null,_(d.value.changes,e=>(D(),h(`li`,{key:e.id,class:A([`change`,{done:e.revoked}])},[o(`span`,{class:`chip`,"data-tone":e.revoked?`quiet`:t(ee).has(e.change)?`danger`:void 0},i(t(R)[e.change]||e.change),9,Pe),o(`div`,Fe,[o(`p`,null,i(e.note||t(a)(`没有写原因`)),1),o(`small`,Ie,i(t(a)(`{v} · {v2} · {length} 处来源`,{v:t(N)(e.created),v2:t(V)[e.origin]||t(a)(`写日记时`),length:e.sources.length})),1)]),e.revoked?(D(),h(`span`,Le,i(t(a)(`已撤销`)),1)):(D(),h(`button`,{key:1,class:`text-button`,"data-revoke-change":``,onClick:t=>T(e)},i(t(a)(`撤销`)),9,Re))],2))),128))])):(D(),h(`p`,ze,i(t(a)(`只是一起聊过天,还没有特别的变化。`)),1))]),d.value.meetings?.length?(D(),h(`section`,Be,[o(`h3`,Ve,i(t(a)(`和{name}的相遇留下了什么`,{name:x.value.name})),1),o(`ul`,He,[(D(!0),h(f,null,_(d.value.meetings,e=>(D(),h(`li`,{key:e.id,class:`change`},[o(`span`,{class:`chip`,"data-tone":e.private?`quiet`:void 0},i(e.private?t(a)(`私下`):e.choice===`silent`?t(a)(`没出声`):t(a)(`出了声`)),9,Ue),o(`div`,We,[o(`p`,null,i(e.meant),1),e.why?(D(),h(`small`,Ge,i(t(a)(`当时:{why}`,{why:e.why})),1)):p(``,!0),o(`small`,Ke,i(e.when)+` · `+i(e.sessionName),1)]),o(`button`,{class:`text-button`,"data-revoke-meeting":``,onClick:t=>H(e)},i(t(a)(`撤销`)),9,qe)]))),128))])])):p(``,!0),o(`section`,null,[o(`h3`,Je,i(t(a)(`TA 记得关于{name}的事`,{name:x.value.name})),1),d.value.memories.length?(D(),h(`ul`,Ye,[(D(!0),h(f,null,_(d.value.memories,e=>(D(),h(`li`,{key:e.id,class:A([`memory-item`,{superseded:e.status===`superseded`}])},[o(`p`,null,i(e.content),1),o(`small`,Xe,i(t(a)(`{v} · 在「{v2}」知道的 · {when}`,{v:t(F)[e.status]||e.status,v2:t(G)({name:e.sessionName,id:e.session_id||e.sessionId}),when:e.when})),1),e.status===`confirmed`?(D(),h(`div`,Ze,[o(`span`,Qe,i(t(a)(`改完即生效`)),1),v(te,{"aria-label":t(a)(`分寸:`)+e.content,"model-value":e.discretion||`open`,options:Object.entries(t(z)).map(([e,t])=>({value:e,label:t})),"onUpdate:modelValue":t=>M(e,{discretion:t})},null,8,[`aria-label`,`model-value`,`options`,`onUpdate:modelValue`]),o(`button`,{class:`small`,onClick:t=>M(e,{locked:!e.locked})},i(e.locked?t(a)(`解锁`):t(a)(`锁定`)),9,$e),o(`button`,{class:`small danger`,onClick:t=>E(e)},i(t(a)(`撤销`)),9,et)])):p(``,!0)],2))),128))])):(D(),h(`p`,tt,i(t(a)(`还没有记住关于这个人的事。`)),1))]),o(`section`,null,[o(`h3`,nt,i(t(a)(`和{name}有关的约定`,{name:x.value.name})),1),d.value.anticipations.length?(D(),h(`ul`,rt,[(D(!0),h(f,null,_(d.value.anticipations,e=>(D(),h(`li`,{key:e.id,class:A([`change`,{done:e.state!==`pending`}])},[o(`span`,{class:`chip`,"data-tone":e.state===`pending`?void 0:`quiet`},i(t(I)[e.state]||e.state),9,it),o(`div`,at,[o(`p`,null,i(e.content),1),o(`small`,ot,i(t(B)[e.kind]||e.kind)+` · `+i(e.when),1)]),e.status===`revoked`?p(``,!0):(D(),h(`button`,{key:0,class:`text-button`,onClick:t=>U(e)},i(t(a)(`撤销`)),9,st))],2))),128))])):(D(),h(`p`,ct,i(t(a)(`没有和这个人有关的约定。`)),1))])])):p(``,!0)]),_:1},8,[`open`,`title`,`eyebrow`]))}}),[[`__scopeId`,`data-v-5645edbd`]]),ut={class:`faces`},dt={class:`faces-head`},ft={class:`eyebrow`},pt={class:`muted`},mt={class:`face-grid`},ht={class:`badge`},gt={key:0,class:`qq`},_t={class:`muted`},vt={key:0,class:`place-notes compact`},yt={class:`face-excerpt`},bt={key:1,class:`muted small-text`},xt=[`onClick`],St={key:0,class:`stack`},Ct={key:0,class:`place-notes`},wt={key:0,class:`muted small-text`},Tt={class:`faint`},Et={key:1,class:`muted`},Dt={key:2,class:`versions`},Ot={key:0},kt={key:1},At={key:0,class:`change-why`},jt={key:4,class:`legacy-face`},Mt={class:`face-full`},Nt=[`onClick`],$={key:0,class:`muted`},Pt=x(S({__name:`GroupFaces`,props:{groups:{}},emits:[`changed`],setup(n,{emit:c}){let l=n,d=c,m=j(null),g=j({}),x=s({impression:b(`印象`),wish:b(`想做的事`),preference:b(`偏好`),question:b(`还在想的问题`),boundary:b(`相处的分寸`)}),S=s({add:b(`新增`),revise:b(`修改`),remove:b(`放下`)});y(()=>l.groups,e=>{m.value&&=e.find(e=>e.session===m.value.session)||null});async function C(e){if(!(g.value[e]&&g.value[e]?.[0]?.id===l.groups.find(t=>t.session===e)?.face?.id))try{g.value={...g.value,[e]:await P.faces(e)}}catch(e){O(e.message,!0)}}async function w(e,t){if(await r(a(`撤销这次想法更新?会恢复上一次保留的想法。`),{title:a(`撤销想法更新`),confirmText:a(`撤销`),danger:!0}))try{await P.revoke(`face`,t),g.value={...g.value,[e]:await P.faces(e)},O(a(`已撤销`)),d(`changed`)}catch(e){O(e.message,!0)}}return(r,s)=>(D(),h(`section`,ut,[o(`div`,dt,[o(`span`,ft,i(t(a)(`在这里的想法`)),1),o(`h2`,null,i(t(a)(`同一个她,在不同的地方留下自己的想法。`)),1),o(`p`,pt,i(t(a)(`她根据相处留下印象、愿望或疑问,选择什么时候新增、修改或放下。每次更新保留理由和来源。`)),1)]),o(`div`,mt,[(D(!0),h(f,null,_(n.groups,e=>(D(),h(`article`,{key:e.session,class:`card face-card`,style:u({"--hue":t(W)(e.session)})},[o(`header`,null,[o(`span`,ht,i(e.kind===`private`?t(a)(`私`):t(a)(`群`)),1),o(`div`,null,[o(`h3`,null,i(t(G)({id:e.session,name:e.name,kind:e.kind})),1),e.kind===`private`?(D(),h(`small`,gt,`QQ `+i(String(e.session||``).split(`:`).pop()),1)):p(``,!0),o(`small`,_t,i(e.kind===`private`?t(a)(`私聊`):t(a)(`群聊`))+i(e.bond?` · ${e.bond.feel}`:``),1)])]),e.face?.notes?.length?(D(),h(`ul`,vt,[(D(!0),h(f,null,_(e.face.notes.slice(0,3),e=>(D(),h(`li`,{key:e.id},[o(`small`,null,i(t(x)[e.kind]||t(a)(`想法`)),1),o(`p`,yt,i(e.content),1)]))),128))])):(D(),h(`p`,bt,i(t(a)(`还没有特别想留在这里的想法,之后由她自己选择。`)),1)),o(`button`,{class:`text-button`,onClick:t=>{m.value=e,C(e.session)}},i(t(a)(`查看想法与变化 ↗`)),9,xt)],4))),128))]),v(J,{open:!!m.value,title:m.value?.name||t(a)(`在这里的想法`),eyebrow:t(a)(`PLACE NOTES / 在这里的想法`),width:`680px`,onClose:s[0]||=e=>m.value=null},{default:k(()=>[m.value?(D(),h(`div`,St,[m.value.face?.notes?.length?(D(),h(`ul`,Ct,[(D(!0),h(f,null,_(m.value.face.notes,e=>(D(),h(`li`,{key:e.id,class:`face-full`},[o(`small`,null,i(t(x)[e.kind]||t(a)(`想法`)),1),o(`p`,null,i(e.content),1),e.why?(D(),h(`p`,wt,i(e.why),1)):p(``,!0),o(`small`,Tt,i(t(a)(`{n} 处来源`,{n:e.sources?.length||0})),1)]))),128))])):(D(),h(`p`,Et,i(t(a)(`还没有特别想留在这里的想法,之后由她自己选择。`)),1)),o(`h3`,null,i(t(a)(`变化历史`)),1),g.value[m.value.session]?(D(),h(`ol`,Dt,[(D(!0),h(f,null,_(g.value[m.value.session],n=>(D(),h(`li`,{key:n.id,class:A({revoked:n.revoked})},[o(`time`,null,i(t(N)(n.created)),1),o(`span`,null,[n.revoked?(D(),h(`b`,Ot,i(t(a)(`已撤销 ·`)),1)):n.origin===`migration`?(D(),h(`b`,kt,i(t(a)(`旧版 ·`)),1)):p(``,!0),n.changes?.length?(D(!0),h(f,{key:2},_(n.changes,(n,r)=>(D(),h(`p`,{key:r},[o(`b`,null,i(t(S)[n.action])+` · `,1),e(i(n.content)+` `,1),n.why?(D(),h(`small`,At,i(n.why),1)):p(``,!0)]))),128)):n.notes?.length?(D(),h(f,{key:3},[e(i(n.notes.map(e=>e.content).join(`;`)),1)],64)):(D(),h(`details`,jt,[o(`summary`,null,i(t(a)(`旧人格记录,仅保留历史`)),1),o(`p`,Mt,i(n.content||[n.role,n.tone,n.aspiration].filter(Boolean).join(`;`)),1)]))]),n.revoked?p(``,!0):(D(),h(`button`,{key:0,class:`text-button`,onClick:e=>w(m.value.session,n.id)},i(t(a)(`撤销`)),9,Nt))],2))),128)),g.value[m.value.session].length?p(``,!0):(D(),h(`li`,$,i(t(a)(`没有记录。`)),1))])):p(``,!0)])):p(``,!0)]),_:1},8,[`open`,`title`,`eyebrow`])]))}}),[[`__scopeId`,`data-v-a6532d0c`]]),Ft={class:`page people`},It={class:`people-bar`},Lt=[`placeholder`,`aria-label`],Rt={class:`muted`},zt={key:1,class:`people-list`},Bt=[`data-person`,`onClick`],Vt={class:`qq`},Ht={class:`muted`},Ut={class:`stack tight`},Wt={class:`faint`},Gt=x(S({__name:`PeoplePage`,setup(e){let n=l(g(`/mind/bonds`)||null),r=j(`galaxy`),s=j(``),c=j(T.sub&&T.sub!==`list`?T.sub:null);T.sub===`list`&&(r.value=`list`);let b=w(()=>{let e=s.value.trim(),t=n.value?.people||[];return e?t.filter(t=>`${t.name}${t.userId}${t.impression||``}`.includes(e)):t}),x=0;async function S(){let e=++x,t=await P.bonds();e===x&&(n.value=t)}function O(e){c.value=e,C(e)}function k(){c.value=null,C(r.value===`list`?`list`:``)}return y(r,e=>{T.page===`people`&&(c.value||C(e===`list`?`list`:``))}),y(()=>[T.page,T.sub],([e,t])=>{e===`people`&&(t===`list`&&(r.value=`list`),c.value=t&&t!==`list`?t:null)}),H(`people`,S),(e,l)=>(D(),h(`div`,Ft,[o(`div`,It,[v(q,{modelValue:r.value,"onUpdate:modelValue":l[0]||=e=>r.value=e,label:t(a)(`人际的视图`),items:[{key:`galaxy`,label:t(a)(`相遇之间`),count:n.value?.people.length??``},{key:`list`,label:t(a)(`列表`)}]},null,8,[`modelValue`,`label`,`items`]),M(o(`input`,{"onUpdate:modelValue":l[1]||=e=>s.value=e,class:`people-search`,type:`search`,placeholder:t(a)(`找一个人`),"aria-label":t(a)(`找一个人`)},null,8,Lt),[[d,s.value]]),o(`p`,Rt,i(t(a)(`她记得相遇,也记得关系会随着时间改变。`)),1)]),n.value?(D(),h(f,{key:0},[b.value.length?(D(),h(f,{key:0},[r.value===`galaxy`?(D(),m(ge,{key:0,people:b.value,activity:t(U).data?.activity?.kind,onOpen:O,onList:l[2]||=e=>r.value=`list`},null,8,[`people`,`activity`])):(D(),h(`div`,zt,[(D(!0),h(f,null,_(b.value,e=>(D(),h(`button`,{key:e.userId,class:`card person-card`,"data-person":e.userId,onClick:t=>O(e.userId)},[o(`header`,null,[o(`span`,{class:`avatar`,style:u({"--hue":t(W)(e.name||e.userId)})},i(t(Y)(e.name)),5),o(`div`,null,[o(`h3`,null,i(e.name),1),o(`small`,Vt,`QQ `+i(e.userId),1),o(`small`,Ht,i(e.feel),1)])]),o(`div`,Ut,[(D(!0),h(f,null,_(t(L),([t,n])=>(D(),m(X,{key:t,label:n,value:e[t],warn:t===`tension`},null,8,[`label`,`value`,`warn`]))),128))]),o(`small`,Wt,i(t(a)(`在 {length} 个地方见过 · 上次说上话 {v}`,{length:e.sessions.length,v:e.lastTalkedAt?t(K)(e.lastTalkedAt,t(U).data?.now):t(a)(`还没有`)})),1)],8,Bt))),128))]))],64)):(D(),m(E,{key:1,title:t(a)(`TA 还没有认识谁`),text:t(a)(`有人和 TA 说话、TA 细看过群聊之后,这里会出现 TA 对每个人的感觉。`)},null,8,[`title`,`text`])),v(Pt,{groups:n.value.groups,onChanged:S},null,8,[`groups`])],64)):p(``,!0),v(lt,{id:c.value,now:t(U).data?.now,onClose:k,onChanged:S},null,8,[`id`,`now`])]))}}),[[`__scopeId`,`data-v-45f19e60`]]);export{Gt as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as e,M as t,P as n,j as r,m as i}from"./Empty-ddEfu0w8.js";var a=new Map;function o(e,t){let n=JSON.stringify([e,t]),r=a.get(n);return r||(r=new Intl.DateTimeFormat(e,t),a.set(n,r),a.size>64&&a.delete(a.keys().next().value)),r}var s={overview:()=>i(`/mind`),mood:()=>i(`/mind/mood`),notes:(e=``)=>i(`/mind/thoughts?q=`+encodeURIComponent(e)),presence:()=>i(`/mind/presence`),today:()=>i(`/mind/today`),person:e=>i(`/mind/people/`+encodeURIComponent(e)),self:()=>i(`/mind/self`),thread:e=>i(`/mind/self/`+encodeURIComponent(e)),bonds:()=>i(`/mind/bonds`),bondChanges:(e,t)=>i(`/mind/bonds/${e}/`+encodeURIComponent(t)),faces:e=>i(`/mind/faces/`+encodeURIComponent(e)),life:(e=``,t)=>i(`/mind/life?q=`+encodeURIComponent(e)+(t?`&before=`+t:``)),day:e=>i(`/mind/day/`+e),chapter:e=>i(`/mind/chapters/`+e),story:()=>i(`/mind/story`),nature:()=>i(`/mind/nature`),saveNature:e=>i(`/mind/nature`,`PUT`,e),saveSettings:e=>i(`/mind/settings`,`PUT`,e),reflect:()=>i(`/mind/reflect`,`POST`,{}),review:()=>i(`/mind/review`,`POST`,{}),revoke:(e,t,n=``)=>i(`/mind/revoke`,`POST`,{kind:e,id:t,reason:n}),thought:(e,t)=>i(`/mind/thoughts/`+encodeURIComponent(e),`PATCH`,t),removeThought:e=>i(`/mind/thoughts/`+encodeURIComponent(e),`DELETE`,{}),preview:e=>i(`/mind/preview`,`POST`,e),savePrompts:e=>i(`/core/prompts`,`PUT`,e)},c=n({promise:r(`答应的事`),plan:r(`想做的事`),event:r(`别人的安排`),date:r(`每年的日子`)}),l=n({pending:r(`还在等`),lapsed:r(`悄悄错过了`),done:r(`做到了`),missed:r(`错过了`),let_go:r(`不再惦记`),revoked:r(`已撤销`)}),u=n({turn:r(`聊天时`),direct:r(`聊天时`),group:r(`聊天时`),solitude:r(`独处时`),daily:r(`写日记时`),weekly:r(`回顾时`),memory:r(`从 TA 说过的话里`),migration:r(`旧版本`)}),d=n({activity:r(`做自己的事`),solitude:r(`独处`),expression:r(`自己的念头`),daily:r(`写日记`),weekly:r(`回顾这段日子`),night:r(`夜里整理`)}),f=n({running:r(`进行中`),written:r(`留下了东西`),state:r(`心情变了`),empty:r(`没什么新的`),error:r(`出了点问题`),skipped:r(`跳过了`),cancelled:r(`作废了`),interrupted:r(`重启中断`)}),p=n({active:r(`已经是 TA 的一部分`),emerging:r(`刚开始有这种感觉`),closed:r(`已经放下`)}),m={interest:`#ffc15f`,view:`#7fc8ff`,trait:`#ff9a8b`,habit:`#b9a4ff`,intention:`#6fe0b8`,care:`#ff8fbf`,curiosity:`#9ef0ff`},h=n({warmer:r(`更亲近了一点`),closer:r(`走近了`),trust_up:r(`更信任了`),trust_down:r(`有点失望`),friction:r(`有了别扭`),repair:r(`和好了`),distance:r(`疏远了一点`),impression:r(`印象`)}),g=new Set([`friction`,`trust_down`,`distance`]),_=n([[`familiarity`,r(`熟悉`)],[`closeness`,r(`亲近`)],[`trust`,r(`信任`)],[`tension`,r(`别扭`)]]),v=n({open:r(`公开`),private:r(`私下知道`),secret:r(`要 TA 保密`)}),y=n({confirmed:r(`TA 这样认为`),superseded:r(`已被新的取代`),candidate:r(`旧版待确认`),disputed:r(`有争议`),expired:r(`已过期`),deleted:r(`已撤销`)}),b=n({planned:r(`等合适的时候`),sending:r(`正在发`),sent:r(`说出口了`),declined:r(`TA 后来决定不说`),skipped:r(`时机不合适`),cancelled:r(`没有发`),uncertain:r(`投递未确认`)}),x=n({speak:r(`开口`),react:r(`简短反应`),decline:r(`说不想聊`),silent:r(`没出声`)});function S(n,r=`Asia/Shanghai`){return n?o(t(),{timeZone:r,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`}).format(n):e(`还没有`)}function C(e){return`${Math.round(Math.max(0,Math.min(1,Number(e)||0))*100)}%`}export{S as _,g as a,m as c,b as d,d as f,C as g,s as h,x as i,y as l,p as m,l as n,_ as o,f as p,h as r,v as s,c as t,u,o as v};
|