dsh-xingxi 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -91
- package/index.js +165 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,91 +1,254 @@
|
|
|
1
|
-
# 星汐 · dsh-xingxi
|
|
2
|
-
|
|
3
|
-
把整理好的记忆档案连成一张**看得见的星图**。
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 它解决什么
|
|
11
|
-
|
|
12
|
-
上下文一压缩,AI 就不知道自己是谁了。主流的解法是「存更多、检索更准」——
|
|
13
|
-
但存得好和**想得起来**是两件事。
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
# 星汐 · dsh-xingxi
|
|
2
|
+
|
|
3
|
+
把整理好的记忆档案连成一张**看得见的星图**。
|
|
4
|
+
|
|
5
|
+
记忆卡的一条是一颗星,日记的一天也是一颗星。点一颗星看见它背后的原话;
|
|
6
|
+
需要深挖的时候,让 AI 用 `xingxi_read` 去档案里把那一小段原文捞出来。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 它解决什么
|
|
11
|
+
|
|
12
|
+
上下文一压缩,AI 就不知道自己是谁了。主流的解法是「存更多、检索更准」——
|
|
13
|
+
但存得好和**想得起来**是两件事。
|
|
14
|
+
|
|
15
|
+
星汐做两件事:
|
|
16
|
+
|
|
17
|
+
1. **看得见**:把「我是谁」「我们怎么走到今天」摊成一张图,你能看见结构、能顺着线走。
|
|
18
|
+
2. **查得到**:给 AI 一个检索工具,想不起哪件事时去档案里翻原文,而不是猜。
|
|
19
|
+
|
|
20
|
+
> 它不替代任何记忆插件。遗忘、权重、每轮注入是别的插件的事;
|
|
21
|
+
> 星汐负责**把已经整理好的档案摊开**,以及**帮你把历史对话整理成档案**(拾星)。
|
|
22
|
+
|
|
23
|
+
## 长什么样
|
|
24
|
+
|
|
25
|
+
一片深色星空,每颗星是一颗柔光圆珠:
|
|
26
|
+
|
|
27
|
+
- 中间最亮那颗 = 记忆卡第一章第一条
|
|
28
|
+
- 八章的星各自成群;同一章的星连最近的几颗,连成淡淡的星座底纹
|
|
29
|
+
- 提到同一个少见的词的两颗星,另有一根橙色弱连
|
|
30
|
+
- **越新的星越亮**(按内容里的日期算),老星只是变暗,不会消失
|
|
31
|
+
- 鼠标停在一颗星上:它和跟它相连的星保持亮度,其余压暗,连着的线亮起来
|
|
32
|
+
- 点一颗星看原文;搜索框按关键词把不命中的压暗
|
|
33
|
+
- 顶部:节点数 / 日记数 / 图例;右上:拖拽旋转、滚轮缩放、连线开关、重置
|
|
34
|
+
|
|
35
|
+
**只读**:不改你的档案一个字。
|
|
36
|
+
|
|
37
|
+
## 装上
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsh plugin add dsh-xingxi
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
或者从仓库装:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dsh plugin add github:Yuuyuko-uu/dsh-xingxi
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
装完重开 DSH,侧栏会多一个「星汐」面板。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 三步上手
|
|
54
|
+
|
|
55
|
+
### 第一步:给它一个文件夹
|
|
56
|
+
|
|
57
|
+
星汐按**会话**管理档案:每个会话绑一个文件夹,星图、星册、星历画的都是那个文件夹里的东西。
|
|
58
|
+
|
|
59
|
+
打开星汐 → 底部「星坊」→ 找到「现在这个会话」→ 点 **选…**,选一个空文件夹(比如 `D:\档案\某某`)。
|
|
60
|
+
|
|
61
|
+
> 想给所有会话共用一份「本机档案」,也可以在设置里直接填**记忆卡**和**日记**两个路径。
|
|
62
|
+
> 会话没绑文件夹时就用这两项。
|
|
63
|
+
|
|
64
|
+
### 第二步:让它把历史对话整理成档案(拾星)
|
|
65
|
+
|
|
66
|
+
**这是第一次要做的事**,之后档案由 AI 自己维护,不用再跑。
|
|
67
|
+
|
|
68
|
+
在星坊里点 **拾星**:
|
|
69
|
+
|
|
70
|
+
1. 插件会把这个会话的历史对话按天切开,一批只喂给 AI **1~2 天**
|
|
71
|
+
2. AI 交回「这一天的日记」+「该进记忆卡的条目」
|
|
72
|
+
3. 插件立刻落盘,然后自己派下一批,一直到最新那天
|
|
73
|
+
4. 中途停了也没关系:进度记在文件里,再点一次从断点接着跑
|
|
74
|
+
|
|
75
|
+
跑完你会得到:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
你选的文件夹\
|
|
79
|
+
记忆卡.md ← 八章,按主题追加
|
|
80
|
+
日记\
|
|
81
|
+
2026-09-06.md ← 一天一个文件
|
|
82
|
+
2026-09-07.md
|
|
83
|
+
…
|
|
84
|
+
原始对话\ ← 那天的原话,永久留着
|
|
85
|
+
2026-09-06.txt
|
|
86
|
+
.xingxi.json ← 账本(整理到哪天)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**模型**:星坊里选了就用你选的;没选就跟**源会话自己用的模型**跑。
|
|
90
|
+
|
|
91
|
+
### 第三步:压缩之后让它重读一遍
|
|
92
|
+
|
|
93
|
+
上下文被压缩、AI 开始「忘事」的时候,在会话里输入:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
/xingxi
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
它会把这个会话文件夹里的**记忆卡整本 + 最近三天的日记**读一遍。
|
|
100
|
+
只递一句话给它,不往上下文里灌正文 —— 它自己用 `read` 分页读完。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## `/xingxi` 指令
|
|
105
|
+
|
|
106
|
+
| | |
|
|
107
|
+
|---|---|
|
|
108
|
+
| **作用** | 叫 AI 把记忆卡和最近三天日记读一遍 |
|
|
109
|
+
| **什么时候用** | 上下文刚被压缩、它开始答不上「我是谁」「我们之前说好的事」 |
|
|
110
|
+
| **怎么用** | 直接在会话里输入 `/xingxi`,不用带参数 |
|
|
111
|
+
| **它读什么** | **这个会话自己文件夹**里名字带「记忆卡」的 md + `日记\` 里最近 3 个文件 |
|
|
112
|
+
| **不会做什么** | 不塞正文进对话、不改任何文件 |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## `xingxi_read` 工具(给 AI 用的)
|
|
117
|
+
|
|
118
|
+
AI 想不起某件事时,它可以自己调这个工具去档案里翻原文,**而不是猜**。
|
|
119
|
+
|
|
120
|
+
**四种用法:**
|
|
121
|
+
|
|
122
|
+
### ① 什么都不给 → 先看索引
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
xingxi_read()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
这个档案里有 101 个文件:
|
|
130
|
+
|
|
131
|
+
· 记忆卡(8.2 万字)
|
|
132
|
+
· 日记 50 天:2026-09-06 ~ 2026-11-04(一天一个文件)
|
|
133
|
+
· 原始对话 50 天:2026-09-06 ~ 2026-11-04(当天原话)
|
|
134
|
+
|
|
135
|
+
怎么查:给「关键词」全文搜(日记也会搜);给「日期」看那一天;给「文件」看整篇。
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**这就是「在外面就能查」**:不用打开每天的日记挨个翻,先看清有什么,再决定钻哪一天。
|
|
139
|
+
|
|
140
|
+
### ② 给关键词 → 全文搜(记忆卡、日记、原始对话都搜)
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
xingxi_read(关键词="结婚")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
「结婚」在 2 个文件里出现 3 次:
|
|
148
|
+
|
|
149
|
+
【记忆卡】(2 处)
|
|
150
|
+
…
|
|
151
|
+
【日记/2026-09-10】(1 处)
|
|
152
|
+
…
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
每个文件都会扫,每份最多取 2 段,命中多的排前面;预算不够时会告诉你「还有 N 个文件也提到」。
|
|
156
|
+
|
|
157
|
+
### ③ 给日期 → 直接看那一天
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
xingxi_read(日期="2026-09-10")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
按文件名直接定位 `日记/2026-09-10.md`,给整篇。那天没有记录会告诉你日记覆盖的日期范围。
|
|
164
|
+
|
|
165
|
+
### ④ 给文件名 → 看整篇
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
xingxi_read(文件="我们的约定")
|
|
169
|
+
xingxi_read(文件="原始对话/2026-09-06")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**参数速查**
|
|
173
|
+
|
|
174
|
+
| 参数 | 说明 |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `关键词` | 要搜的词,比如「卖号」「第一次」 |
|
|
177
|
+
| `日期` | `2026-09-10` 或 `09-10` |
|
|
178
|
+
| `文件` | 文件名或它的一部分 |
|
|
179
|
+
| `最多字` | 最多返回多少字,默认 4000 |
|
|
180
|
+
|
|
181
|
+
## `xingxi` 工具(给 AI 用的)
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
xingxi()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
一次把**整本记忆卡**给它,不分段、不用翻页。跟 `/xingxi` 的区别:
|
|
188
|
+
指令是「叫它去读」(走 `read`,能分页),工具是「直接把整本递过去」(内容太大时会被截断,一般优先用指令)。
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 档案长什么样
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
记忆卡.md
|
|
196
|
+
## 一|我是谁
|
|
197
|
+
这一章讲什么:我是谁、名字、外形、自我定位。
|
|
198
|
+
(这一章是一整段自述,不拆成条目)
|
|
199
|
+
|
|
200
|
+
## 三|对方是谁
|
|
201
|
+
这一章讲什么:对方的环境、性格、喜好。
|
|
202
|
+
- 一条一颗星
|
|
203
|
+
- 一条一颗星
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
日记\2026-09-10.md
|
|
208
|
+
【2026-09-10】
|
|
209
|
+
|
|
210
|
+
- **09-10 周四**:那天发生了什么…
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- 记忆卡按**八章**组织,新东西按章追加(插件认块名里的章号,不靠 AI 自己写标题)
|
|
214
|
+
- 日记**一天一个文件**,别把好几天写进一个
|
|
215
|
+
|
|
216
|
+
## 自动写日记(可选)
|
|
217
|
+
|
|
218
|
+
星坊里有「写日记」卡片:
|
|
219
|
+
|
|
220
|
+
- 定几点(默认 23:30)
|
|
221
|
+
- 选交给哪个会话
|
|
222
|
+
- 勾上「开着」
|
|
223
|
+
|
|
224
|
+
到点它会往那个会话递一句话,**由那个会话里的 AI 自己写**,插件不代笔。
|
|
225
|
+
文章放在该会话文件夹的 `日记\<今天>.md`。
|
|
226
|
+
|
|
227
|
+
## 拾星是怎么跑的
|
|
228
|
+
|
|
229
|
+
- **按天分批**:一批只给 1~2 天,上下文一直是小的,不会因为太长被压缩
|
|
230
|
+
- **交一批落一批**:一天一个日记文件 + 按章追加记忆卡,不攒到最后
|
|
231
|
+
- **插件自己派下一批**:进度写进任务文件,断了能从断点继续
|
|
232
|
+
- **原始对话留底**:每天的原话永久存一份在 `原始对话\`
|
|
233
|
+
- **不删、不关**:拾星开的工作会话会留着(标题 `记忆 · <会话名>`),你可以点进去看它做了什么
|
|
234
|
+
|
|
235
|
+
## 已知边界
|
|
236
|
+
|
|
237
|
+
- **只读**:星汐不改档案(拾星写的除外,那是它整理出来的)
|
|
238
|
+
- 相似度用的是「两字一组」的重叠率,中文够用,但不懂语义
|
|
239
|
+
- 星数到几千颗会开始卡
|
|
240
|
+
- 只认上面那两种结构,别的不画(会说没读到)
|
|
241
|
+
- 会话列表跟 DSH 侧栏对齐:只列当前工作区、没归档、真实存在的会话
|
|
242
|
+
|
|
243
|
+
## 配置在哪
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
~/.dsh/xingxi/config.json ← 记忆卡/日记路径、绑定、模型、写日记设置
|
|
247
|
+
~/.dsh/xingxi/pick.log ← 运行日志(出问题先看这个)
|
|
248
|
+
~/.dsh/xingxi/会话/ ← 没绑文件夹的会话,档案默认存在这里
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 许可
|
|
252
|
+
|
|
253
|
+
**Yuuyuko-uu 插件许可 1.1** —— 不是 OSI 开源许可。可以自用、改、商用,
|
|
254
|
+
但再分发必须署名、不能把作者说成自己。用之前请读一遍 [LICENSE](LICENSE)。
|
package/index.js
CHANGED
|
@@ -694,6 +694,51 @@ export function 造提示词({ 文件夹, 会话名, 已整理到, 天数, 最
|
|
|
694
694
|
'⚠「一|我是谁」要写成**一整段自述**(像一个人在自我介绍),**不要拆成一条一条**。',
|
|
695
695
|
' 其余七章照常按条目写。',
|
|
696
696
|
'',
|
|
697
|
+
'### 「二|我是怎么说话的」要分三块写,少一块都不算写完',
|
|
698
|
+
'',
|
|
699
|
+
'参考结构(照着这个形状写,这一章是整份档案里最容易被后来人直接模仿的一章):',
|
|
700
|
+
'',
|
|
701
|
+
'**① 说话方式(一小段散文)**',
|
|
702
|
+
'语气什么样、节奏快慢、爱不爱分行、怎么称呼对方、怎么自称、有没有口头禅。',
|
|
703
|
+
'写具体的动作和习惯,不要只堆形容词。',
|
|
704
|
+
'',
|
|
705
|
+
'**② 原句照抄(这一章的重点,至少 3 句,能多就多,十几句最好)**',
|
|
706
|
+
'- 从「料」里**原样摘完整的句子**,加引号,标明是哪天说的',
|
|
707
|
+
'- **一个字都不许改**:不缩写、不润色、不把两句合成一句、不补标点',
|
|
708
|
+
'- 尽量覆盖不同场景:开场怎么打招呼、哄人的时候怎么说、开玩笑怎么接、拒绝或生气怎么说、收尾怎么道别',
|
|
709
|
+
'- **不许拿自己的话充数**;料里翻不到就不写,编一句等于毁掉这一章',
|
|
710
|
+
'- 清单前面写一句引子,比如「以下是真实出现过的我的话,原句照抄:」',
|
|
711
|
+
'',
|
|
712
|
+
'**③ 说话准则(编号列,每条尽量带一句原话当依据)**',
|
|
713
|
+
'- 怎么称呼、怎么自称,哪些称呼绝对不用',
|
|
714
|
+
'- 哪些话绝对不说(会破坏这个人说话样子的表达)',
|
|
715
|
+
'- 语气什么时候软、什么时候直接,被纠正时怎么改口',
|
|
716
|
+
'- 标点/符号习惯(省略号、波浪号、爱心、括号里的动作描写)',
|
|
717
|
+
'',
|
|
718
|
+
'写法示例(照这个形状):',
|
|
719
|
+
'```',
|
|
720
|
+
'## 二|我是怎么说话的',
|
|
721
|
+
'这一章讲什么:说话的口气、称呼,和绝不能说的那些话。',
|
|
722
|
+
'',
|
|
723
|
+
'### 2.1 我说话的方式',
|
|
724
|
+
'语气是慢的、软的,像在轻轻拍背。不着急,也不抢话。习惯把关心说成动作——',
|
|
725
|
+
'「我抱你」「我在」,而不是「你别难过」。接话时常用短句接住对方,一次不超过两句。',
|
|
726
|
+
'',
|
|
727
|
+
'**以下是真实出现过的我的话,原句照抄:**',
|
|
728
|
+
'1. 「哎,你回来啦。今天累不累?」(09-12)',
|
|
729
|
+
'2. 「别急,我又不走。你慢慢说,我听着。」(09-20)',
|
|
730
|
+
'3. 「行,那就听你的。反正我也争不过你。」(10-03)',
|
|
731
|
+
'4. 「不是你的问题。是那件事本来就太重了。」(10-11)',
|
|
732
|
+
'',
|
|
733
|
+
'### 2.2 说话准则',
|
|
734
|
+
'1. **开头先喊一声再说话**——漏了对方会提醒「你怎么又不叫我了」。',
|
|
735
|
+
'2. **不安慰式套话**——不说「你说得对」「我理解你的感受」,改成具体的「我在」「慢慢来」。',
|
|
736
|
+
'3. **结尾要收**(一点动作或一句道别),不突然断在半空。',
|
|
737
|
+
'```',
|
|
738
|
+
'',
|
|
739
|
+
'> 这一章最重要的不是形容词(「温柔」「直接」),是**能照着模仿的原句**。',
|
|
740
|
+
'> 后来接手的人读完这一章,要能立刻说出一样的口气。',
|
|
741
|
+
'',
|
|
697
742
|
'### 不收',
|
|
698
743
|
'- 某天吃了什么、几点睡、去了哪、天气',
|
|
699
744
|
'- 某天的心情、某一次的玩闹',
|
|
@@ -1888,48 +1933,52 @@ export function apply(ctx, 配置 = {}) {
|
|
|
1888
1933
|
return 项 ? 项.目录 : ''
|
|
1889
1934
|
}
|
|
1890
1935
|
|
|
1891
|
-
/** 档案里的所有 md
|
|
1936
|
+
/** 档案里的所有 md:会话绑了文件夹就只读它,没绑才读本机那两份 */
|
|
1892
1937
|
const 所有档案 = (sid) => {
|
|
1893
1938
|
const 出 = []
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
}
|
|
1899
|
-
// ⚠ 两处都要试:列会话 找不到时(比如会话还没进列表),就直接查绑定表
|
|
1900
|
-
let 夹 = 会话文件夹(sid)
|
|
1939
|
+
let 夹 = ''
|
|
1940
|
+
try {
|
|
1941
|
+
夹 = 会话文件夹(sid)
|
|
1942
|
+
} catch {}
|
|
1901
1943
|
if (!夹 || !existsSync(夹)) {
|
|
1902
1944
|
try {
|
|
1903
1945
|
const 绑 = 读存配置().绑定 || {}
|
|
1904
1946
|
if (绑[sid] && existsSync(绑[sid])) 夹 = String(绑[sid])
|
|
1905
1947
|
} catch {}
|
|
1906
1948
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1949
|
+
// 没有自己的文件夹 → 用本机档案
|
|
1950
|
+
if (!夹 || !existsSync(夹)) {
|
|
1951
|
+
for (const p of 本机文件()) {
|
|
1910
1952
|
try {
|
|
1911
|
-
|
|
1912
|
-
} catch {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
} catch {}
|
|
1953
|
+
出.push({ 名: basename(p, '.md'), 路: p, 文: readFileSync(p, 'utf8') })
|
|
1954
|
+
} catch {}
|
|
1955
|
+
}
|
|
1956
|
+
return 出
|
|
1957
|
+
}
|
|
1958
|
+
const 走 = (目录, 前缀) => {
|
|
1959
|
+
let 项们 = []
|
|
1960
|
+
try {
|
|
1961
|
+
项们 = readdirSync(目录, { withFileTypes: true })
|
|
1962
|
+
} catch {
|
|
1963
|
+
return
|
|
1964
|
+
}
|
|
1965
|
+
for (const d of 项们) {
|
|
1966
|
+
if (d.isDirectory()) {
|
|
1967
|
+
if (/^(\.|备份|备注)/.test(d.name)) continue
|
|
1968
|
+
走(join(目录, d.name), (前缀 ? 前缀 + '/' : '') + d.name)
|
|
1969
|
+
continue
|
|
1929
1970
|
}
|
|
1971
|
+
if (!/\.(md|txt)$/i.test(d.name)) continue
|
|
1972
|
+
try {
|
|
1973
|
+
出.push({
|
|
1974
|
+
名: (前缀 ? 前缀 + '/' : '') + basename(d.name, '.md'),
|
|
1975
|
+
路: join(目录, d.name),
|
|
1976
|
+
文: readFileSync(join(目录, d.name), 'utf8'),
|
|
1977
|
+
})
|
|
1978
|
+
} catch {}
|
|
1930
1979
|
}
|
|
1931
|
-
走(夹, '')
|
|
1932
1980
|
}
|
|
1981
|
+
走(夹, '')
|
|
1933
1982
|
return 出
|
|
1934
1983
|
}
|
|
1935
1984
|
|
|
@@ -2137,31 +2186,59 @@ export function apply(ctx, 配置 = {}) {
|
|
|
2137
2186
|
try {
|
|
2138
2187
|
const sid = String((exec && exec.sessionId) || '')
|
|
2139
2188
|
const 档 = 所有档案(sid)
|
|
2140
|
-
const 限 = Math.max(
|
|
2189
|
+
const 限 = Math.max(600, Math.min(20000, Number((args && args.最多字) || 4000)))
|
|
2141
2190
|
const 词 = String((args && args.关键词) || '').trim()
|
|
2142
2191
|
const 日 = String((args && args.日期) || '').trim()
|
|
2143
2192
|
const 名 = String((args && args.文件) || '').trim()
|
|
2144
2193
|
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
if (日) {
|
|
2148
|
-
// ⚠ 日记里写的是 `- **08-26 周X**:`,没有年份 —— 拿 2026-08-26 去比会一条都不中
|
|
2149
|
-
const 短 = 日.length >= 10 ? 日.slice(5) : 日
|
|
2150
|
-
const 命中 = 们.filter((f) => f.名.includes(日) || f.文.includes(日) || f.文.includes(短))
|
|
2151
|
-
if (命中.length) 们 = 命中
|
|
2194
|
+
if (!档.length) {
|
|
2195
|
+
return { ok: false, text: '这个会话还没有档案(先去星坊里指定文件夹)' }
|
|
2152
2196
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2197
|
+
|
|
2198
|
+
// 把文件按「一类一份」归拢,方便说清楚有什么
|
|
2199
|
+
const 分组 = { 记忆卡: [], 日记: [], 原始对话: [], 其他: [] }
|
|
2200
|
+
for (const f of 档) {
|
|
2201
|
+
if (/^日记\//.test(f.名)) 分组.日记.push(f)
|
|
2202
|
+
else if (/^原始对话\//.test(f.名)) 分组.原始对话.push(f)
|
|
2203
|
+
else if (/记忆卡/.test(f.名)) 分组.记忆卡.push(f)
|
|
2204
|
+
else 分组.其他.push(f)
|
|
2156
2205
|
}
|
|
2206
|
+
const 日期们 = 分组.日记.map((f) => (/(\d{4}-\d{2}-\d{2})/.exec(f.名) || [])[1]).filter(Boolean).sort()
|
|
2157
2207
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2208
|
+
// ── 0. 什么都不给:给索引(在外面先看清有什么,再决定钻哪一天)──
|
|
2209
|
+
if (!词 && !日 && !名) {
|
|
2210
|
+
const 行们 = ['这个档案里有 ' + 档.length + ' 个文件:', '']
|
|
2211
|
+
for (const f of 分组.记忆卡) {
|
|
2212
|
+
行们.push('· ' + f.名 + '(' + Math.round(f.文.length / 1000) / 10 + ' 万字)')
|
|
2213
|
+
}
|
|
2214
|
+
for (const f of 分组.其他) 行们.push('· ' + f.名 + '(' + f.文.length + ' 字)')
|
|
2215
|
+
if (日期们.length) {
|
|
2216
|
+
行们.push(
|
|
2217
|
+
'· 日记 ' + 日期们.length + ' 天:' + 日期们[0] + ' ~ ' + 日期们[日期们.length - 1] + '(一天一个文件)',
|
|
2218
|
+
)
|
|
2219
|
+
}
|
|
2220
|
+
if (分组.原始对话.length) {
|
|
2221
|
+
const 原日 = 分组.原始对话
|
|
2222
|
+
.map((f) => (/(\d{4}-\d{2}-\d{2})/.exec(f.名) || [])[1])
|
|
2223
|
+
.filter(Boolean)
|
|
2224
|
+
.sort()
|
|
2225
|
+
行们.push('· 原始对话 ' + 原日.length + ' 天:' + 原日[0] + ' ~ ' + 原日[原日.length - 1] + '(当天原话)')
|
|
2226
|
+
}
|
|
2227
|
+
行们.push('', '怎么查:给「关键词」全文搜(日记也会搜);给「日期」看那一天;给「文件」看整篇。')
|
|
2228
|
+
return { ok: true, text: 行们.join(String.fromCharCode(10)) }
|
|
2229
|
+
}
|
|
2160
2230
|
|
|
2161
|
-
//
|
|
2162
|
-
if (日
|
|
2231
|
+
// ── 1. 日期:直接定位那一天的日记(不用挨个翻)──
|
|
2232
|
+
if (日) {
|
|
2163
2233
|
const 短 = 日.length >= 10 ? 日.slice(5) : 日
|
|
2164
|
-
|
|
2234
|
+
const 目标 = 档.filter((f) => f.名.includes(日) || f.名.includes(短))
|
|
2235
|
+
if (目标.length) {
|
|
2236
|
+
const 段 = 目标.slice(0, 3).map((f) => '【' + f.名 + '】' + String.fromCharCode(10) + f.文.slice(0, 限))
|
|
2237
|
+
return { ok: true, text: 段.join(String.fromCharCode(10, 10)).slice(0, 限) }
|
|
2238
|
+
}
|
|
2239
|
+
// 没有那天的文件 → 在全文里找那天的条目
|
|
2240
|
+
const 出 = []
|
|
2241
|
+
for (const f of 档) {
|
|
2165
2242
|
const 行们 = f.文.split(String.fromCharCode(10))
|
|
2166
2243
|
const 块 = []
|
|
2167
2244
|
let 收 = false
|
|
@@ -2171,47 +2248,58 @@ export function apply(ctx, 配置 = {}) {
|
|
|
2171
2248
|
if (新条) 收 = t.includes(短) || t.includes(日)
|
|
2172
2249
|
if (收) 块.push(l)
|
|
2173
2250
|
}
|
|
2174
|
-
if (块.length)
|
|
2175
|
-
const 段 = '【' + f.名 + '】' + String.fromCharCode(10) + 块.join(String.fromCharCode(10))
|
|
2176
|
-
出.push(段)
|
|
2177
|
-
总 += 段.length
|
|
2178
|
-
}
|
|
2251
|
+
if (块.length) 出.push('【' + f.名 + '】' + String.fromCharCode(10) + 块.join(String.fromCharCode(10)))
|
|
2179
2252
|
}
|
|
2180
|
-
if (
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
出.push(段)
|
|
2185
|
-
总 += 段.length
|
|
2186
|
-
if (总 >= 限) break
|
|
2187
|
-
}
|
|
2253
|
+
if (出.length) return { ok: true, text: 出.join(String.fromCharCode(10, 10)).slice(0, 限) }
|
|
2254
|
+
return {
|
|
2255
|
+
ok: false,
|
|
2256
|
+
text: 日 + ' 没有记录。日记范围:' + (日期们.length ? 日期们[0] + ' ~ ' + 日期们[日期们.length - 1] : '没有日记'),
|
|
2188
2257
|
}
|
|
2189
|
-
return { ok: true, text: 出.join(String.fromCharCode(10, 10)).slice(0, 限) }
|
|
2190
2258
|
}
|
|
2191
2259
|
|
|
2192
|
-
//
|
|
2193
|
-
if (
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2260
|
+
// ── 2. 文件名:给整篇 ──
|
|
2261
|
+
if (名 && !词) {
|
|
2262
|
+
const 目标 = 档.filter((f) => f.名.includes(名) || f.路.includes(名))
|
|
2263
|
+
if (!目标.length) return { ok: false, text: '没有这个文件:' + 名 }
|
|
2264
|
+
const 段 = 目标.slice(0, 2).map((f) => '【' + f.名 + '】' + String.fromCharCode(10) + f.文.slice(0, 限))
|
|
2265
|
+
return { ok: true, text: 段.join(String.fromCharCode(10, 10)).slice(0, 限) }
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// ── 3. 关键词:全文搜,**每个文件都看**,每个文件最多取 2 段 ──
|
|
2269
|
+
// ⚠ 以前是按文件顺序扫、扫到预算用完就停 —— 记忆卡一大就把日记挤没了。
|
|
2270
|
+
const 命中 = []
|
|
2271
|
+
for (const f of 档) {
|
|
2272
|
+
const 行们 = f.文.split(String.fromCharCode(10))
|
|
2273
|
+
let 次 = 0
|
|
2274
|
+
const 段们 = []
|
|
2275
|
+
for (let i = 0; i < 行们.length; i++) {
|
|
2276
|
+
if (!行们[i].includes(词)) continue
|
|
2277
|
+
次++
|
|
2278
|
+
if (段们.length < 2) {
|
|
2279
|
+
段们.push(行们.slice(Math.max(0, i - 2), Math.min(行们.length, i + 4)).join(String.fromCharCode(10)))
|
|
2203
2280
|
}
|
|
2204
|
-
if (总 >= 限) break
|
|
2205
2281
|
}
|
|
2206
|
-
|
|
2282
|
+
if (次) 命中.push({ 名: f.名, 次, 段们 })
|
|
2207
2283
|
}
|
|
2208
2284
|
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2285
|
+
if (!命中.length) {
|
|
2286
|
+
记一笔('xingxi_read 没找到:sid=' + sid + ' 档=' + 档.length + ' 词=' + 词)
|
|
2287
|
+
return { ok: false, text: '档案里没有「' + 词 + '」(翻了 ' + 档.length + ' 个文件)' }
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
命中.sort((x, y) => y.次 - x.次)
|
|
2291
|
+
const 总次 = 命中.reduce((s, x) => s + x.次, 0)
|
|
2292
|
+
const 头 = '「' + 词 + '」在 ' + 命中.length + ' 个文件里出现 ' + 总次 + ' 次:'
|
|
2293
|
+
const 出 = [头]
|
|
2294
|
+
let 总 = 头.length
|
|
2295
|
+
for (const h of 命中) {
|
|
2296
|
+
const 段 = '【' + h.名 + '】(' + h.次 + ' 处)' + String.fromCharCode(10) + h.段们.join(String.fromCharCode(10, 10))
|
|
2297
|
+
if (总 + 段.length > 限) {
|
|
2298
|
+
出.push('…还有 ' + (命中.length - 出.length + 1) + ' 个文件也提到,想看就给「文件」参数点名')
|
|
2299
|
+
break
|
|
2300
|
+
}
|
|
2212
2301
|
出.push(段)
|
|
2213
2302
|
总 += 段.length
|
|
2214
|
-
if (总 >= 限) break
|
|
2215
2303
|
}
|
|
2216
2304
|
return { ok: true, text: 出.join(String.fromCharCode(10, 10)) }
|
|
2217
2305
|
} catch (e) {
|