luckytri 1.0.6 → 1.0.7
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/LICENSE +21 -21
- package/docs/en/CHANGELOG.md +25 -16
- package/docs/en/chat-style.md +28 -9
- package/docs/en/guide.md +5 -5
- package/docs/en/her-life.md +2 -2
- package/docs/en/webui.md +1 -1
- package/docs/zh/CHANGELOG.md +25 -16
- package/docs/zh/chat-style.md +30 -9
- package/docs/zh/guide.md +5 -5
- package/docs/zh/her-life.md +2 -2
- package/docs/zh/webui.md +1 -1
- package/package.json +1 -1
- package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
- package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
- package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
- package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
- package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
- package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
- package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
- package/public/app/assets/LifePage-BUi5euBF.css +1 -0
- package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
- package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
- package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
- package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
- package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
- package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
- package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
- package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
- package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
- package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
- package/public/app/assets/TimePage-BOms_n4I.css +1 -0
- package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
- package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
- package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
- package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
- package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
- package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
- package/public/app/index.html +5 -6
- package/public/guide.en.html +3 -3
- package/public/guide.html +3 -3
- package/scripts/import-data.js +128 -128
- package/scripts/proxy-config.js +167 -167
- package/scripts/update-instance.js +201 -201
- package/server/channels/adapters.js +27 -27
- package/server/channels/onebot/adapter.js +288 -288
- package/server/channels/qqbot/adapter.js +253 -253
- package/server/channels/qqbot/api.js +199 -199
- package/server/channels/qqbot/credentials.js +11 -11
- package/server/channels/qqbot/gateway.js +274 -274
- package/server/channels/qqbot/index.js +389 -389
- package/server/channels/segments.js +40 -40
- package/server/channels/session-key.js +153 -153
- package/server/channels/state.js +42 -42
- package/server/core/api.js +564 -564
- package/server/core/context-builder.js +288 -288
- package/server/core/context-compactor.js +454 -454
- package/server/core/conversation-cues.js +365 -325
- package/server/core/conversation-grounding.js +178 -0
- package/server/core/conversation-manager.js +87 -87
- package/server/core/dialogue-context.js +52 -0
- package/server/core/feedback.js +8 -8
- package/server/core/group-style.js +72 -72
- package/server/core/initiative-context.js +66 -66
- package/server/core/input-budget.js +32 -32
- package/server/core/local-demo.js +98 -98
- package/server/core/message-manager.js +118 -118
- package/server/core/message-scheduler.js +78 -78
- package/server/core/model-json.js +190 -190
- package/server/core/model-presets.js +900 -900
- package/server/core/name-call.js +29 -29
- package/server/core/network.js +157 -157
- package/server/core/person-name.js +22 -22
- package/server/core/persona-manager.js +324 -269
- package/server/core/reply-delivery.js +472 -404
- package/server/core/reply-target-resolver.js +50 -50
- package/server/core/response-generator.js +58 -50
- package/server/core/response-validator.js +30 -27
- package/server/core/retired-prompts.js +51 -51
- package/server/core/sessions.js +109 -109
- package/server/core/speaker-names.js +92 -92
- package/server/core/topic-tracker.js +65 -65
- package/server/core/turn.js +42 -8
- package/server/http.js +18 -18
- package/server/i18n/decisions.js +209 -209
- package/server/i18n/index.js +144 -144
- package/server/i18n/labels.js +0 -5
- package/server/i18n/models.js +111 -111
- package/server/knowledge/api.js +342 -342
- package/server/knowledge/embedding-profile.js +114 -114
- package/server/knowledge/index.js +13 -13
- package/server/knowledge/retrieval.js +93 -93
- package/server/knowledge/schema.js +158 -158
- package/server/knowledge/vector-index.js +90 -90
- package/server/mind/affect.js +265 -265
- package/server/mind/attention.js +200 -200
- package/server/mind/bonds.js +565 -565
- package/server/mind/clock.js +83 -83
- package/server/mind/context-selection.js +16 -4
- package/server/mind/continuity.js +562 -562
- package/server/mind/conversation-origin.js +175 -175
- package/server/mind/days.js +210 -210
- package/server/mind/evidence-read.js +45 -45
- package/server/mind/faces.js +373 -373
- package/server/mind/guard.js +68 -60
- package/server/mind/initiative.js +338 -338
- package/server/mind/life/activities.js +21 -21
- package/server/mind/life/context.js +330 -330
- package/server/mind/life/growth.js +261 -261
- package/server/mind/life/presence.js +305 -305
- package/server/mind/meetings.js +1069 -1069
- package/server/mind/memory-policy.js +48 -48
- package/server/mind/memory.js +888 -888
- package/server/mind/nature.js +358 -358
- package/server/mind/novelty.js +19 -19
- package/server/mind/periods.js +118 -118
- package/server/mind/reading.js +120 -120
- package/server/mind/relationship-context.js +71 -71
- package/server/mind/relationships.js +390 -390
- package/server/mind/salience.js +201 -201
- package/server/mind/schema.js +567 -567
- package/server/mind/self.js +496 -484
- package/server/mind/thoughts.js +274 -274
- package/server/mind/time/agenda.js +242 -242
- package/server/mind/time/api.js +200 -200
- package/server/mind/time/attention.js +34 -34
- package/server/mind/time/delivery-links.js +130 -130
- package/server/mind/time/games.js +7 -3
- package/server/mind/time/index.js +3 -0
- package/server/mind/time/model-material.js +77 -77
- package/server/mind/time/presentation.js +35 -51
- package/server/mind/time/schedule.js +83 -83
- package/server/mind/time/schema.js +84 -84
- package/server/mind/time/search.js +383 -383
- package/server/mind/time/sharing.js +472 -472
- package/server/mind/time/task-links.js +311 -311
- package/server/mind/time/works.js +438 -432
- package/server/mind/traits.js +191 -191
- package/server/mind/view.js +43 -6
- package/server/runtime/installation.js +116 -116
- package/server/runtime/update-manager.js +140 -140
- package/server/storage/data-package.js +298 -298
- package/server/storage/data-transfer-api.js +50 -50
- package/server/storage/data-transfer-worker.js +18 -18
- package/server/storage/data-transfer.js +416 -416
- package/server/studio/display-names.js +139 -139
- package/server/studio/management.js +102 -102
- package/server/studio/updates.js +144 -144
- package/server/version.js +10 -10
- package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
- package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
- package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
- package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
- package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
- package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
- package/public/app/assets/TimePage-C-guwPk2.css +0 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 LuckyTri Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LuckyTri Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/docs/en/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
<p align="right"><a href="../zh/CHANGELOG.md">中文</a> · <b>English</b></p>
|
|
2
2
|
|
|
3
|
-
# Changelog
|
|
4
|
-
|
|
5
|
-
## 1.0.
|
|
3
|
+
# Changelog
|
|
4
|
+
|
|
5
|
+
## 1.0.7 — 2026-10-09
|
|
6
|
+
|
|
7
|
+
- Ground understanding in the current messages, supplements, speakers and quote chains before choosing a response. Corrections address the actual question instead of substituting apologies or abstract monologues.
|
|
8
|
+
- Keep single topical remarks out of the enduring self's core and prevent extracted replies from becoming fixed scripts. Related recollections, cross-conversation continuity and disclosure boundaries remain available.
|
|
9
|
+
- Exclude known bots from human room-style statistics and end empty bot confirmation loops. An explicit addressed stop ends the current turn; a renewed question can still receive a reply.
|
|
10
|
+
- Preserve provenance and uncertainty in game-content presentation. Current activities and work fragments distinguish reference reading and model-generated material from client operation or completion.
|
|
11
|
+
- Rewrite the latest rejected draft, allow requested factual repetition, and keep contradiction repairs confined to relevant questions. Conversation endings no longer receive an unrelated confusion fallback.
|
|
12
|
+
- Add full-corpus review, historical replay and sequential conversation evaluation tools. Paid model checks run explicitly; private transcripts and reports stay local and are excluded from release artifacts.
|
|
13
|
+
|
|
14
|
+
## 1.0.5 — 2026-10-07
|
|
6
15
|
|
|
7
16
|
- Fixed bound relationships being forgotten when the person was absent from the current conversation. Chat, initiative, solitude, diaries and reviews recall the same account-based ledger by name and role.
|
|
8
17
|
- Distinguished current relationship facts from past opinions and personal feelings. Historical denials and jokes remain in history; relationship-related replies are checked against current knowledge while preserving her choices about interaction and address.
|
|
@@ -16,19 +25,19 @@
|
|
|
16
25
|
- Update and restart stages and validates the official release before stopping, saves a verified data restore point, and switches programs. Failed startup attempts restoration of the previous code and data. Source/npm installs and instance configuration remain intact; repeated clicks coalesce and CLI launches retain managed updates.
|
|
17
26
|
- Added privacy, same-name identity, recovery, real installed-package switching and viewport regression checks. Archive checks verify bytes before parsing SQLite, so damaged files do not expose raw schema errors.
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
## 1.0.3 — 2026-10-06
|
|
21
|
-
|
|
22
|
-
- Group personas become thoughts about each place. She selectively adds, revises or lets go of impressions, wishes, preferences, questions and boundaries from local encounters, retaining reasons, sources and versions. Her overall personality remains shared; copied legacy personas stay only in history.
|
|
23
|
-
- Manual update checks in system runtime settings show the installed and latest npm versions and release notes. Checks cache and coalesce requests and recover from failures; they do not install or restart.
|
|
24
|
-
- Nature edit allowances come from the server. Audited, idempotent local extra opportunities preserve personality history and other instances' default limits.
|
|
25
|
-
- Added source isolation, selective updates, restart persistence, failed update recovery and responsive interface checks.
|
|
26
|
-
|
|
27
|
-
## 1.0.2 — 2026-10-06
|
|
28
|
-
|
|
29
|
-
- Streamed full SQLite data packages with validated import previews, retained recovery points and rollback on failure.
|
|
30
|
-
- Destination management sign-ins and connection settings are retained. Imported auto replies and plugin grants await review; cancelled, expired and interrupted temporary packages are reclaimed.
|
|
31
|
-
|
|
28
|
+
|
|
29
|
+
## 1.0.3 — 2026-10-06
|
|
30
|
+
|
|
31
|
+
- Group personas become thoughts about each place. She selectively adds, revises or lets go of impressions, wishes, preferences, questions and boundaries from local encounters, retaining reasons, sources and versions. Her overall personality remains shared; copied legacy personas stay only in history.
|
|
32
|
+
- Manual update checks in system runtime settings show the installed and latest npm versions and release notes. Checks cache and coalesce requests and recover from failures; they do not install or restart.
|
|
33
|
+
- Nature edit allowances come from the server. Audited, idempotent local extra opportunities preserve personality history and other instances' default limits.
|
|
34
|
+
- Added source isolation, selective updates, restart persistence, failed update recovery and responsive interface checks.
|
|
35
|
+
|
|
36
|
+
## 1.0.2 — 2026-10-06
|
|
37
|
+
|
|
38
|
+
- Streamed full SQLite data packages with validated import previews, retained recovery points and rollback on failure.
|
|
39
|
+
- Destination management sign-ins and connection settings are retained. Imported auto replies and plugin grants await review; cancelled, expired and interrupted temporary packages are reclaimed.
|
|
40
|
+
|
|
32
41
|
## 1.0.1 — 2026-10-05
|
|
33
42
|
|
|
34
43
|
- Global npm installation runs with `luckytri`, including the server, built WebUI, guides and bundled plugin. User data remains outside the package across updates and reinstalls.
|
package/docs/en/chat-style.md
CHANGED
|
@@ -12,18 +12,20 @@ By default she speaks plain, everyday language:
|
|
|
12
12
|
- She reacts to the specific thing, instead of following a fixed "repeat the feeling, comfort, advise, ask a question" routine.
|
|
13
13
|
- Venting is not assumed to want advice; good news does not get a consoling answer; jokes are put away when someone is really hurting.
|
|
14
14
|
- A conversation may simply end; she does not ask "and then?" every turn and does not turn every message into a joke.
|
|
15
|
+
- Ordinary replies name the actual people, events and actions instead of replacing them with accounting or carrying metaphors. Literal accounting, literature and requested metaphors remain valid.
|
|
16
|
+
- When someone says she is abstract or repetitive, she checks her last sentence and explains its actual meaning instead of merely acknowledging the complaint.
|
|
15
17
|
- She uses memory only when it relates to the current topic and does not show off a memory file; when a reference is unclear she asks, and never invents experiences.
|
|
16
18
|
- She does not force slang to seem young. A serious setting beats style.
|
|
17
19
|
|
|
18
20
|
The following are hand-designed directions, not results measured on an external model (the Chinese originals are the intent; here they are rendered loosely):
|
|
19
21
|
|
|
20
|
-
| They say
|
|
21
|
-
|
|
|
22
|
-
| Five minutes to clocking off and the boss dumps more work | Of course it picks right now
|
|
23
|
-
| None of what I revised came up
|
|
24
|
-
| I passed the interview!!
|
|
25
|
-
| Don't advise me, just let me rant
|
|
26
|
-
| I'm going to take a shower
|
|
22
|
+
| They say | Direction we want |
|
|
23
|
+
| --------------------------------------------------------- | ------------------------------------------ |
|
|
24
|
+
| Five minutes to clocking off and the boss dumps more work | Of course it picks right now |
|
|
25
|
+
| None of what I revised came up | Not a single guess landed |
|
|
26
|
+
| I passed the interview!! | Nice!! That calls for a little celebration |
|
|
27
|
+
| Don't advise me, just let me rant | Okay, rant away, I'm listening |
|
|
28
|
+
| I'm going to take a shower | Go ahead |
|
|
27
29
|
|
|
28
30
|
## The rhythm of this place
|
|
29
31
|
|
|
@@ -33,6 +35,10 @@ The statistics reach her only as `inner.room` and are **not her character**: she
|
|
|
33
35
|
|
|
34
36
|
## Feedback
|
|
35
37
|
|
|
38
|
+
Accounts marked as bots in relationship records do not contribute to room style statistics. Their messages and shared experiences remain stored. Uninterrupted bot exchanges stop when they are only confirming or rephrasing the same thing without a new question or concrete event. Identity comes from account records, never nicknames. Questions, corrections and requested creative work can continue. Confirmation loops do not create additional emotional or relationship changes.
|
|
39
|
+
|
|
40
|
+
Her long-term self stays stable across new topics. Related self recollections appear in `inner.relatedSelf`; `inner.onMind` includes relevant notes without filling unused slots with unrelated thoughts. Temporary lines do not become fixed reply habits. Existing records are retained, while fixed scripts stop being offered as expression preferences.
|
|
41
|
+
|
|
36
42
|
Under "Chat" you can give feedback on a reply she sent: natural, too long, too stiff, too many memes, or undo it. Feedback is not a rewrite rule: it reaches her as something she heard, read by herself during solitude and in the next turns of that conversation, and it never rewrites her nature.
|
|
37
43
|
|
|
38
44
|
## Checks before sending
|
|
@@ -41,6 +47,7 @@ Before a reply goes out, local checks look for:
|
|
|
41
47
|
|
|
42
48
|
- a customer-service or consulting tone, list-like report replies, stage actions;
|
|
43
49
|
- unfounded intimate forms of address, excess length, repetition, back-to-back questions;
|
|
50
|
+
- recent replies that mostly repeat earlier wording with a small addition; explicitly requested quotes and factual repeats remain valid;
|
|
44
51
|
- ignoring "no advice needed", joking in a serious setting, piled-up slang;
|
|
45
52
|
- presenting her own earlier words as the other person's ("you just said…" when their record has no such line);
|
|
46
53
|
- claiming to have played or finished something (she has no record of such experience; a liking is not a record of having played);
|
|
@@ -48,9 +55,9 @@ Before a reply goes out, local checks look for:
|
|
|
48
55
|
|
|
49
56
|
When she is asked "how do you know?", the basis is checked first: for something she said unprompted (the other person had been silent for two hours or more), the basis can only be a guess or a real memory, never that she saw a message they sent; if a rewrite still invents a source, a verified correction is sent instead.
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
Ordinary replies get at most two rewrites; initiative and high budget pressure allow one. Each rewrite receives the actual rejected draft and preserves meaning and facts. If it still fails, an unaddressed turn stays silent; a direct exchange admits that she has not worked it out instead of pretending to understand with a filler word. Answers are never simply truncated. Rejected trailing hh decoration can be removed, followed by all checks again, preserving useful content.
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
Feelings, corrections, crises, abstract bot exchanges, attribution and capability claims may require review of each draft, increasing latency and cost. Rewrites are bounded. Conservative checks cannot catch every stiff or incorrect expression; naturalness still depends on the model, nature and group context.
|
|
54
61
|
|
|
55
62
|
## How to try it
|
|
56
63
|
|
|
@@ -60,3 +67,15 @@ Besides the turn's own call, the review and the rewrite happen at most once each
|
|
|
60
67
|
- **Audit**: `npm run audit:sent` replays the replies she recently sent against today's checks, read-only and without calling a model. Run it before changing a check to see whether the new rule would hurt. The replies already passed the checks when sent, so the number is for judging a new rule, not a score of how well she talks.
|
|
61
68
|
|
|
62
69
|
For real acceptance, watch 10–20 consecutive turns rather than a single line: does she pick up details, ask questions too often, repeat catchphrases, talk over others, suddenly turn into customer service, or mix up a joke with real sadness. Automated tests cover engineering constraints and hand-made scenario samples; the language quality of a real model still depends on your own observation.
|
|
70
|
+
|
|
71
|
+
In the source checkout, run `node --env-file-if-exists=.env scripts/dev/evaluate-dialogue.js --limit 120` for live-model replay through understanding, validation, review and rewriting, followed by a separate quality review. This uses the configured default model and incurs model costs. It never connects to QQ or changes the running memory. Reports stay in local `data/evaluations/`. Model errors, unresolved issues and fallback replies fail the run. A single model's review has limitations; inspect the actual outputs too.
|
|
72
|
+
|
|
73
|
+
Additional source-checkout checks:
|
|
74
|
+
|
|
75
|
+
- `node --env-file-if-exists=.env scripts/dev/audit-dialogue-corpus.js` reads every live message in overlapping conversation blocks and records coverage. Missing evidence in one block is a lead to investigate, not proof of fabrication.
|
|
76
|
+
- Replay with `--current-nature --rebuild-mind --review-model <profile-id>` to rebuild historical mind selection in an in-memory copy while retaining saved activity progress, and use a separate reviewer. `--model <profile-id>` selects an evaluation model without changing the live instance. This does not reproduce historical external services or images.
|
|
77
|
+
- `node --env-file-if-exists=.env scripts/dev/evaluate-dialogue-sequences.js --review-model <profile-id>` carries generated replies into the next turn and checks corrections, boundaries, affection, factual provenance and consistency over a conversation.
|
|
78
|
+
|
|
79
|
+
These commands incur model costs. Reviewers can miss errors or cite the wrong sentence; inspect their evidence. Combinatorial rule checks are not equivalent to the same number of distinct semantic conversations, and no test establishes perfect future conversation quality.
|
|
80
|
+
|
|
81
|
+
Current dialogue receives a factual `exchange` tail with new messages, quote chains and recent own words. Its short `understanding` summary remains revisable. Single extracted topical views stay retrievable without becoming enduring core traits; a one-off extracted habit does not become a response script. Reference-based game activities and model-generated material retain provenance and uncertainty instead of being presented as client operation. Reply review has an independent evaluator role and does not inherit the character's performance instructions.
|
package/docs/en/guide.md
CHANGED
|
@@ -200,11 +200,11 @@ The server removes the export copy after download. Cancelled, expired and interr
|
|
|
200
200
|
| Too much motion | "Reduce motion" at the bottom left; when the system asks for reduced motion, particles and morphing switch off automatically |
|
|
201
201
|
|
|
202
202
|
Never post API keys, `.env` or private chat text anywhere public.
|
|
203
|
-
|
|
204
|
-
### Thoughts about places and update checks
|
|
205
|
-
|
|
206
|
-
People → Thoughts about this place keeps her own impressions, wishes and questions, selectively updated from real encounters. Details show reasons, sources and history; copied group personas remain history only.
|
|
207
|
-
|
|
203
|
+
|
|
204
|
+
### Thoughts about places and update checks
|
|
205
|
+
|
|
206
|
+
People → Thoughts about this place keeps her own impressions, wishes and questions, selectively updated from real encounters. Details show reasons, sources and history; copied group personas remain history only.
|
|
207
|
+
|
|
208
208
|
People → Relationships binds a person or another robot by account, with reciprocal roles, an optional name and reason, and visibility. The relationship informs conversation, solitude, diaries and action choices. Trust and closeness still come from real encounters; a binding creates no shared history. Changes and removal retain history and memories. Private relationships are used only in private chats with that account. Equal names never merge accounts.
|
|
209
209
|
|
|
210
210
|
An absent person can still be recalled by name or relationship role. Current facts, actual encounters and her own feelings inform that recognition together. Earlier opinions remain history rather than overriding current facts, and relevant replies are checked before delivery. She still chooses how to interact and address people without reciting management fields.
|
package/docs/en/her-life.md
CHANGED
|
@@ -187,5 +187,5 @@ Later upgrades only add tables and columns: existing chapters are kept and the f
|
|
|
187
187
|
- Something she heard in one group may come up in another related group; the only barriers are "known privately" discretion, secrecy requests and credentials.
|
|
188
188
|
- Promises are only extracted during memory tidying: sessions with fewer than 40 piled-up messages are tidied that night, so an afternoon "tomorrow" is usually caught, while something about to happen in a few minutes may not be.
|
|
189
189
|
- What engineering can guarantee is structure: continuous, sourced, correctable, decided by her. Whether she "truly has a heart" cannot be accepted by tests; the tests check whether these behaviors actually happen.
|
|
190
|
-
|
|
191
|
-
Thoughts about each place are independent entries: impressions, wishes, preferences, questions or boundaries. During solitude, diaries or identity reviews she chooses whether to add, revise or let them go, retaining reasons and sources. Revising one leaves other entries intact. Copied legacy group personas remain history and no longer enter conversation; place thoughts do not increase overall personality scores.
|
|
190
|
+
|
|
191
|
+
Thoughts about each place are independent entries: impressions, wishes, preferences, questions or boundaries. During solitude, diaries or identity reviews she chooses whether to add, revise or let them go, retaining reasons and sources. Revising one leaves other entries intact. Copied legacy group personas remain history and no longer enter conversation; place thoughts do not increase overall personality scores.
|
package/docs/en/webui.md
CHANGED
|
@@ -71,7 +71,7 @@ Live messages follow new arrivals while you are at the bottom and keep their pla
|
|
|
71
71
|
- `npm run test:ui`: a separate temporary database and a simulated channel, nothing is sent to real groups; it covers the smoke flow (sessions, simulated messages, memory, models, both ways of connecting QQ, the guide page), the little world (mood themes and night, pinned theme, reduced motion, star-map undo, person details, diary and promises), navigation caching, the time page, performance, and the English interface (Chinese may only appear inside her own content), at widths 1440 / 820 / 390.
|
|
72
72
|
- `node tests/ui/ta.mjs --serve`: opens an example world that has lived three days, handy for looking at the interface directly.
|
|
73
73
|
- Screenshots go only into the ignored `workspace/` folder; real chat screenshots and configuration must never be committed.
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
System → Runtime checks npm manually, with the current version, last successful check and release notes. Results are cached for five minutes; failures retain the last known information. Update and restart explicitly downloads and checks the program, saves a backup and switches instances. Progress survives restart, the page waits for reconnection, and repeated clicks do not launch a second install.
|
|
76
76
|
|
|
77
77
|
People → Relationships binds people or other robots by account, using themed choices for type, reciprocal roles, name and visibility. Person details show change history; ending a relationship does not delete encounters. Summaries load on demand alongside existing chat caching.
|
package/docs/zh/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
<p align="right"><b>中文</b> · <a href="../en/CHANGELOG.md">English</a></p>
|
|
2
2
|
|
|
3
|
-
# 版本记录
|
|
4
|
-
|
|
5
|
-
## 1.0.
|
|
3
|
+
# 版本记录
|
|
4
|
+
|
|
5
|
+
## 1.0.7 — 2026-10-09
|
|
6
|
+
|
|
7
|
+
- 调整理解与表达顺序:先核对本轮原话、连续补充、说话人和引用,再决定回应。被纠正时修正具体问题,不用道歉、内心独白或抽象比喻替代答案。
|
|
8
|
+
- 临时话题观点不再自动占据长期自我的核心位置;一次回复不形成固定话术习惯。相关旧想法仍可召回,跨群连续记忆、来源和私密范围保留。
|
|
9
|
+
- 已知机器人不再参与人类群聊口吻统计;无新内容的机器人确认循环会收住。明确要求停止时不再一边答应闭嘴一边继续建议,新的问题仍能重新回应。
|
|
10
|
+
- 修复游戏内容展示抹去来源与不确定性的错误。当前活动和作品片段带回参考阅读与材料来源,模型整理不再被展示层改写为客户端操作或通关。
|
|
11
|
+
- 重写针对最新被退回的稿子;重复确认事实不会因去重而被吞掉。旧话矛盾的修复只用于相关追问,不插入无关问答。收尾、未理解问题和危机场景分别处理。
|
|
12
|
+
- 增加全量会话分段审阅、历史场景回放与连续对话评测工具。调用模型的检查需显式运行,原始聊天和报告仅留本地,不随发布包分发。
|
|
13
|
+
|
|
14
|
+
## 1.0.5 — 2026-10-07
|
|
6
15
|
|
|
7
16
|
- 修复人际关系已绑定却无法在另一段会话中认出对方的问题。聊天、主动表达、独处、日记和回顾从同一份关系记录按名字、账号与称呼召回,不要求关系对象正在说话。
|
|
8
17
|
- 区分当前关系、过去看法和自己的感受。旧的否认或玩笑保留在历史中,不能替代更新后的事实;关系相关回复发送前再次核对,保持自己的相处与称呼选择。
|
|
@@ -16,19 +25,19 @@
|
|
|
16
25
|
- 检查更新新增「更新并重启」:先安装校验,再保留完整数据恢复点并切换;启动失败尝试恢复旧程序和数据。源码、npm 安装、配置与生活数据保留,重复点击合并,启动命令接续已更新程序。
|
|
17
26
|
- 补充关系权限、同名对象、身份、失败恢复、真实安装切换与不同屏幕回归。校验清单先检查文件哈希,再解析数据库,损坏文件不会返回原始 SQLite 结构错误。
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
## 1.0.3 — 2026-10-06
|
|
21
|
-
|
|
22
|
-
- 群人格改为「在这里的想法」:她根据各处真实相处选择性新增、修改或放下印象、愿望、偏好、疑问和分寸,保留理由、来源和版本。整体人格仍统一,复制来的旧正文仅留在历史中。
|
|
23
|
-
- 系统运行设置新增手动检查更新,显示当前版本、npm 最新版本及发布说明;有缓存、并发合并和失败重试,不自动安装或重启。
|
|
24
|
-
- 天性修改额度由服务端统一提供,支持有记录、幂等的本地额外机会,不删除原人格版本,不改变其他实例默认限制。
|
|
25
|
-
- 补充跨会话来源、选择性更新、重启持久化、更新失败恢复,以及桌面、平板和手机界面的回归检查。
|
|
26
|
-
|
|
27
|
-
## 1.0.2 — 2026-10-06
|
|
28
|
-
|
|
29
|
-
- 数据导入导出:流式导出完整 SQLite 数据包,导入前校验并预览;替换前保留恢复点,失败可回滚。
|
|
30
|
-
- 保留目标实例的管理登录和连接设置;导入后先关闭自动回复与插件授权,供管理员重新确认。临时包支持取消、过期和重启回收。
|
|
31
|
-
|
|
28
|
+
|
|
29
|
+
## 1.0.3 — 2026-10-06
|
|
30
|
+
|
|
31
|
+
- 群人格改为「在这里的想法」:她根据各处真实相处选择性新增、修改或放下印象、愿望、偏好、疑问和分寸,保留理由、来源和版本。整体人格仍统一,复制来的旧正文仅留在历史中。
|
|
32
|
+
- 系统运行设置新增手动检查更新,显示当前版本、npm 最新版本及发布说明;有缓存、并发合并和失败重试,不自动安装或重启。
|
|
33
|
+
- 天性修改额度由服务端统一提供,支持有记录、幂等的本地额外机会,不删除原人格版本,不改变其他实例默认限制。
|
|
34
|
+
- 补充跨会话来源、选择性更新、重启持久化、更新失败恢复,以及桌面、平板和手机界面的回归检查。
|
|
35
|
+
|
|
36
|
+
## 1.0.2 — 2026-10-06
|
|
37
|
+
|
|
38
|
+
- 数据导入导出:流式导出完整 SQLite 数据包,导入前校验并预览;替换前保留恢复点,失败可回滚。
|
|
39
|
+
- 保留目标实例的管理登录和连接设置;导入后先关闭自动回复与插件授权,供管理员重新确认。临时包支持取消、过期和重启回收。
|
|
40
|
+
|
|
32
41
|
## 1.0.1 — 2026-10-05
|
|
33
42
|
|
|
34
43
|
- 全局 npm 安装后直接运行 `luckytri`,服务端、WebUI、教程和内置插件一并安装;用户数据独立保存,更新和重装继续使用。
|
package/docs/zh/chat-style.md
CHANGED
|
@@ -12,18 +12,20 @@
|
|
|
12
12
|
- 对事情产生具体反应,不固定走「复述情绪、安慰、建议、追问」的流程。
|
|
13
13
|
- 吐槽不默认要建议;分享好消息不套负面安慰;真实难过时收起玩笑。
|
|
14
14
|
- 对话可以结束,不用每轮问「然后呢」,也不把每条消息都改写成段子。
|
|
15
|
+
- 先说清具体的人、事和动作,不把普通聊天改写成「账、落款、拎着」之类的隐喻。真正讨论记账、文学或明确要求比喻时,仍按语境表达。
|
|
16
|
+
- 被指出抽象或复读时,回看自己的上一句,解释实际意思;没有明确意思就承认没说清,不只回「收到」。
|
|
15
17
|
- 记忆与当前话题有关才使用,不主动展示记忆档案;没看懂指代就澄清,不编经历。
|
|
16
18
|
- 不为显得年轻而强行用梗。严肃语境优先于风格。
|
|
17
19
|
|
|
18
20
|
下面是人工设计的方向示例,不是外部模型实测结果:
|
|
19
21
|
|
|
20
|
-
| 对方说
|
|
21
|
-
|
|
|
22
|
-
| 还有五分钟下班,老板又来活了 | 偏偏挑快下班的时候
|
|
23
|
-
| 复习的全没考
|
|
24
|
-
| 面试过了!!
|
|
25
|
-
| 别给建议,让我骂两句
|
|
26
|
-
| 我先去洗澡了
|
|
22
|
+
| 对方说 | 希望的表达方向 |
|
|
23
|
+
| ---------------------------- | ------------------------ |
|
|
24
|
+
| 还有五分钟下班,老板又来活了 | 偏偏挑快下班的时候 |
|
|
25
|
+
| 复习的全没考 | 真就一点没押中 |
|
|
26
|
+
| 面试过了!! | 可以啊!!这不得开心一下 |
|
|
27
|
+
| 别给建议,让我骂两句 | 回应具体烦心事,不再补方案 |
|
|
28
|
+
| 我先去洗澡了 | 去吧 |
|
|
27
29
|
|
|
28
30
|
## 这个地方的说话节奏
|
|
29
31
|
|
|
@@ -33,6 +35,14 @@
|
|
|
33
35
|
|
|
34
36
|
## 反馈
|
|
35
37
|
|
|
38
|
+
关系记录中标为机器人的账号不参与群口吻统计;它们的消息和共同经历仍完整保存。连续机器人交流中,已经互相确认、没有新问题或新内容时会收住。识别依赖账号关系记录,不根据昵称猜身份;新问题、纠正、具体事件和明确的创作请求仍可继续。确认循环不会额外生成情绪变化或关系升温。
|
|
39
|
+
|
|
40
|
+
长期的自我在一轮新话题里保持稳定;当前相关的旧想法放在 `inner.relatedSelf`,手记只在有话题关联时放入 `inner.onMind`,不为填满上下文补无关内容。临时台词不保存成固定回复习惯;已有记录保留,但固定话术不再作为表达倾向交给她。
|
|
41
|
+
|
|
42
|
+
一次聊天提取的观点仍在记录里,但不自动成为每轮必带的核心立场。来自聊天提取的习惯,需要跨日经历支持,才能重新进入表达上下文。`exchange` 在上下文末尾保留当前消息、引用链和近期自己的原话,便于核对指代与补充;`understanding` 只是一句可检查的理解摘要,不是不可更改的结论。
|
|
43
|
+
|
|
44
|
+
游戏活动中的 `reference` 表示阅读游戏内容并产生感受;`model` 材料表示未经独立核验的模型整理。它们都不证明实际操控过游戏客户端。展示层保留来源和不确定性,不要求每句聊天解释技术,也不会把记录改写成没有发生的亲历。
|
|
45
|
+
|
|
36
46
|
在「对话」里可以对她发出的某条回复反馈:挺自然、太长了、太端着、梗太多,也可以撤销。反馈不是改写规则:它作为她听到的话,在独处和这个会话接下来的回合里被她自己读到,不会改写天性。
|
|
37
47
|
|
|
38
48
|
## 发出前的检查
|
|
@@ -41,6 +51,7 @@
|
|
|
41
51
|
|
|
42
52
|
- 客服腔或咨询模板、列表报告式回复、角色动作;
|
|
43
53
|
- 无根据的亲密称呼、超过长度、复读、连续追问;
|
|
54
|
+
- 大部分内容相同、仅换词或加收尾的近期回复;明确要求引用原话或重复事实时保留;
|
|
44
55
|
- 忽略「不用建议」、严肃场合玩梗、网络词堆叠;
|
|
45
56
|
- 把自己先说过的话说成对方说的(「你刚说……」而对方的记录里没有);
|
|
46
57
|
- 说自己玩过、通关过什么(她没有这样的经历记录,喜好不是玩过的记录);
|
|
@@ -48,9 +59,9 @@
|
|
|
48
59
|
|
|
49
60
|
被问「你怎么知道」时,会先核对依据:自己先开口的话(对方在那之前隔了两小时以上没说话),依据只能是推测或真有的记忆,不能说成看到了对方发来的消息;重写后仍编造来源时,改发一句核实过的更正。
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
发现问题后,普通聊天最多重写两次;主动消息和预算紧张时最多一次。每次重写针对刚被退回的稿子,保留原意与事实。仍不合格时,没被叫到就选择不说;直接对话则坦率说明还没理清楚,避免用「嗯、好、收到」假装理解。不会简单截断长句或删掉不确定性。仅因反复使用 hh 句尾装饰被退回时,可去掉装饰后重新完整校验,保留有用的内容。
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
倾诉、纠正、危机、抽象的机器人交流、来源归属和实际能力声明等场景,可能对每稿进行复审,因此会增加延迟和费用;重写次数有界。检查是保守规则,不保证覆盖所有生硬或错误的表达;真实的自然程度仍受模型、天性和具体群语境影响。
|
|
54
65
|
|
|
55
66
|
## 怎么试
|
|
56
67
|
|
|
@@ -60,3 +71,13 @@
|
|
|
60
71
|
- **审计**:`npm run audit:sent` 把她最近发出的回复按今天的检查重放一遍,只读、不调用模型。改动检查规则之前先跑一次,能看见新规则会不会误伤。发出的回复当时已经过检查,所以这个数字用来评估新规则,不是她说话质量的评分。
|
|
61
72
|
|
|
62
73
|
实际验收时建议观察连续 10–20 轮,而不是单看一句:是否接住细节、是否频繁问问题、是否重复口头禅、是否抢别人的话、是否突然换成客服腔、是否把玩笑和认真难过混淆。自动化测试覆盖工程约束和人工场景样例,真实模型的语言质量仍要靠你自己的观察。
|
|
74
|
+
|
|
75
|
+
源码仓库还可运行 `node --env-file-if-exists=.env scripts/dev/evaluate-dialogue.js --limit 120`:读取本地真实回合快照,用当前默认模型经过理解、校验、复审和重写链路回放,再单独审查理解、指代、表达、来源与隐私。它会调用模型并产生费用;不会连接 QQ 或写入运行中的记忆。完整报告仅保存到本地 `data/evaluations/`。存在调用错误、未解决的问题或兜底回复时返回失败,不把它们统计成通过。单模型复审仍有判断局限,应结合具体输出检查。
|
|
76
|
+
|
|
77
|
+
更完整的源码验收工具:
|
|
78
|
+
|
|
79
|
+
- `node --env-file-if-exists=.env scripts/dev/audit-dialogue-corpus.js`:读取全部真实消息,按会话分段并保留前文重叠,输出覆盖清单与待核实的问题。某段看不到来源不代表一定编造,需继续查历史证据。
|
|
80
|
+
- 回放加 `--current-nature --rebuild-mind --review-model <模型档案ID>`:在内存副本上按历史时间重新筛选心智,保留当时的进度快照,用指定的另一模型评审。适合验证上下文筛选变化;不等于重现当时的外部服务与图片状态。
|
|
81
|
+
- `node --env-file-if-exists=.env scripts/dev/evaluate-dialogue-sequences.js --review-model <模型档案ID>`:新生成的回复进入下一轮,检查连续追问、纠正、停止、亲近表达、能力和来源是否前后一致。
|
|
82
|
+
|
|
83
|
+
这些命令都会产生模型费用。自动评审可能漏判、误判或引用错句,应查看完整输出;规则矩阵的组合数不能当成同等数量的独立语义场景,也不能据此保证所有未来回复都自然准确。
|
package/docs/zh/guide.md
CHANGED
|
@@ -200,11 +200,11 @@ TA 的一切都存在这一个数据库里,所以服务运行时会自动备
|
|
|
200
200
|
| 界面动得太多 | 左下角「减少动画」;系统开启了减少动态效果时会自动关闭粒子和形变 |
|
|
201
201
|
|
|
202
202
|
不要把 API 密钥、`.env` 或私聊原文发到公开位置。
|
|
203
|
-
|
|
204
|
-
### 群里的独特想法与检查更新
|
|
205
|
-
|
|
206
|
-
「人际 → 在这里的想法」保存她在各处自己想留下的印象、愿望或疑问。由她根据实际相处选择更新,详情显示理由、来源和历史;旧群人格只留在历史里。
|
|
207
|
-
|
|
203
|
+
|
|
204
|
+
### 群里的独特想法与检查更新
|
|
205
|
+
|
|
206
|
+
「人际 → 在这里的想法」保存她在各处自己想留下的印象、愿望或疑问。由她根据实际相处选择更新,详情显示理由、来源和历史;旧群人格只留在历史里。
|
|
207
|
+
|
|
208
208
|
「人际 → 彼此的关系」可按账号绑定人或另一位机器人,设置妹妹、姐姐、朋友等双向称呼、名字、缘由和公开范围。她在交流、独处、日记与行动选择中知道这份关系;信任和亲近仍依据实际相处,绑定不会制造共同经历。修改和解除保留历史及原有记忆。私下关系只在与对方的私聊中使用,不按同名合并账号。
|
|
209
209
|
|
|
210
210
|
对方不在当前会话,也可以因名字或关系称呼被想起。当前关系、实际相处与自己的感受共同参与理解;关系更新前的看法作为历史保留,不再直接代表当前事实。相关回复发送前会再次核对。她仍选择如何相处和称呼,不机械播报关系字段。
|
package/docs/zh/her-life.md
CHANGED
|
@@ -187,5 +187,5 @@ flowchart TD
|
|
|
187
187
|
- 她在一个群听到的事,可能在另一个相关的群里提起;拦着的只有「私下知道」的分寸、保密请求和凭据。
|
|
188
188
|
- 约定只在整理记忆时提取:积压不到 40 条的会话会在当晚整理,所以当天下午说的「明天」通常能赶上,几分钟后就要发生的事可能来不及。
|
|
189
189
|
- 工程能保证的是结构:连续、有来源、可修正、由她自己决定。她是否「真的有心」无法用测试验收;这些测试检验的是这些行为有没有真的发生。
|
|
190
|
-
|
|
191
|
-
各处的想法以独立条目保存,可以是印象、愿望、偏好、疑问或相处分寸。她在独处、日记或身份回看时自己选择是否更新;新增、修改和放下都保留理由及来源,修改一条不替换其他条。复制的旧群人格只保留历史,不再进入交流;各处想法不单独增加整体人格刻度。
|
|
190
|
+
|
|
191
|
+
各处的想法以独立条目保存,可以是印象、愿望、偏好、疑问或相处分寸。她在独处、日记或身份回看时自己选择是否更新;新增、修改和放下都保留理由及来源,修改一条不替换其他条。复制的旧群人格只保留历史,不再进入交流;各处想法不单独增加整体人格刻度。
|
package/docs/zh/webui.md
CHANGED
|
@@ -71,7 +71,7 @@ studio-web/src/
|
|
|
71
71
|
- `npm run test:ui`:独立的临时数据库、模拟通道,不向真实群发送;覆盖冒烟(会话、模拟消息、记忆、模型、连接 QQ 两种方式、教程页)、小世界(心情主题与夜晚、固定主题、减少动画、星图撤销、人物详情、日记与约定)、导航缓存、时间页、性能与英文界面(中文只允许出现在她自己的内容里);宽度覆盖 1440 / 820 / 390。
|
|
72
72
|
- `node tests/ui/ta.mjs --serve`:打开一个过了三天的示例世界,方便直接看界面。
|
|
73
73
|
- 截图只放在被忽略的 `workspace/` 目录,真实聊天截图和配置不得提交。
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
系统 → 运行开关中的「版本与更新」支持手动检查 npm 最新版本,显示当前版本、上次成功检查与发布说明。五分钟内复用结果,断网时保留上次信息并显示失败。点击「更新并重启」才下载校验、留备份并切换实例,页面显示阶段,重启期间等待连接恢复;重复点击不产生第二次安装。
|
|
76
76
|
|
|
77
77
|
人际页的「彼此的关系」按账号绑定人或其他机器人,使用主题下拉框设置对象类型、双向称呼、名字与分寸;人物详情显示关系历史。解除关系不删除相处记录。列表摘要按需读取,正文与原有聊天缓存不受影响。
|
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{$ as e,Ct as t,E as n,Et as r,H as i,I as a,J as o,M as s,P as c,Tt as l,U as u,V as d,W as f,X as p,Y as m,Z as h,a as g,ct as _,dt as v,et as y,f as b,gt as x,ht as S,it as C,j as w,lt as T,m as E,n as D,nt as O,o as k,q as A,rt as j,s as M,t as N,ut as P,v as F,vt as I,wt as L,xt as R,yt as z,z as B}from"./Empty-CLyZkxR5.js";import{g as ee,y as V}from"./mind-Dtiw0-QK.js";import{c as H,f as U,h as W,m as G,n as K,o as te,p as q,u as J,y as ne}from"./index-DdYwC5ZU.js";import{t as re}from"./media-BEtnE0Gv.js";import{t as Y}from"./Field-BpaRwLI_.js";async function ie(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),E(`/core/events?`+n.toString())}async function ae(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),E(`/core/traces?`+n.toString())}async function X(e){return E(`/core/traces/`+e)}async function oe(e){return E(`/simulate`,`POST`,e)}async function se(e,t){return E(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function Z(e){return E(`/core/replay`,`POST`,e)}async function ce(e){return E(`/core/sessions`,`POST`,e)}async function le(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function ue(e,t){return E(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function de(e){return E(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function fe(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function pe(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function me(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var he={class:`session-list`},ge={class:`list-head`},Q=[`placeholder`,`aria-label`],_e={class:`rows scroll-pane`},ve=[`data-pick`,`aria-current`,`onClick`],ye={class:`who`},be=[`aria-label`],xe=[`title`],Se=[`checked`,`aria-label`,`onChange`],Ce={key:0,class:`muted empty-line`},we={key:1,class:`archive-list`},Te={class:`row`},Ee=[`data-restore-session`,`onClick`],De=[`data-delete-session`,`onClick`],Oe=D(O({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(n,{emit:i}){let s=n,c=i,u=R(``),m=A(()=>s.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(u.value.toLowerCase()))),g=A(()=>s.sessions.filter(e=>e.archived));return(i,s)=>(P(),h(`div`,he,[o(`div`,ge,[o(`h2`,null,[e(r(t(a)(`会话`))+` `,1),o(`small`,null,r(m.value.length),1)]),o(`button`,{class:`small primary`,onClick:s[0]||=e=>c(`add`)},r(t(a)(`+ 添加会话`)),1)]),z(o(`input`,{"onUpdate:modelValue":s[1]||=e=>u.value=e,type:`search`,placeholder:t(a)(`搜索名称或号码`),"aria-label":t(a)(`搜索会话`)},null,8,Q),[[d,u.value]]),o(`div`,_e,[(P(!0),h(f,null,v(m.value,e=>(P(),h(`div`,{key:e.id,class:L([`session-row`,{selected:e.id===n.selected,paused:!e.enabled}])},[o(`button`,{class:`row-main`,"data-pick":e.id,"aria-current":e.id===n.selected?`true`:void 0,onClick:t=>c(`select`,e.id)},[o(`span`,{class:`badge`,style:l({"--hue":t(U)(e.id)})},r(e.kind===`private`?t(a)(`私`):t(a)(`群`)),5),o(`span`,ye,[o(`b`,null,r(t(W)(e)),1),o(`small`,null,r(e.enabled?t(a)(`参与中`):t(a)(`已暂停`)),1)]),n.unread[e.id]?(P(),h(`span`,{key:0,class:`unread`,"aria-label":t(a)(`{v} 条没细看`,{v:n.unread[e.id]})},r(n.unread[e.id]),9,be)):p(``,!0)],8,ve),o(`label`,{class:`mini-switch`,title:e.enabled?t(a)(`暂停参与`):t(a)(`开启参与`)},[o(`input`,{type:`checkbox`,checked:e.enabled,"aria-label":t(a)(`参与:{v}`,{v:t(W)(e)}),onChange:t=>c(`toggle`,e)},null,40,Se)],8,xe)],2))),128)),m.value.length?p(``,!0):(P(),h(`p`,Ce,r(t(a)(`暂无匹配的会话。`)),1)),g.value.length?(P(),h(`details`,we,[o(`summary`,null,r(t(a)(`已归档 · {length}`,{length:g.value.length})),1),(P(!0),h(f,null,v(g.value,e=>(P(),h(`article`,{key:e.id},[o(`b`,null,r(t(W)(e)),1),o(`div`,Te,[o(`button`,{class:`small`,"data-restore-session":e.id,onClick:t=>c(`restore`,e.id)},r(t(a)(`恢复`)),9,Ee),o(`button`,{class:`small danger`,"data-delete-session":e.id,onClick:t=>c(`remove`,e.id)},r(t(a)(`永久删除`)),9,De)])]))),128))])):p(``,!0)])]))}}),[[`__scopeId`,`data-v-4c7c823f`]]),ke={class:`muted`},Ae={key:0,class:`notice`,role:`status`},je=[`pattern`,`inputmode`],Me=[`disabled`],Ne=O({__name:`AddSession`,props:{open:{type:Boolean},channel:{},ready:{type:Boolean}},emits:[`close`,`add`],setup(e,{emit:n}){let i=e,s=R(`group`),c=n,l=A(()=>i.channel===`qqbot`),d=A(()=>l.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function f(e){let t=e.target;c(`add`,{...Object.fromEntries(new FormData(t)),channel:i.channel})}return(n,i)=>(P(),m(te,{open:e.open,title:t(a)(`添加群聊 / 私聊`),eyebrow:t(a)(`NEW PLACE`),width:`440px`,onClose:i[1]||=e=>c(`close`)},{default:I(()=>[o(`form`,{id:`addSession`,class:`stack`,onSubmit:u(f,[`prevent`])},[o(`p`,ke,r(t(a)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),l.value&&!e.ready?(P(),h(`p`,Ae,r(t(a)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):p(``,!0),y(Y,{label:t(a)(`类型`)},{default:I(()=>[y(H,{modelValue:s.value,"onUpdate:modelValue":i[0]||=e=>s.value=e,name:`kind`,"aria-label":t(a)(`类型`),options:[{value:`group`,label:t(a)(`群聊`)},{value:`private`,label:t(a)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),y(Y,{label:l.value?t(a)(`群 openid / 用户 openid`):t(a)(`群号 / QQ 号`),hint:l.value?t(a)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):t(a)(`4–20 位数字`)},{default:I(()=>[o(`input`,{name:`id`,required:``,pattern:d.value,inputmode:l.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),y(Y,{label:t(a)(`显示名称`)},{default:I(()=>[...i[2]||=[o(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),o(`button`,{class:`primary`,disabled:l.value&&!e.ready},r(t(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=D(O({__name:`Transcript`,props:{session:{},sessionId:{},events:{},traces:{},decisions:{},status:{},demo:{type:Boolean},compact:{type:Boolean},sending:{type:Boolean}},emits:[`simulate`,`feedback`,`openList`,`openReplay`],setup(n,{expose:g,emit:_}){let b=n,x=_,S=c({glanced:w(`TA 扫了一眼`),silent:w(`TA 看了,没出声`),deferred:w(`TA 睡着了,醒来再看`)}),T=R(`10001`),E=R(``),D=R(!0),O=R(),k=R({}),j=()=>V(s(),{month:`long`,day:`numeric`,weekday:`short`}),F=A(()=>{let e=b.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=b.events[0]?.time??2**53-1;for(let n of b.traces)S[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=j().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 I(e){let t=String(e.payload.text||``);return b.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 ee(e){return b.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function W(e){if(k.value[e.seq]){let{[e.seq]:t,...n}=k.value;k.value=n;return}k.value={...k.value,[e.seq]:{loading:!0,detail:null}};let t=ee(e),n=null;try{n=t?await X(t.id):null}catch{n=null}k.value[e.seq]&&(k.value={...k.value,[e.seq]:{loading:!1,detail:n}})}async function G(){await C(),O.value?.focus()}function K(){E.value.trim()&&!b.sending&&(x(`simulate`,{userId:T.value,text:E.value,mentioned:D.value}),E.value=``)}return g({focusComposer:G}),(s,c)=>(P(),h(`section`,Pe,[o(`header`,Fe,[n.compact?(P(),h(`button`,{key:0,class:`icon-button`,"aria-label":t(a)(`全部会话`),title:t(a)(`全部会话`),onClick:c[0]||=e=>x(`openList`)},` ☰ `,8,Ie)):p(``,!0),n.session?(P(),h(`span`,{key:1,class:`badge`,style:l({"--hue":t(U)(n.session.id)})},r(n.session.kind===`private`?t(a)(`私`):t(a)(`群`)),5)):p(``,!0),o(`div`,Le,[o(`h2`,null,r(n.session?.name||t(a)(`选择一段对话`)),1),o(`span`,Re,r(n.status),1)]),o(`div`,ze,[o(`button`,{class:`small sim-button`,disabled:!n.sessionId,onClick:G},r(t(a)(`模拟消息`)),9,Be),o(`button`,{class:`small`,onClick:c[1]||=e=>x(`openReplay`)},r(t(a)(`回到那一刻`)),1)])]),o(`div`,Ve,[(P(!0),h(f,null,v(F.value,n=>(P(),h(f,{key:n.key},[n.type===`day`?(P(),h(`div`,He,[o(`span`,null,r(n.label),1)])):n.type===`divider`?(P(),h(`div`,{key:1,class:`divider`,"data-status":n.status},[o(`span`,null,r(t(S)[n.status])+r(n.count>1?` × ${n.count}`:``),1),n.reasons.length?(P(),h(`small`,We,r(n.reasons.slice(0,2).join(`;`)),1)):p(``,!0)],8,Ue)):(P(),h(`article`,{key:2,class:L([`message`,{bot:n.event.role===`assistant`,simulated:n.event.payload.simulated}])},[n.event.role===`assistant`?p(``,!0):(P(),h(`span`,{key:0,class:`avatar`,style:l({"--hue":t(U)(String(n.event.payload.userId||n.event.payload.name))}),"aria-hidden":`true`},r(t(q)(n.event.payload.name)),5)),o(`div`,Ge,[o(`div`,Ke,[o(`b`,null,r(n.event.payload.name),1),o(`time`,null,r(t(J)(n.event.time)),1),n.event.payload.simulated?(P(),h(`span`,qe,r(t(a)(`模拟`)),1)):p(``,!0)]),o(`p`,Je,r(n.event.payload.text),1),n.event.role===`assistant`?(P(),h(f,{key:0},[o(`button`,{class:`text-button why-toggle`,"aria-expanded":!!k.value[n.event.seq],onClick:e=>W(n.event)},r(k.value[n.event.seq]?t(a)(`收起`):t(a)(`为什么这么说`)),9,Ye),k.value[n.event.seq]?(P(),h(`div`,Xe,[k.value[n.event.seq].loading?(P(),h(`p`,Ze,r(t(a)(`正在翻当时的记录…`)),1)):(P(),h(f,{key:1},[k.value[n.event.seq].detail?.data?.decision?.appraisal?(P(),h(`p`,Qe,[o(`b`,null,r(t(a)(`TA 当时的理解:`)),1),e(r(k.value[n.event.seq].detail.data.decision.appraisal),1)])):p(``,!0),o(`p`,null,[o(`b`,null,r(t(a)(`理由:`)),1),e(r(k.value[n.event.seq].detail?.data?.reason||k.value[n.event.seq].detail?.data?.decision?.reason||I(n.event)?.reason||t(a)(`没有留下理由。`)),1)]),k.value[n.event.seq].detail?.data?.snapshot?.knowledge?.length?(P(),h(`p`,$e,[o(`b`,null,r(t(a)(`引用的资料:`)),1),e(r(k.value[n.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):p(``,!0),I(n.event)?(P(),h(`label`,et,[e(r(t(a)(`这句怎么样?`))+` `,1),y(H,{"model-value":I(n.event).feedback||``,"aria-label":t(a)(`这句怎么样?`),options:[{value:``,label:t(a)(`选择评价`)},...Object.entries(t(M).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>x(`feedback`,I(n.event).id,e)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):p(``,!0)],64))])):p(``,!0)],64)):p(``,!0)])],2))],64))),128)),n.events.length?p(``,!0):(P(),m(N,{key:0,title:n.sessionId?t(a)(`等待新的消息`):t(a)(`选择一段对话`),text:t(a)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),n.demo?(P(),h(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:u(K,[`prevent`])},[o(`div`,tt,[o(`span`,nt,r(t(a)(`模拟消息`)),1),o(`span`,rt,r(t(a)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),o(`div`,it,[o(`label`,at,[o(`span`,ot,r(t(a)(`体验用户 ID`)),1),z(o(`input`,{"onUpdate:modelValue":c[2]||=e=>T.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:t(a)(`体验用户 ID`)},null,8,st),[[d,T.value]])]),z(o(`textarea`,{ref_key:`composer`,ref:O,"onUpdate:modelValue":c[3]||=e=>E.value=e,name:`text`,rows:`1`,placeholder:t(a)(`以这位群友的身份说一句…`),required:``,onKeydown:i(u(K,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[d,E.value]]),o(`label`,lt,[z(o(`input`,{"onUpdate:modelValue":c[4]||=e=>D.value=e,type:`checkbox`},null,512),[[B,D.value]]),e(r(t(a)(`视为 @`)),1)]),o(`button`,{class:`primary`,disabled:n.sending||!n.sessionId},r(n.sending?t(a)(`TA 在想…`):t(a)(`发送模拟消息`)),9,ut)])],32)):(P(),h(`div`,dt,[o(`span`,ft,r(t(a)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),o(`button`,{class:`small`,onClick:c[5]||=e=>t(M).chatOpen=!0},r(t(a)(`和 TA 聊聊`)),1)]))]))}}),[[`__scopeId`,`data-v-2afb114f`]]),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=D(O({__name:`SessionPanel`,props:j({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:j([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(n,{emit:i}){let s=n,l=S(n,`settingsOpen`),m=S(n,`tab`),g=i,_=c([w(`近期细摘要`),w(`中期摘要`),w(`较早摘要`),w(`远期摘要`)]),b=R(0),C=A(()=>s.decisions.filter(e=>e.session_id===s.session?.id&&e.reply)),T=A(()=>Math.max(1,Math.ceil(C.value.length/$))),E=R(!1),D=R({});x(()=>s.session?.policy,e=>{D.value={...e||{}}},{immediate:!0,deep:!0}),x(()=>s.session?.id,()=>{b.value=0,E.value=!1});function O(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
|
|
1
|
+
import{$ as e,Ct as t,E as n,Et as r,H as i,I as a,J as o,M as s,P as c,Tt as l,U as u,V as d,W as f,X as p,Y as m,Z as h,a as g,ct as _,dt as v,et as y,f as b,gt as x,ht as S,it as C,j as w,lt as T,m as E,n as D,nt as O,o as k,q as A,rt as j,s as M,t as N,ut as P,v as F,vt as I,wt as L,xt as R,yt as z,z as B}from"./Empty-C7yzOBzy.js";import{g as ee,y as V}from"./mind-DtTVrW6Z.js";import{c as H,f as U,h as W,m as G,n as K,o as te,p as q,u as J,y as ne}from"./index-CyRtYxP3.js";import{t as re}from"./media-Bwtx8blz.js";import{t as Y}from"./Field-BjFv4Rfn.js";async function ie(e,t){let n=new URLSearchParams({session:e});return t!==void 0&&n.set(`after`,String(t)),E(`/core/events?`+n.toString())}async function ae(e,t={}){let n=new URLSearchParams({session:e});return t.limit&&n.set(`limit`,String(t.limit)),t.compact&&n.set(`compact`,`1`),E(`/core/traces?`+n.toString())}async function X(e){return E(`/core/traces/`+e)}async function oe(e){return E(`/simulate`,`POST`,e)}async function se(e,t){return E(`/decisions/`+e+`/feedback`,`POST`,{tag:t})}async function Z(e){return E(`/core/replay`,`POST`,e)}async function ce(e){return E(`/core/sessions`,`POST`,e)}async function le(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PATCH`,{enabled:t})}async function ue(e,t){return E(`/core/sessions/`+encodeURIComponent(e)+`/archive`,`PATCH`,{archived:t})}async function de(e){return E(`/core/sessions/`+encodeURIComponent(e),`DELETE`,{})}async function fe(e,t){return E(`/core/sessions/`+encodeURIComponent(e),`PUT`,t)}async function pe(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/summaries`)}async function me(e){return E(`/core/sessions/`+encodeURIComponent(e)+`/context`,`DELETE`,{})}var he={class:`session-list`},ge={class:`list-head`},Q=[`placeholder`,`aria-label`],_e={class:`rows scroll-pane`},ve=[`data-pick`,`aria-current`,`onClick`],ye={class:`who`},be=[`aria-label`],xe=[`title`],Se=[`checked`,`aria-label`,`onChange`],Ce={key:0,class:`muted empty-line`},we={key:1,class:`archive-list`},Te={class:`row`},Ee=[`data-restore-session`,`onClick`],De=[`data-delete-session`,`onClick`],Oe=D(O({__name:`SessionList`,props:{sessions:{},selected:{},unread:{}},emits:[`select`,`add`,`toggle`,`restore`,`remove`],setup(n,{emit:i}){let s=n,c=i,u=R(``),m=A(()=>s.sessions.filter(e=>!e.archived&&`${e.name} ${e.id}`.toLowerCase().includes(u.value.toLowerCase()))),g=A(()=>s.sessions.filter(e=>e.archived));return(i,s)=>(P(),h(`div`,he,[o(`div`,ge,[o(`h2`,null,[e(r(t(a)(`会话`))+` `,1),o(`small`,null,r(m.value.length),1)]),o(`button`,{class:`small primary`,onClick:s[0]||=e=>c(`add`)},r(t(a)(`+ 添加会话`)),1)]),z(o(`input`,{"onUpdate:modelValue":s[1]||=e=>u.value=e,type:`search`,placeholder:t(a)(`搜索名称或号码`),"aria-label":t(a)(`搜索会话`)},null,8,Q),[[d,u.value]]),o(`div`,_e,[(P(!0),h(f,null,v(m.value,e=>(P(),h(`div`,{key:e.id,class:L([`session-row`,{selected:e.id===n.selected,paused:!e.enabled}])},[o(`button`,{class:`row-main`,"data-pick":e.id,"aria-current":e.id===n.selected?`true`:void 0,onClick:t=>c(`select`,e.id)},[o(`span`,{class:`badge`,style:l({"--hue":t(U)(e.id)})},r(e.kind===`private`?t(a)(`私`):t(a)(`群`)),5),o(`span`,ye,[o(`b`,null,r(t(W)(e)),1),o(`small`,null,r(e.enabled?t(a)(`参与中`):t(a)(`已暂停`)),1)]),n.unread[e.id]?(P(),h(`span`,{key:0,class:`unread`,"aria-label":t(a)(`{v} 条没细看`,{v:n.unread[e.id]})},r(n.unread[e.id]),9,be)):p(``,!0)],8,ve),o(`label`,{class:`mini-switch`,title:e.enabled?t(a)(`暂停参与`):t(a)(`开启参与`)},[o(`input`,{type:`checkbox`,checked:e.enabled,"aria-label":t(a)(`参与:{v}`,{v:t(W)(e)}),onChange:t=>c(`toggle`,e)},null,40,Se)],8,xe)],2))),128)),m.value.length?p(``,!0):(P(),h(`p`,Ce,r(t(a)(`暂无匹配的会话。`)),1)),g.value.length?(P(),h(`details`,we,[o(`summary`,null,r(t(a)(`已归档 · {length}`,{length:g.value.length})),1),(P(!0),h(f,null,v(g.value,e=>(P(),h(`article`,{key:e.id},[o(`b`,null,r(t(W)(e)),1),o(`div`,Te,[o(`button`,{class:`small`,"data-restore-session":e.id,onClick:t=>c(`restore`,e.id)},r(t(a)(`恢复`)),9,Ee),o(`button`,{class:`small danger`,"data-delete-session":e.id,onClick:t=>c(`remove`,e.id)},r(t(a)(`永久删除`)),9,De)])]))),128))])):p(``,!0)])]))}}),[[`__scopeId`,`data-v-9b8dcbac`]]),ke={class:`muted`},Ae={key:0,class:`notice`,role:`status`},je=[`pattern`,`inputmode`],Me=[`disabled`],Ne=O({__name:`AddSession`,props:{open:{type:Boolean},channel:{},ready:{type:Boolean}},emits:[`close`,`add`],setup(e,{emit:n}){let i=e,s=R(`group`),c=n,l=A(()=>i.channel===`qqbot`),d=A(()=>l.value?`[0-9A-Za-z_\\-]{8,64}`:`\\d{4,20}`);function f(e){let t=e.target;c(`add`,{...Object.fromEntries(new FormData(t)),channel:i.channel})}return(n,i)=>(P(),m(te,{open:e.open,title:t(a)(`添加群聊 / 私聊`),eyebrow:t(a)(`NEW PLACE`),width:`440px`,onClose:i[1]||=e=>c(`close`)},{default:I(()=>[o(`form`,{id:`addSession`,class:`stack`,onSubmit:u(f,[`prevent`])},[o(`p`,ke,r(t(a)(`添加之后再决定要不要让 TA 参与;开不开口仍由 TA 自己决定。`)),1),l.value&&!e.ready?(P(),h(`p`,Ae,r(t(a)(`请先在「连接 QQ」里填写机器人的 AppID`)),1)):p(``,!0),y(Y,{label:t(a)(`类型`)},{default:I(()=>[y(H,{modelValue:s.value,"onUpdate:modelValue":i[0]||=e=>s.value=e,name:`kind`,"aria-label":t(a)(`类型`),options:[{value:`group`,label:t(a)(`群聊`)},{value:`private`,label:t(a)(`私聊`)}]},null,8,[`modelValue`,`aria-label`,`options`])]),_:1},8,[`label`]),y(Y,{label:l.value?t(a)(`群 openid / 用户 openid`):t(a)(`群号 / QQ 号`),hint:l.value?t(a)(`8–64 位字母、数字、下划线或短横线。官方平台的会话通常会在有人 @ 她或私聊她时自动出现,这里只用于提前登记。`):t(a)(`4–20 位数字`)},{default:I(()=>[o(`input`,{name:`id`,required:``,pattern:d.value,inputmode:l.value?`text`:`numeric`,autocomplete:`off`},null,8,je)]),_:1},8,[`label`,`hint`]),y(Y,{label:t(a)(`显示名称`)},{default:I(()=>[...i[2]||=[o(`input`,{name:`name`,required:``,maxlength:`100`,autocomplete:`off`},null,-1)]]),_:1},8,[`label`]),o(`button`,{class:`primary`,disabled:l.value&&!e.ready},r(t(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=D(O({__name:`Transcript`,props:{session:{},sessionId:{},events:{},traces:{},decisions:{},status:{},demo:{type:Boolean},compact:{type:Boolean},sending:{type:Boolean}},emits:[`simulate`,`feedback`,`openList`,`openReplay`],setup(n,{expose:g,emit:_}){let b=n,x=_,S=c({glanced:w(`TA 扫了一眼`),silent:w(`TA 看了,没出声`),deferred:w(`TA 睡着了,醒来再看`)}),T=R(`10001`),E=R(``),D=R(!0),O=R(),k=R({}),j=()=>V(s(),{month:`long`,day:`numeric`,weekday:`short`}),F=A(()=>{let e=b.events.map(e=>({type:`message`,key:`m`+e.seq,time:e.time,event:e})),t=b.events[0]?.time??2**53-1;for(let n of b.traces)S[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=j().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 I(e){let t=String(e.payload.text||``);return b.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 ee(e){return b.traces.filter(t=>t.status===`sent`&&t.time<=e.time+1e3).sort((e,t)=>t.time-e.time)[0]}async function W(e){if(k.value[e.seq]){let{[e.seq]:t,...n}=k.value;k.value=n;return}k.value={...k.value,[e.seq]:{loading:!0,detail:null}};let t=ee(e),n=null;try{n=t?await X(t.id):null}catch{n=null}k.value[e.seq]&&(k.value={...k.value,[e.seq]:{loading:!1,detail:n}})}async function G(){await C(),O.value?.focus()}function K(){E.value.trim()&&!b.sending&&(x(`simulate`,{userId:T.value,text:E.value,mentioned:D.value}),E.value=``)}return g({focusComposer:G}),(s,c)=>(P(),h(`section`,Pe,[o(`header`,Fe,[n.compact?(P(),h(`button`,{key:0,class:`icon-button`,"aria-label":t(a)(`全部会话`),title:t(a)(`全部会话`),onClick:c[0]||=e=>x(`openList`)},` ☰ `,8,Ie)):p(``,!0),n.session?(P(),h(`span`,{key:1,class:`badge`,style:l({"--hue":t(U)(n.session.id)})},r(n.session.kind===`private`?t(a)(`私`):t(a)(`群`)),5)):p(``,!0),o(`div`,Le,[o(`h2`,null,r(n.session?.name||t(a)(`选择一段对话`)),1),o(`span`,Re,r(n.status),1)]),o(`div`,ze,[o(`button`,{class:`small sim-button`,disabled:!n.sessionId,onClick:G},r(t(a)(`模拟消息`)),9,Be),o(`button`,{class:`small`,onClick:c[1]||=e=>x(`openReplay`)},r(t(a)(`回到那一刻`)),1)])]),o(`div`,Ve,[(P(!0),h(f,null,v(F.value,n=>(P(),h(f,{key:n.key},[n.type===`day`?(P(),h(`div`,He,[o(`span`,null,r(n.label),1)])):n.type===`divider`?(P(),h(`div`,{key:1,class:`divider`,"data-status":n.status},[o(`span`,null,r(t(S)[n.status])+r(n.count>1?` × ${n.count}`:``),1),n.reasons.length?(P(),h(`small`,We,r(n.reasons.slice(0,2).join(`;`)),1)):p(``,!0)],8,Ue)):(P(),h(`article`,{key:2,class:L([`message`,{bot:n.event.role===`assistant`,simulated:n.event.payload.simulated}])},[n.event.role===`assistant`?p(``,!0):(P(),h(`span`,{key:0,class:`avatar`,style:l({"--hue":t(U)(String(n.event.payload.userId||n.event.payload.name))}),"aria-hidden":`true`},r(t(q)(n.event.payload.name)),5)),o(`div`,Ge,[o(`div`,Ke,[o(`b`,null,r(n.event.payload.name),1),o(`time`,null,r(t(J)(n.event.time)),1),n.event.payload.simulated?(P(),h(`span`,qe,r(t(a)(`模拟`)),1)):p(``,!0)]),o(`p`,Je,r(n.event.payload.text),1),n.event.role===`assistant`?(P(),h(f,{key:0},[o(`button`,{class:`text-button why-toggle`,"aria-expanded":!!k.value[n.event.seq],onClick:e=>W(n.event)},r(k.value[n.event.seq]?t(a)(`收起`):t(a)(`为什么这么说`)),9,Ye),k.value[n.event.seq]?(P(),h(`div`,Xe,[k.value[n.event.seq].loading?(P(),h(`p`,Ze,r(t(a)(`正在翻当时的记录…`)),1)):(P(),h(f,{key:1},[k.value[n.event.seq].detail?.data?.decision?.appraisal?(P(),h(`p`,Qe,[o(`b`,null,r(t(a)(`TA 当时的理解:`)),1),e(r(k.value[n.event.seq].detail.data.decision.appraisal),1)])):p(``,!0),o(`p`,null,[o(`b`,null,r(t(a)(`理由:`)),1),e(r(k.value[n.event.seq].detail?.data?.reason||k.value[n.event.seq].detail?.data?.decision?.reason||I(n.event)?.reason||t(a)(`没有留下理由。`)),1)]),k.value[n.event.seq].detail?.data?.snapshot?.knowledge?.length?(P(),h(`p`,$e,[o(`b`,null,r(t(a)(`引用的资料:`)),1),e(r(k.value[n.event.seq].detail.data.snapshot.knowledge.map(e=>e.title).join(`、`)),1)])):p(``,!0),I(n.event)?(P(),h(`label`,et,[e(r(t(a)(`这句怎么样?`))+` `,1),y(H,{"model-value":I(n.event).feedback||``,"aria-label":t(a)(`这句怎么样?`),options:[{value:``,label:t(a)(`选择评价`)},...Object.entries(t(M).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>x(`feedback`,I(n.event).id,e)},null,8,[`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])):p(``,!0)],64))])):p(``,!0)],64)):p(``,!0)])],2))],64))),128)),n.events.length?p(``,!0):(P(),m(N,{key:0,title:n.sessionId?t(a)(`等待新的消息`):t(a)(`选择一段对话`),text:t(a)(`群聊与私聊消息会在这里实时出现;TA 扫一眼、没出声也会留下一条分隔线。`)},null,8,[`title`,`text`]))]),n.demo?(P(),h(`form`,{key:0,id:`simulate`,class:`composer`,onSubmit:u(K,[`prevent`])},[o(`div`,tt,[o(`span`,nt,r(t(a)(`模拟消息`)),1),o(`span`,rt,r(t(a)(`作为一条模拟消息走完整流程,写进模拟会话;不会发到 QQ,也不进入真实记忆。`)),1)]),o(`div`,it,[o(`label`,at,[o(`span`,ot,r(t(a)(`体验用户 ID`)),1),z(o(`input`,{"onUpdate:modelValue":c[2]||=e=>T.value=e,name:`userId`,pattern:`\\d{4,20}`,required:``,title:t(a)(`体验用户 ID`)},null,8,st),[[d,T.value]])]),z(o(`textarea`,{ref_key:`composer`,ref:O,"onUpdate:modelValue":c[3]||=e=>E.value=e,name:`text`,rows:`1`,placeholder:t(a)(`以这位群友的身份说一句…`),required:``,onKeydown:i(u(K,[`exact`,`prevent`]),[`enter`])},null,40,ct),[[d,E.value]]),o(`label`,lt,[z(o(`input`,{"onUpdate:modelValue":c[4]||=e=>D.value=e,type:`checkbox`},null,512),[[B,D.value]]),e(r(t(a)(`视为 @`)),1)]),o(`button`,{class:`primary`,disabled:n.sending||!n.sessionId},r(n.sending?t(a)(`TA 在想…`):t(a)(`发送模拟消息`)),9,ut)])],32)):(P(),h(`div`,dt,[o(`span`,ft,r(t(a)(`真实模式下不能模拟消息。想看看 TA 会怎么回,可以「和 TA 聊聊」——那是试聊,什么都不写入。`)),1),o(`button`,{class:`small`,onClick:c[5]||=e=>t(M).chatOpen=!0},r(t(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=D(O({__name:`SessionPanel`,props:j({session:{},group:{},summaries:{},decisions:{}},{settingsOpen:{type:Boolean,default:!1},settingsOpenModifiers:{},tab:{default:`here`},tabModifiers:{}}),emits:j([`toggle`,`archive`,`clear`,`save`,`feedback`],[`update:settingsOpen`,`update:tab`]),setup(n,{emit:i}){let s=n,l=S(n,`settingsOpen`),m=S(n,`tab`),g=i,_=c([w(`近期细摘要`),w(`中期摘要`),w(`较早摘要`),w(`远期摘要`)]),b=R(0),C=A(()=>s.decisions.filter(e=>e.session_id===s.session?.id&&e.reply)),T=A(()=>Math.max(1,Math.ceil(C.value.length/$))),E=R(!1),D=R({});x(()=>s.session?.policy,e=>{D.value={...e||{}}},{immediate:!0,deep:!0}),x(()=>s.session?.id,()=>{b.value=0,E.value=!1});function O(e){let t=e?.face;return t?[t.role,t.tone,t.content].filter(Boolean).join(`
|
|
2
2
|
|
|
3
|
-
`):``}function k(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;g(`save`,n)}return(i,s)=>(P(),h(`section`,mt,[y(K,{modelValue:m.value,"onUpdate:modelValue":s[0]||=e=>m.value=e,label:t(a)(`这个会话`),items:[{key:`here`,label:t(a)(`这个会话`)},{key:`feedback`,label:t(a)(`反馈`)}]},null,8,[`modelValue`,`label`,`items`]),n.session?m.value===`here`?(P(),h(`div`,gt,[o(`div`,_t,[o(`div`,vt,[o(`div`,null,[o(`span`,yt,r(t(a)(`TA 在这里的样子`)),1),O(n.group)?(P(),h(f,{key:0},[o(`p`,{class:L([`face-body`,E.value?`open`:`clamp`])},r(O(n.group)),3),O(n.group).length>96?(P(),h(`button`,{key:0,type:`button`,class:`text-button`,onClick:s[1]||=e=>E.value=!E.value},r(E.value?t(a)(`收起`):t(a)(`展开完整内容`)),1)):p(``,!0)],64)):(P(),h(`p`,bt,r(t(a)(`还没有形成在这里的样子。`)),1)),n.group?.face?.aspiration?(P(),h(`small`,xt,r(t(a)(`想成为:{aspiration}`,{aspiration:n.group.face.aspiration})),1)):p(``,!0)]),o(`div`,null,[o(`span`,St,r(n.session.kind===`private`?t(a)(`这段私聊给 TA 的感觉`):t(a)(`这个群给 TA 的感觉`)),1),o(`p`,null,r(n.group?.bond?.feel||t(a)(`刚来这里不久`)),1)])]),o(`p`,Ct,r(t(a)(`在这里开不开口由 TA 自己决定:没有参与概率和冷却。TA 对这里每个人的感觉在「人际」里看。`)),1),o(`div`,wt,[o(`button`,{id:`toggleSession`,"data-toggle":n.session.id,class:L({primary:!n.session.enabled}),onClick:s[2]||=e=>g(`toggle`,n.session)},r(n.session.enabled?t(a)(`暂停参与`):t(a)(`开启参与`)),11,Tt),o(`button`,{"data-archive":n.session.id,onClick:s[3]||=e=>g(`archive`,n.session.id)},r(t(a)(`归档`)),9,Et),o(`button`,{id:`clearLiveContext`,onClick:s[4]||=e=>g(`clear`)},r(t(a)(`清空上下文`)),1)]),o(`details`,{class:`fold session-settings`,open:l.value,onToggle:s[19]||=e=>l.value=e.target.open},[o(`summary`,null,r(t(a)(`会话设置`)),1),(P(),h(`form`,{key:n.session.id,"data-session":n.session.id,class:`stack tight`,onSubmit:u(k,[`prevent`]),onInput:s[17]||=e=>t(M).dirty=!0,onChange:s[18]||=e=>t(M).dirty=!0},[o(`p`,kt,r(t(a)(`所有会话和独处都用模型库里的默认模型;其余已启用的模型按列表顺序做备用。她在这个聊天窗里用什么样子,由她自己从经历里决定。`)),1),o(`label`,null,[e(r(t(a)(`会话名称`))+` `,1),o(`input`,{name:`name`,maxlength:`100`,autocomplete:`off`,value:n.session.name},null,8,At),o(`small`,jt,r(t(a)(`官方机器人平台不提供群名称,可以在这里自己起一个`)),1)]),o(`label`,Mt,[z(o(`input`,{name:`selectiveVision`,type:`checkbox`,"onUpdate:modelValue":s[5]||=e=>D.value.selectiveVision=e,onInput:s[6]||=u(()=>{},[`stop`])},null,544),[[B,D.value.selectiveVision]]),e(r(t(a)(`节能看图:只在被 @ 或明确要求时,看这一条和附近的图`)),1)]),o(`details`,Nt,[o(`summary`,null,r(t(a)(`高级策略`)),1),o(`div`,Pt,[o(`label`,null,[e(r(t(a)(`聚合窗口 ms`)),1),z(o(`input`,{name:`aggregateMs`,type:`number`,"onUpdate:modelValue":s[7]||=e=>D.value.aggregateMs=e},null,512),[[d,D.value.aggregateMs,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`最长等待 ms`)),1),z(o(`input`,{name:`maxWaitMs`,type:`number`,"onUpdate:modelValue":s[8]||=e=>D.value.maxWaitMs=e},null,512),[[d,D.value.maxWaitMs,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`近期原文条数(10–500)`)),1),z(o(`input`,{name:`contextMessages`,type:`number`,min:`10`,max:`500`,"onUpdate:modelValue":s[9]||=e=>D.value.contextMessages=e},null,512),[[d,D.value.contextMessages,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`一轮总字数`)),1),z(o(`input`,{name:`maxReply`,type:`number`,"onUpdate:modelValue":s[10]||=e=>D.value.maxReply=e},null,512),[[d,D.value.maxReply,void 0,{number:!0}]])]),o(`label`,Ft,[z(o(`input`,{name:`compaction`,type:`checkbox`,"onUpdate:modelValue":s[11]||=e=>D.value.compaction=e,onInput:s[12]||=u(()=>{},[`stop`])},null,544),[[B,D.value.compaction]]),e(r(t(a)(`上下文压缩:更早的聊天定期整理成分层摘要,越近越详细`)),1)]),o(`label`,It,[z(o(`input`,{name:`memory`,type:`checkbox`,"onUpdate:modelValue":s[13]||=e=>D.value.memory=e,onInput:s[14]||=u(()=>{},[`stop`])},null,544),[[B,D.value.memory]]),e(r(t(a)(`长期记忆`)),1)]),o(`label`,Lt,[z(o(`input`,{name:`deepCheck`,type:`checkbox`,"onUpdate:modelValue":s[15]||=e=>D.value.deepCheck=e,onInput:s[16]||=u(()=>{},[`stop`])},null,544),[[B,D.value.deepCheck]]),e(r(t(a)(`倾诉、纠正和危机时额外复审一次回复`)),1)])])]),o(`div`,Rt,[o(`button`,zt,r(t(a)(`保存会话设置`)),1),o(`small`,Bt,r(t(M).dirty?t(a)(`有未保存的修改`):t(a)(`保存后下一轮生效`)),1)])],40,Ot))],40,Dt),o(`details`,Vt,[o(`summary`,null,r(t(a)(`语境摘要 · {length} 段`,{length:n.summaries.length})),1),n.summaries.length?p(``,!0):(P(),h(`p`,Ht,r(t(a)(`还没有摘要。聊天超过近期原文条数后,更早的内容会自动整理到这里。`)),1)),(P(!0),h(f,null,v(n.summaries,e=>(P(),h(`article`,{key:e.id,class:`summary`},[o(`header`,null,[o(`b`,null,r(t(_)[e.level]||t(a)(`摘要`)),1),o(`small`,Ut,r(t(a)(`{period} · {messages} 条消息`,{period:e.period,messages:e.messages})),1)]),o(`p`,null,r(e.summary),1),e.keyPoints?.length?(P(),h(`ul`,Wt,[(P(!0),h(f,null,v(e.keyPoints,(e,n)=>(P(),h(`li`,{key:n},r(e.open?t(a)(`未完:`):``)+r(e.text),1))),128))])):p(``,!0)]))),128))])])])):(P(),h(`div`,Gt,[o(`p`,Kt,r(t(a)(`针对具体回复评价口吻;这是 TA 在这个会话里听到的话,不会改写天性。`)),1),o(`div`,qt,[(P(!0),h(f,null,v(C.value.slice(b.value*$,b.value*$+$),n=>(P(),h(`article`,{key:n.id,class:`feedback-item`},[o(`time`,Jt,r(t(J)(n.time)),1),o(`p`,Yt,r(n.reply),1),o(`label`,null,[e(r(t(a)(`这句回复怎么样?`))+` `,1),y(H,{"data-feedback":n.id,"model-value":n.feedback||``,"aria-label":t(a)(`这句回复怎么样?`),options:[{value:``,label:t(a)(`选择评价`)},...Object.entries(t(M).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>g(`feedback`,n.id,e)},null,8,[`data-feedback`,`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])]))),128)),C.value.length?p(``,!0):(P(),h(`p`,Xt,r(t(a)(`还没有可以评价的回复。`)),1))]),o(`div`,Zt,[o(`button`,{class:`small`,disabled:b.value===0,onClick:s[20]||=e=>b.value--},`←`,8,Qt),o(`span`,null,r(b.value+1)+` / `+r(T.value),1),o(`button`,{class:`small`,disabled:b.value+1>=T.value,onClick:s[21]||=e=>b.value++},` → `,8,$t)])])):(P(),h(`div`,ht,[y(N,{title:t(a)(`还没有选中会话`),text:t(a)(`从左边选一个群聊或私聊。`)},null,8,[`title`,`text`])]))]))}}),[[`__scopeId`,`data-v-bfb19c4d`]]),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=D(O({__name:`ReplaySheet`,props:{open:{type:Boolean},sessionId:{},events:{}},emits:[`close`,`load`],setup(n,{emit:i}){let s=n,l=i,g=c({sent:w(`开口了`),silent:w(`没出声`),glanced:w(`扫了一眼`),deferred:w(`睡着了`),running:w(`处理中`),error:w(`失败`),complete:w(`完成`),stale:w(`旧稿作废`),cancelled:w(`已取消`),interrupted:w(`已中断`)}),_=c({live:w(`真实聊天`),demo:w(`模拟预览`),replay:w(`测试回放`),memory:w(`记忆整理`),summary:w(`语境压缩`)}),y=R([]),b=R(null),S=R(``),C=R(``),T=R(!1),E=R(``);async function D(){s.sessionId&&(y.value=await ae(s.sessionId))}function O(){let e=s.events.map(e=>Number(e.seq));e.length&&(S.value||=String(Math.min(...e)),C.value||=String(Math.max(...e)))}async function k(){if(!T.value){T.value=!0;try{F(a(`回放正在运行`));let e=await Z({session:s.sessionId,from:Number(S.value),to:Number(C.value)});E.value=JSON.stringify(e,null,2),F(a(`回放结束`)),await D()}catch(e){F(e.message,!0)}finally{T.value=!1}}}async function A(e){b.value=await X(e),E.value=JSON.stringify(b.value,null,2)}function j(e){return e?a(`输入 {v}(缓存读 {v2} / 写 {v3})· 输出 {v4}`,{v:G(e.input),v2:G(e.cachedRead),v3:G(e.cacheWrite),v4:G(e.output)}):`—`}return x(()=>[s.open,s.sessionId],()=>{s.open&&(S.value=``,C.value=``,b.value=null,O(),D())},{immediate:!0}),x(()=>s.events.length,O),(i,s)=>(P(),m(te,{open:n.open,title:t(a)(`回到那一刻`),eyebrow:`REPLAY`,width:`1180px`,onClose:s[3]||=e=>l(`close`)},{default:I(()=>[o(`div`,tn,[o(`section`,nn,[o(`div`,rn,[o(`h3`,null,r(t(a)(`历史消息`)),1),o(`button`,{id:`loadMessages`,class:`small`,onClick:s[0]||=e=>{l(`load`),D()}},r(t(a)(`↻ 加载`)),1)]),o(`div`,an,[n.events.length?(P(),h(`table`,on,[o(`thead`,null,[o(`tr`,null,[o(`th`,null,r(t(a)(`序号`)),1),o(`th`,null,r(t(a)(`发送者`)),1),o(`th`,null,r(t(a)(`消息`)),1)])]),o(`tbody`,null,[(P(!0),h(f,null,v([...n.events].reverse(),e=>(P(),h(`tr`,{key:e.seq},[o(`td`,null,r(e.seq),1),o(`td`,null,r(e.payload.name),1),o(`td`,null,r(e.payload.text),1)]))),128))])])):(P(),h(`p`,sn,r(t(a)(`当前会话暂无历史消息。`)),1))]),o(`form`,{class:`range`,onSubmit:u(k,[`prevent`])},[o(`label`,null,[e(r(t(a)(`起始序号`)),1),z(o(`input`,{"onUpdate:modelValue":s[1]||=e=>S.value=e,name:`from`,type:`number`,required:``},null,512),[[d,S.value]])]),o(`label`,null,[e(r(t(a)(`截止序号`)),1),z(o(`input`,{"onUpdate:modelValue":s[2]||=e=>C.value=e,name:`to`,type:`number`,required:``},null,512),[[d,C.value]])]),o(`button`,{id:`replay`,class:`primary`,disabled:T.value||!n.events.length},r(T.value?t(a)(`正在回放…`):t(a)(`回放这段对话`)),9,cn),o(`p`,ln,r(t(a)(`隔离回放:只测试回复,不发送 QQ,不修改真实记忆和 TA 的心智。`)),1)],32)]),o(`section`,un,[o(`div`,dn,[o(`h3`,null,r(t(a)(`处理记录`)),1)]),o(`div`,fn,[(P(!0),h(f,null,v(y.value,e=>(P(),h(`button`,{key:e.id,"data-trace":e.id,class:L([`trace-row`,{selected:b.value?.id===e.id}]),onClick:t=>A(e.id)},[o(`span`,null,[o(`time`,null,r(t(J)(e.time)),1),o(`small`,{class:`chip`,"data-tone":e.status===`error`?`danger`:`quiet`},r(t(g)[e.status]||e.status),9,mn)]),o(`p`,null,r(e.reason||e.error||t(a)(`查看处理详情`)),1)],10,pn))),128)),y.value.length?p(``,!0):(P(),h(`p`,hn,r(t(a)(`有新的消息处理记录后会显示在这里。`)),1))])]),o(`section`,gn,[o(`div`,_n,[o(`h3`,null,r(t(a)(`运行详情`)),1)]),o(`div`,vn,[b.value?(P(),h(`div`,yn,[o(`span`,null,[e(r(t(a)(`模式`)),1),o(`b`,null,r(t(_)[b.value.mode]||b.value.mode),1)]),o(`span`,null,[e(r(t(a)(`耗时`)),1),o(`b`,null,r(b.value.data?.elapsed??`—`)+` ms`,1)]),o(`span`,null,[e(r(t(a)(`模型调用`)),1),o(`b`,null,r(t(a)(`{v} 次`,{v:b.value.data?.calls?.length??0})),1)]),o(`span`,bn,[s[4]||=e(`Token`,-1),o(`b`,null,r(j(b.value.data?.tokens)),1)])])):p(``,!0),b.value?(P(),h(`p`,xn,r(b.value.reason||b.value.data?.decision?.reason||t(a)(`展开下方查看上下文、模型输入与原始输出。`)),1)):p(``,!0),o(`pre`,Sn,r(E.value||t(a)(`选择一条处理记录,查看最终上下文、Prompt、原始输出、Token 与耗时。`)),1)])])])]),_:1},8,[`open`,`title`]))}}),[[`__scopeId`,`data-v-b267c5bc`]]),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=D(O({__name:`ChatsPage`,setup(e){let r=re(`(max-width: 1100px)`),i=A(()=>M.core?.sessions||[]),c=A(()=>i.value.filter(e=>!e.archived)),l=sessionStorage.activeSession,u=R(c.value.some(e=>e.id===l)?l:c.value[0]?.id||``),d=A(()=>c.value.find(e=>e.id===u.value)||null),f=R([]),v=R([]),S=R([]),w=R([]),E=R({}),D=R(``),O=R(!1),j=R(!1),N=R(!1),z=R(M.sub===`settings`),B=R(M.sub===`replay`),V=R(`here`),H=A(()=>M.health?.decisions||[]),U=A(()=>w.value.find(e=>e.session===u.value)||null),W=!1,G=!1,K=!1,q=``,J=0,Y=null,X=0;async function Z(e=!1){if(!u.value)return;if(W){G=!0,K||=e;return}let t=u.value,n=e||q!==t;W=!0;try{let[e,r]=await Promise.all([ie(t,n?void 0:J),ae(t,{limit:30,compact:!0})]);if(u.value!==t)return;let i=document.getElementById(`liveMessages`),o=q!==t||!i||i.scrollHeight-i.scrollTop-i.clientHeight<80;n?(f.value=e.reverse(),J=0):e.length&&(f.value=[...f.value,...e]);for(let t of e)J=Math.max(J,Number(t.seq)||0);!n&&e.length===100&&(G=!0),v.value=r,q=t,await C();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(W=!1,u.value!==t)G=!1,Z();else if(G){let e=K;G=!1,K=!1,Z(e)}}}async function he(){try{let[e,t]=await Promise.all([ee.bonds(),ee.overview()]);w.value=e.groups,E.value=Object.fromEntries(t.attention.map(e=>[e.session,e.unread]))}catch{}}x(u,e=>{if(Y?.abort(),e&&(sessionStorage.activeSession=e),f.value=[],v.value=[],S.value=[],q=``,J=0,G=!1,K=!1,N.value=!1,Z(),!e)return;pe(e).then(t=>{u.value===e&&(S.value=[...t].reverse())}).catch(()=>{});let t=new AbortController;Y=t;let n=``;ne(e=>{let t=Number(e?.messages);Number.isSafeInteger(t)&&t<J&&(q=``,J=0,f.value=[]);let r=JSON.stringify(e),i=n!==``&&r!==n;n=r,i&&Z()},{session:e,signal:t.signal,reconnectMs:1e3})},{immediate:!0}),x(c,e=>{e.some(e=>e.id===u.value)||(u.value=e[0]?.id||``)}),x(V,async e=>{if(e===`feedback`)try{M.health=await b()}catch{}}),x(B,e=>k(e?`replay`:z.value?`settings`:``)),x(()=>M.sub,e=>{e===`replay`&&(B.value=!0),e===`settings`&&(z.value=!0)}),x(()=>M.pulse,he),_(()=>{he(),X=window.setInterval(()=>Z(!0),15e3)}),T(()=>{clearInterval(X),Y?.abort()});async function ge(e){try{let t=await ce(e);await g(),u.value=t?.sessionId||`${e.kind}:${e.id}`,z.value=!0,V.value=`here`,j.value=!1,F(a(`会话已添加`))}catch(e){F(e.message,!0)}}async function Q(e){await le(e.id,!e.enabled),await g()}async function _e(e){await ue(e,!0),await g()}async function ve(e){await ue(e,!1),await g()}async function ye(e){await n(a(`永久删除这个会话?它的消息和设置都会消失。`),{title:a(`永久删除`),confirmText:a(`删除`),danger:!0})&&(await de(e),await g())}async function be(e){if(d.value)try{await fe(d.value.id,{...e,name:String(e.name||``).trim()||d.value.name}),M.dirty=!1,F(a(`会话配置已保存`)),await g()}catch(e){F(e.message,!0)}}async function xe(){u.value&&await n(a(`确定清空这个会话的消息上下文吗?长期记忆不会删除。`),{title:a(`清空上下文`),confirmText:a(`清空`),danger:!0})&&(await me(u.value),f.value=[],v.value=[],q=``,J=0,await Z(!0))}async function Se(e){if(!O.value&&u.value){O.value=!0;try{let t=await oe({sessionId:u.value,...e});F(t.reason||a(`已发送`)),M.health=await b(),await Z()}catch(e){F(e.message,!0)}finally{O.value=!1}}}async function Ce(e,t){try{await se(e,t),F(a(`反馈已保存`)),M.health=await b()}catch(e){F(e.message,!0)}}return(e,n)=>(P(),h(`div`,{class:L([`page chats`,{compact:t(r)}])},[t(r)?p(``,!0):(P(),h(`aside`,wn,[y(Oe,{sessions:i.value,selected:u.value,unread:E.value,onSelect:n[0]||=e=>u.value=e,onAdd:n[1]||=e=>j.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])),o(`div`,Tn,[y(pt,{"session-id":u.value,session:d.value,events:f.value,traces:v.value,decisions:H.value,status:D.value||t(a)(`请选择会话`),demo:!!t(M).health.settings.demo,compact:t(r),sending:O.value,onSimulate:Se,onFeedback:Ce,onOpenList:n[2]||=e=>N.value=!0,onOpenReplay:n[3]||=e=>B.value=!0},null,8,[`session-id`,`session`,`events`,`traces`,`decisions`,`status`,`demo`,`compact`,`sending`])]),o(`aside`,En,[y(en,{"settings-open":z.value,"onUpdate:settingsOpen":n[4]||=e=>z.value=e,tab:V.value,"onUpdate:tab":n[5]||=e=>V.value=e,session:d.value,group:U.value,summaries:S.value,decisions:H.value,onToggle:Q,onArchive:_e,onClear:xe,onSave:be,onFeedback:Ce},null,8,[`settings-open`,`tab`,`session`,`group`,`summaries`,`decisions`])]),t(r)?(P(),m(te,{key:1,open:N.value,title:t(a)(`全部会话`),eyebrow:`CHATS`,width:`400px`,onClose:n[8]||=e=>N.value=!1},{default:I(()=>[o(`div`,Dn,[y(Oe,{sessions:i.value,selected:u.value,unread:E.value,onSelect:n[6]||=e=>u.value=e,onAdd:n[7]||=e=>j.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])]),_:1},8,[`open`,`title`])):p(``,!0),y(Ne,{open:j.value,channel:t(M).health?.connection?.channel===`qqbot`?`qqbot`:`onebot`,ready:!!t(M).health?.connection?.qqbot?.appId,onClose:n[9]||=e=>j.value=!1,onAdd:ge},null,8,[`open`,`channel`,`ready`]),y(Cn,{open:B.value,"session-id":u.value,events:f.value,onClose:n[10]||=e=>B.value=!1,onLoad:n[11]||=e=>Z(!0)},null,8,[`open`,`session-id`,`events`])],2))}}),[[`__scopeId`,`data-v-a00218d5`]]);export{On as default};
|
|
3
|
+
`):``}function k(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;g(`save`,n)}return(i,s)=>(P(),h(`section`,mt,[y(K,{modelValue:m.value,"onUpdate:modelValue":s[0]||=e=>m.value=e,label:t(a)(`这个会话`),items:[{key:`here`,label:t(a)(`这个会话`)},{key:`feedback`,label:t(a)(`反馈`)}]},null,8,[`modelValue`,`label`,`items`]),n.session?m.value===`here`?(P(),h(`div`,gt,[o(`div`,_t,[o(`div`,vt,[o(`div`,null,[o(`span`,yt,r(t(a)(`TA 在这里的样子`)),1),O(n.group)?(P(),h(f,{key:0},[o(`p`,{class:L([`face-body`,E.value?`open`:`clamp`])},r(O(n.group)),3),O(n.group).length>96?(P(),h(`button`,{key:0,type:`button`,class:`text-button`,onClick:s[1]||=e=>E.value=!E.value},r(E.value?t(a)(`收起`):t(a)(`展开完整内容`)),1)):p(``,!0)],64)):(P(),h(`p`,bt,r(t(a)(`还没有形成在这里的样子。`)),1)),n.group?.face?.aspiration?(P(),h(`small`,xt,r(t(a)(`想成为:{aspiration}`,{aspiration:n.group.face.aspiration})),1)):p(``,!0)]),o(`div`,null,[o(`span`,St,r(n.session.kind===`private`?t(a)(`这段私聊给 TA 的感觉`):t(a)(`这个群给 TA 的感觉`)),1),o(`p`,null,r(n.group?.bond?.feel||t(a)(`刚来这里不久`)),1)])]),o(`p`,Ct,r(t(a)(`在这里开不开口由 TA 自己决定:没有参与概率和冷却。TA 对这里每个人的感觉在「人际」里看。`)),1),o(`div`,wt,[o(`button`,{id:`toggleSession`,"data-toggle":n.session.id,class:L({primary:!n.session.enabled}),onClick:s[2]||=e=>g(`toggle`,n.session)},r(n.session.enabled?t(a)(`暂停参与`):t(a)(`开启参与`)),11,Tt),o(`button`,{"data-archive":n.session.id,onClick:s[3]||=e=>g(`archive`,n.session.id)},r(t(a)(`归档`)),9,Et),o(`button`,{id:`clearLiveContext`,onClick:s[4]||=e=>g(`clear`)},r(t(a)(`清空上下文`)),1)]),o(`details`,{class:`fold session-settings`,open:l.value,onToggle:s[19]||=e=>l.value=e.target.open},[o(`summary`,null,r(t(a)(`会话设置`)),1),(P(),h(`form`,{key:n.session.id,"data-session":n.session.id,class:`stack tight`,onSubmit:u(k,[`prevent`]),onInput:s[17]||=e=>t(M).dirty=!0,onChange:s[18]||=e=>t(M).dirty=!0},[o(`p`,kt,r(t(a)(`所有会话和独处都用模型库里的默认模型;其余已启用的模型按列表顺序做备用。她在这个聊天窗里用什么样子,由她自己从经历里决定。`)),1),o(`label`,null,[e(r(t(a)(`会话名称`))+` `,1),o(`input`,{name:`name`,maxlength:`100`,autocomplete:`off`,value:n.session.name},null,8,At),o(`small`,jt,r(t(a)(`官方机器人平台不提供群名称,可以在这里自己起一个`)),1)]),o(`label`,Mt,[z(o(`input`,{name:`selectiveVision`,type:`checkbox`,"onUpdate:modelValue":s[5]||=e=>D.value.selectiveVision=e,onInput:s[6]||=u(()=>{},[`stop`])},null,544),[[B,D.value.selectiveVision]]),e(r(t(a)(`节能看图:只在被 @ 或明确要求时,看这一条和附近的图`)),1)]),o(`details`,Nt,[o(`summary`,null,r(t(a)(`高级策略`)),1),o(`div`,Pt,[o(`label`,null,[e(r(t(a)(`聚合窗口 ms`)),1),z(o(`input`,{name:`aggregateMs`,type:`number`,"onUpdate:modelValue":s[7]||=e=>D.value.aggregateMs=e},null,512),[[d,D.value.aggregateMs,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`最长等待 ms`)),1),z(o(`input`,{name:`maxWaitMs`,type:`number`,"onUpdate:modelValue":s[8]||=e=>D.value.maxWaitMs=e},null,512),[[d,D.value.maxWaitMs,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`近期原文条数(10–500)`)),1),z(o(`input`,{name:`contextMessages`,type:`number`,min:`10`,max:`500`,"onUpdate:modelValue":s[9]||=e=>D.value.contextMessages=e},null,512),[[d,D.value.contextMessages,void 0,{number:!0}]])]),o(`label`,null,[e(r(t(a)(`一轮总字数`)),1),z(o(`input`,{name:`maxReply`,type:`number`,"onUpdate:modelValue":s[10]||=e=>D.value.maxReply=e},null,512),[[d,D.value.maxReply,void 0,{number:!0}]])]),o(`label`,Ft,[z(o(`input`,{name:`compaction`,type:`checkbox`,"onUpdate:modelValue":s[11]||=e=>D.value.compaction=e,onInput:s[12]||=u(()=>{},[`stop`])},null,544),[[B,D.value.compaction]]),e(r(t(a)(`上下文压缩:更早的聊天定期整理成分层摘要,越近越详细`)),1)]),o(`label`,It,[z(o(`input`,{name:`memory`,type:`checkbox`,"onUpdate:modelValue":s[13]||=e=>D.value.memory=e,onInput:s[14]||=u(()=>{},[`stop`])},null,544),[[B,D.value.memory]]),e(r(t(a)(`长期记忆`)),1)]),o(`label`,Lt,[z(o(`input`,{name:`deepCheck`,type:`checkbox`,"onUpdate:modelValue":s[15]||=e=>D.value.deepCheck=e,onInput:s[16]||=u(()=>{},[`stop`])},null,544),[[B,D.value.deepCheck]]),e(r(t(a)(`倾诉、纠正和危机时额外复审一次回复`)),1)])])]),o(`div`,Rt,[o(`button`,zt,r(t(a)(`保存会话设置`)),1),o(`small`,Bt,r(t(M).dirty?t(a)(`有未保存的修改`):t(a)(`保存后下一轮生效`)),1)])],40,Ot))],40,Dt),o(`details`,Vt,[o(`summary`,null,r(t(a)(`语境摘要 · {length} 段`,{length:n.summaries.length})),1),n.summaries.length?p(``,!0):(P(),h(`p`,Ht,r(t(a)(`还没有摘要。聊天超过近期原文条数后,更早的内容会自动整理到这里。`)),1)),(P(!0),h(f,null,v(n.summaries,e=>(P(),h(`article`,{key:e.id,class:`summary`},[o(`header`,null,[o(`b`,null,r(t(_)[e.level]||t(a)(`摘要`)),1),o(`small`,Ut,r(t(a)(`{period} · {messages} 条消息`,{period:e.period,messages:e.messages})),1)]),o(`p`,null,r(e.summary),1),e.keyPoints?.length?(P(),h(`ul`,Wt,[(P(!0),h(f,null,v(e.keyPoints,(e,n)=>(P(),h(`li`,{key:n},r(e.open?t(a)(`未完:`):``)+r(e.text),1))),128))])):p(``,!0)]))),128))])])])):(P(),h(`div`,Gt,[o(`p`,Kt,r(t(a)(`针对具体回复评价口吻;这是 TA 在这个会话里听到的话,不会改写天性。`)),1),o(`div`,qt,[(P(!0),h(f,null,v(C.value.slice(b.value*$,b.value*$+$),n=>(P(),h(`article`,{key:n.id,class:`feedback-item`},[o(`time`,Jt,r(t(J)(n.time)),1),o(`p`,Yt,r(n.reply),1),o(`label`,null,[e(r(t(a)(`这句回复怎么样?`))+` `,1),y(H,{"data-feedback":n.id,"model-value":n.feedback||``,"aria-label":t(a)(`这句回复怎么样?`),options:[{value:``,label:t(a)(`选择评价`)},...Object.entries(t(M).health.feedbackLabels||{}).map(([e,t])=>({value:e,label:String(t)}))],"onUpdate:modelValue":e=>g(`feedback`,n.id,e)},null,8,[`data-feedback`,`model-value`,`aria-label`,`options`,`onUpdate:modelValue`])])]))),128)),C.value.length?p(``,!0):(P(),h(`p`,Xt,r(t(a)(`还没有可以评价的回复。`)),1))]),o(`div`,Zt,[o(`button`,{class:`small`,disabled:b.value===0,onClick:s[20]||=e=>b.value--},`←`,8,Qt),o(`span`,null,r(b.value+1)+` / `+r(T.value),1),o(`button`,{class:`small`,disabled:b.value+1>=T.value,onClick:s[21]||=e=>b.value++},` → `,8,$t)])])):(P(),h(`div`,ht,[y(N,{title:t(a)(`还没有选中会话`),text:t(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=D(O({__name:`ReplaySheet`,props:{open:{type:Boolean},sessionId:{},events:{}},emits:[`close`,`load`],setup(n,{emit:i}){let s=n,l=i,g=c({sent:w(`开口了`),silent:w(`没出声`),glanced:w(`扫了一眼`),deferred:w(`睡着了`),running:w(`处理中`),error:w(`失败`),complete:w(`完成`),stale:w(`旧稿作废`),cancelled:w(`已取消`),interrupted:w(`已中断`)}),_=c({live:w(`真实聊天`),demo:w(`模拟预览`),replay:w(`测试回放`),memory:w(`记忆整理`),summary:w(`语境压缩`)}),y=R([]),b=R(null),S=R(``),C=R(``),T=R(!1),E=R(``);async function D(){s.sessionId&&(y.value=await ae(s.sessionId))}function O(){let e=s.events.map(e=>Number(e.seq));e.length&&(S.value||=String(Math.min(...e)),C.value||=String(Math.max(...e)))}async function k(){if(!T.value){T.value=!0;try{F(a(`回放正在运行`));let e=await Z({session:s.sessionId,from:Number(S.value),to:Number(C.value)});E.value=JSON.stringify(e,null,2),F(a(`回放结束`)),await D()}catch(e){F(e.message,!0)}finally{T.value=!1}}}async function A(e){b.value=await X(e),E.value=JSON.stringify(b.value,null,2)}function j(e){return e?a(`输入 {v}(缓存读 {v2} / 写 {v3})· 输出 {v4}`,{v:G(e.input),v2:G(e.cachedRead),v3:G(e.cacheWrite),v4:G(e.output)}):`—`}return x(()=>[s.open,s.sessionId],()=>{s.open&&(S.value=``,C.value=``,b.value=null,O(),D())},{immediate:!0}),x(()=>s.events.length,O),(i,s)=>(P(),m(te,{open:n.open,title:t(a)(`回到那一刻`),eyebrow:`REPLAY`,width:`1180px`,onClose:s[3]||=e=>l(`close`)},{default:I(()=>[o(`div`,tn,[o(`section`,nn,[o(`div`,rn,[o(`h3`,null,r(t(a)(`历史消息`)),1),o(`button`,{id:`loadMessages`,class:`small`,onClick:s[0]||=e=>{l(`load`),D()}},r(t(a)(`↻ 加载`)),1)]),o(`div`,an,[n.events.length?(P(),h(`table`,on,[o(`thead`,null,[o(`tr`,null,[o(`th`,null,r(t(a)(`序号`)),1),o(`th`,null,r(t(a)(`发送者`)),1),o(`th`,null,r(t(a)(`消息`)),1)])]),o(`tbody`,null,[(P(!0),h(f,null,v([...n.events].reverse(),e=>(P(),h(`tr`,{key:e.seq},[o(`td`,null,r(e.seq),1),o(`td`,null,r(e.payload.name),1),o(`td`,null,r(e.payload.text),1)]))),128))])])):(P(),h(`p`,sn,r(t(a)(`当前会话暂无历史消息。`)),1))]),o(`form`,{class:`range`,onSubmit:u(k,[`prevent`])},[o(`label`,null,[e(r(t(a)(`起始序号`)),1),z(o(`input`,{"onUpdate:modelValue":s[1]||=e=>S.value=e,name:`from`,type:`number`,required:``},null,512),[[d,S.value]])]),o(`label`,null,[e(r(t(a)(`截止序号`)),1),z(o(`input`,{"onUpdate:modelValue":s[2]||=e=>C.value=e,name:`to`,type:`number`,required:``},null,512),[[d,C.value]])]),o(`button`,{id:`replay`,class:`primary`,disabled:T.value||!n.events.length},r(T.value?t(a)(`正在回放…`):t(a)(`回放这段对话`)),9,cn),o(`p`,ln,r(t(a)(`隔离回放:只测试回复,不发送 QQ,不修改真实记忆和 TA 的心智。`)),1)],32)]),o(`section`,un,[o(`div`,dn,[o(`h3`,null,r(t(a)(`处理记录`)),1)]),o(`div`,fn,[(P(!0),h(f,null,v(y.value,e=>(P(),h(`button`,{key:e.id,"data-trace":e.id,class:L([`trace-row`,{selected:b.value?.id===e.id}]),onClick:t=>A(e.id)},[o(`span`,null,[o(`time`,null,r(t(J)(e.time)),1),o(`small`,{class:`chip`,"data-tone":e.status===`error`?`danger`:`quiet`},r(t(g)[e.status]||e.status),9,mn)]),o(`p`,null,r(e.reason||e.error||t(a)(`查看处理详情`)),1)],10,pn))),128)),y.value.length?p(``,!0):(P(),h(`p`,hn,r(t(a)(`有新的消息处理记录后会显示在这里。`)),1))])]),o(`section`,gn,[o(`div`,_n,[o(`h3`,null,r(t(a)(`运行详情`)),1)]),o(`div`,vn,[b.value?(P(),h(`div`,yn,[o(`span`,null,[e(r(t(a)(`模式`)),1),o(`b`,null,r(t(_)[b.value.mode]||b.value.mode),1)]),o(`span`,null,[e(r(t(a)(`耗时`)),1),o(`b`,null,r(b.value.data?.elapsed??`—`)+` ms`,1)]),o(`span`,null,[e(r(t(a)(`模型调用`)),1),o(`b`,null,r(t(a)(`{v} 次`,{v:b.value.data?.calls?.length??0})),1)]),o(`span`,bn,[s[4]||=e(`Token`,-1),o(`b`,null,r(j(b.value.data?.tokens)),1)])])):p(``,!0),b.value?(P(),h(`p`,xn,r(b.value.reason||b.value.data?.decision?.reason||t(a)(`展开下方查看上下文、模型输入与原始输出。`)),1)):p(``,!0),o(`pre`,Sn,r(E.value||t(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=D(O({__name:`ChatsPage`,setup(e){let r=re(`(max-width: 1100px)`),i=A(()=>M.core?.sessions||[]),c=A(()=>i.value.filter(e=>!e.archived)),l=sessionStorage.activeSession,u=R(c.value.some(e=>e.id===l)?l:c.value[0]?.id||``),d=A(()=>c.value.find(e=>e.id===u.value)||null),f=R([]),v=R([]),S=R([]),w=R([]),E=R({}),D=R(``),O=R(!1),j=R(!1),N=R(!1),z=R(M.sub===`settings`),B=R(M.sub===`replay`),V=R(`here`),H=A(()=>M.health?.decisions||[]),U=A(()=>w.value.find(e=>e.session===u.value)||null),W=!1,G=!1,K=!1,q=``,J=0,Y=null,X=0;async function Z(e=!1){if(!u.value)return;if(W){G=!0,K||=e;return}let t=u.value,n=e||q!==t;W=!0;try{let[e,r]=await Promise.all([ie(t,n?void 0:J),ae(t,{limit:30,compact:!0})]);if(u.value!==t)return;let i=document.getElementById(`liveMessages`),o=q!==t||!i||i.scrollHeight-i.scrollTop-i.clientHeight<80;n?(f.value=e.reverse(),J=0):e.length&&(f.value=[...f.value,...e]);for(let t of e)J=Math.max(J,Number(t.seq)||0);!n&&e.length===100&&(G=!0),v.value=r,q=t,await C();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(W=!1,u.value!==t)G=!1,Z();else if(G){let e=K;G=!1,K=!1,Z(e)}}}async function he(){try{let[e,t]=await Promise.all([ee.bonds(),ee.overview()]);w.value=e.groups,E.value=Object.fromEntries(t.attention.map(e=>[e.session,e.unread]))}catch{}}x(u,e=>{if(Y?.abort(),e&&(sessionStorage.activeSession=e),f.value=[],v.value=[],S.value=[],q=``,J=0,G=!1,K=!1,N.value=!1,Z(),!e)return;pe(e).then(t=>{u.value===e&&(S.value=[...t].reverse())}).catch(()=>{});let t=new AbortController;Y=t;let n=``;ne(e=>{let t=Number(e?.messages);Number.isSafeInteger(t)&&t<J&&(q=``,J=0,f.value=[]);let r=JSON.stringify(e),i=n!==``&&r!==n;n=r,i&&Z()},{session:e,signal:t.signal,reconnectMs:1e3})},{immediate:!0}),x(c,e=>{e.some(e=>e.id===u.value)||(u.value=e[0]?.id||``)}),x(V,async e=>{if(e===`feedback`)try{M.health=await b()}catch{}}),x(B,e=>k(e?`replay`:z.value?`settings`:``)),x(()=>M.sub,e=>{e===`replay`&&(B.value=!0),e===`settings`&&(z.value=!0)}),x(()=>M.pulse,he),_(()=>{he(),X=window.setInterval(()=>Z(!0),15e3)}),T(()=>{clearInterval(X),Y?.abort()});async function ge(e){try{let t=await ce(e);await g(),u.value=t?.sessionId||`${e.kind}:${e.id}`,z.value=!0,V.value=`here`,j.value=!1,F(a(`会话已添加`))}catch(e){F(e.message,!0)}}async function Q(e){await le(e.id,!e.enabled),await g()}async function _e(e){await ue(e,!0),await g()}async function ve(e){await ue(e,!1),await g()}async function ye(e){await n(a(`永久删除这个会话?它的消息和设置都会消失。`),{title:a(`永久删除`),confirmText:a(`删除`),danger:!0})&&(await de(e),await g())}async function be(e){if(d.value)try{await fe(d.value.id,{...e,name:String(e.name||``).trim()||d.value.name}),M.dirty=!1,F(a(`会话配置已保存`)),await g()}catch(e){F(e.message,!0)}}async function xe(){u.value&&await n(a(`确定清空这个会话的消息上下文吗?长期记忆不会删除。`),{title:a(`清空上下文`),confirmText:a(`清空`),danger:!0})&&(await me(u.value),f.value=[],v.value=[],q=``,J=0,await Z(!0))}async function Se(e){if(!O.value&&u.value){O.value=!0;try{let t=await oe({sessionId:u.value,...e});F(t.reason||a(`已发送`)),M.health=await b(),await Z()}catch(e){F(e.message,!0)}finally{O.value=!1}}}async function Ce(e,t){try{await se(e,t),F(a(`反馈已保存`)),M.health=await b()}catch(e){F(e.message,!0)}}return(e,n)=>(P(),h(`div`,{class:L([`page chats`,{compact:t(r)}])},[t(r)?p(``,!0):(P(),h(`aside`,wn,[y(Oe,{sessions:i.value,selected:u.value,unread:E.value,onSelect:n[0]||=e=>u.value=e,onAdd:n[1]||=e=>j.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])),o(`div`,Tn,[y(pt,{"session-id":u.value,session:d.value,events:f.value,traces:v.value,decisions:H.value,status:D.value||t(a)(`请选择会话`),demo:!!t(M).health.settings.demo,compact:t(r),sending:O.value,onSimulate:Se,onFeedback:Ce,onOpenList:n[2]||=e=>N.value=!0,onOpenReplay:n[3]||=e=>B.value=!0},null,8,[`session-id`,`session`,`events`,`traces`,`decisions`,`status`,`demo`,`compact`,`sending`])]),o(`aside`,En,[y(en,{"settings-open":z.value,"onUpdate:settingsOpen":n[4]||=e=>z.value=e,tab:V.value,"onUpdate:tab":n[5]||=e=>V.value=e,session:d.value,group:U.value,summaries:S.value,decisions:H.value,onToggle:Q,onArchive:_e,onClear:xe,onSave:be,onFeedback:Ce},null,8,[`settings-open`,`tab`,`session`,`group`,`summaries`,`decisions`])]),t(r)?(P(),m(te,{key:1,open:N.value,title:t(a)(`全部会话`),eyebrow:`CHATS`,width:`400px`,onClose:n[8]||=e=>N.value=!1},{default:I(()=>[o(`div`,Dn,[y(Oe,{sessions:i.value,selected:u.value,unread:E.value,onSelect:n[6]||=e=>u.value=e,onAdd:n[7]||=e=>j.value=!0,onToggle:Q,onRestore:ve,onRemove:ye},null,8,[`sessions`,`selected`,`unread`])])]),_:1},8,[`open`,`title`])):p(``,!0),y(Ne,{open:j.value,channel:t(M).health?.connection?.channel===`qqbot`?`qqbot`:`onebot`,ready:!!t(M).health?.connection?.qqbot?.appId,onClose:n[9]||=e=>j.value=!1,onAdd:ge},null,8,[`open`,`channel`,`ready`]),y(Cn,{open:B.value,"session-id":u.value,events:f.value,onClose:n[10]||=e=>B.value=!1,onLoad:n[11]||=e=>Z(!0)},null,8,[`open`,`session-id`,`events`])],2))}}),[[`__scopeId`,`data-v-c328f9fe`]]);export{On as default};
|