dsh-feini 0.1.3 → 0.1.5
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/INSTALL.md +21 -4
- package/bin/cli.mjs +88 -1
- package/package.json +1 -1
- package/src/bridge.mjs +92 -62
- package/src/websearch.mjs +9 -0
package/INSTALL.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 安装 dsh-feini
|
|
1
|
+
# 安装 dsh-feini
|
|
2
2
|
|
|
3
3
|
两条路:自己动手,或者把下面那段提示词丢给一个 AI 让它替你装。
|
|
4
4
|
|
|
@@ -103,7 +103,24 @@ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和
|
|
|
103
103
|
2. 从 QQ 发一句,能收到回复
|
|
104
104
|
3. `memory.json` 里多了你俩那一轮
|
|
105
105
|
|
|
106
|
-
##
|
|
106
|
+
## 四、识图和联网(可选:一个智谱 key)
|
|
107
|
+
|
|
108
|
+
不配也能用,只是两件事干不了:**看不懂你发的图**、**查不了现在的事**(天气、价格、赛果、新版本)。
|
|
109
|
+
|
|
110
|
+
要这两样,去 [open.bigmodel.cn](https://open.bigmodel.cn/) 注册,实名后建一个 API Key(`<一串>.<一串>` 的格式),然后二选一:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# 装机的时候填过就算了;没填就用这条:
|
|
114
|
+
dsh-feini key vision 你的智谱key
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
或者直接把 key 粘进 `%USERPROFILE%\.dsh\dsh-feini\vision-key.txt`。
|
|
118
|
+
|
|
119
|
+
认图和联网都是调一次扣一点,按量计费;正常聊天一天几分钱到几毛钱,具体看你在那边充了多少。
|
|
120
|
+
|
|
121
|
+
写完不用重启——下一句话就生效。想关掉就把 `vision-key.txt` 清空。
|
|
122
|
+
|
|
123
|
+
## 五、排错
|
|
107
124
|
|
|
108
125
|
| 现象 | 多半是 | 怎么办 |
|
|
109
126
|
| --- | --- | --- |
|
|
@@ -113,7 +130,7 @@ AI 不会替你扫 NapCat 的码、也不会替你买 key,所以第 3 步和
|
|
|
113
130
|
| 它重复说同一句 | 发送超时后补发 | 看日志里的「核对」;已经修过,还犯就把日志发我 |
|
|
114
131
|
| 它老忘事 | 笔记本没写进去 | 直接说「记住:…」;改 `notebook.md` 立刻生效 |
|
|
115
132
|
|
|
116
|
-
##
|
|
133
|
+
## 六、想跟桌宠共用一个身份
|
|
117
134
|
|
|
118
135
|
想在桌面上有一只、QQ 里这只又跟它是同一个人的话,装两个东西:
|
|
119
136
|
|
|
@@ -141,7 +158,7 @@ dsh-feini start
|
|
|
141
158
|
- **想断开**:把 `config.json` 里的 `systemPromptPath` 改回 `%USERPROFILE%\.dsh\dsh-feini\persona.md`,重启
|
|
142
159
|
- **桌宠没装也别慌**:`link-pet` 会提示找不到配置,正常用 `persona.md` 就行
|
|
143
160
|
|
|
144
|
-
##
|
|
161
|
+
## 七、更新与卸载
|
|
145
162
|
|
|
146
163
|
```bash
|
|
147
164
|
npm i -g dsh-feini # 更新
|
package/bin/cli.mjs
CHANGED
|
@@ -98,6 +98,57 @@ function tail(file, n = 6) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const versionSeenPath = path.join(home, 'version-seen.json');
|
|
102
|
+
|
|
103
|
+
function pkgVersion() {
|
|
104
|
+
return String(readJson(path.join(pkgRoot, 'package.json'), {})?.version || '0.0.0');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 智谱 key 配了没(识图 + 联网都要它) */
|
|
108
|
+
function visionConfigured() {
|
|
109
|
+
try {
|
|
110
|
+
const p = path.join(home, 'vision-key.txt');
|
|
111
|
+
return fs.existsSync(p) && !!fs.readFileSync(p, 'utf8').trim();
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 版本变了(或者第一次跑)就体检一遍:缺哪个功能、怎么补,说清楚。
|
|
119
|
+
* 同一个版本只提醒一次,免得每次 start 都唠叨。
|
|
120
|
+
*/
|
|
121
|
+
function checkFeatures() {
|
|
122
|
+
const ver = pkgVersion();
|
|
123
|
+
let seen = {};
|
|
124
|
+
try {
|
|
125
|
+
seen = JSON.parse(fs.readFileSync(versionSeenPath, 'utf8'));
|
|
126
|
+
} catch {
|
|
127
|
+
seen = {};
|
|
128
|
+
}
|
|
129
|
+
if (String(seen.version || '') === ver) return;
|
|
130
|
+
const tips = [];
|
|
131
|
+
if (!visionConfigured()) {
|
|
132
|
+
tips.push('识图和联网还没开:dsh-feini key vision <智谱key>(没有这个 key,看不懂图、也查不了现在的事)');
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const persona = path.join(home, 'persona.md');
|
|
136
|
+
if (fs.existsSync(persona) && /自己改这一份就行/.test(fs.readFileSync(persona, 'utf8'))) {
|
|
137
|
+
tips.push('人设还是空模板:打开 persona.md,把名字和性格写上');
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
/* 没有就不提 */
|
|
141
|
+
}
|
|
142
|
+
if (tips.length) {
|
|
143
|
+
console.log(`\n体检(${seen.version ? `${seen.version} → ${ver}` : `首次 ${ver}`}),还差这些:`);
|
|
144
|
+
for (const t of tips) console.log(' · ' + t);
|
|
145
|
+
console.log(' 补完就不用管了,下个版本再体检\n');
|
|
146
|
+
} else if (seen.version) {
|
|
147
|
+
console.log(`升级完成:${seen.version} → ${ver},功能都在\n`);
|
|
148
|
+
}
|
|
149
|
+
writeJson(versionSeenPath, { version: ver, at: Date.now() });
|
|
150
|
+
}
|
|
151
|
+
|
|
101
152
|
const help = `dsh-feini —— 在 QQ 里养一个自己的伙伴
|
|
102
153
|
|
|
103
154
|
用法:
|
|
@@ -107,6 +158,8 @@ const help = `dsh-feini —— 在 QQ 里养一个自己的伙伴
|
|
|
107
158
|
dsh-feini status 看状态 + 最近日志
|
|
108
159
|
dsh-feini sea 手动重建记忆海(桥在跑时每两分钟自动建)
|
|
109
160
|
dsh-feini link-pet 和桌面宠物共用同一份人设(读 dsh-pet 的配置)
|
|
161
|
+
dsh-feini key vision <k> 补写智谱 key(识图 + 联网用)
|
|
162
|
+
dsh-feini key model <k> 补写 DeepSeek key
|
|
110
163
|
dsh-feini path 打印数据目录
|
|
111
164
|
|
|
112
165
|
参数(init 可用,也可以不交互直接给):
|
|
@@ -164,7 +217,11 @@ async function cmdInit(argv) {
|
|
|
164
217
|
const owner = String(a.owner || (await ask('你怎么称呼自己', '主人'))).trim() || '主人';
|
|
165
218
|
|
|
166
219
|
const key = String(a.key || (await ask('DeepSeek API key(可留空,事后写进 key.txt)', '')) || '').trim();
|
|
167
|
-
const vkey = String(
|
|
220
|
+
const vkey = String(
|
|
221
|
+
a['vision-key'] ||
|
|
222
|
+
(await ask('智谱 key(识图+联网用,open.bigmodel.cn 建,没有就留空)', '')) ||
|
|
223
|
+
'',
|
|
224
|
+
).trim();
|
|
168
225
|
rl?.close();
|
|
169
226
|
|
|
170
227
|
if (key) fs.writeFileSync(path.join(home, 'key.txt'), key, 'utf8');
|
|
@@ -239,6 +296,7 @@ async function cmdInit(argv) {
|
|
|
239
296
|
2. dsh-feini start
|
|
240
297
|
3. QQ 上给那个号发一句,看看回不回
|
|
241
298
|
`);
|
|
299
|
+
checkFeatures();
|
|
242
300
|
}
|
|
243
301
|
|
|
244
302
|
/* ---------- start / stop / status ---------- */
|
|
@@ -249,6 +307,7 @@ function cmdStart() {
|
|
|
249
307
|
console.log(`已经在跑了(pid ${running}),不用再起一个`);
|
|
250
308
|
return;
|
|
251
309
|
}
|
|
310
|
+
checkFeatures();
|
|
252
311
|
if (!fs.existsSync(configPath)) {
|
|
253
312
|
console.error('还没有配置,先跑 dsh-feini init');
|
|
254
313
|
process.exit(1);
|
|
@@ -284,6 +343,7 @@ function cmdStop() {
|
|
|
284
343
|
function cmdStatus() {
|
|
285
344
|
const pid = currentPid();
|
|
286
345
|
console.log(pid ? `在跑,pid ${pid}` : '没在跑');
|
|
346
|
+
console.log(`识图/联网:${visionConfigured() ? '已配(智谱 key 在)' : '没配(dsh-feini key vision <key>)'}`);
|
|
287
347
|
console.log('--- 最近日志 ---');
|
|
288
348
|
console.log(tail(logPath, 8));
|
|
289
349
|
}
|
|
@@ -346,6 +406,32 @@ function cmdSea() {
|
|
|
346
406
|
console.log('文件:' + (cfg.seaFile || path.join(home, 'me.json')));
|
|
347
407
|
}
|
|
348
408
|
|
|
409
|
+
/**
|
|
410
|
+
* 补写 key:dsh-feini key vision <智谱key> / key model <DeepSeek key>
|
|
411
|
+
* 装的时候没填、或者后来换了 key,用它,不用重新 init。
|
|
412
|
+
*/
|
|
413
|
+
function cmdKey(argv) {
|
|
414
|
+
const kind = String(argv[0] || '').toLowerCase();
|
|
415
|
+
const value = String(argv[1] || '').trim();
|
|
416
|
+
if (!kind || !value) {
|
|
417
|
+
console.error('用法:dsh-feini key vision <智谱key> 或者 dsh-feini key model <DeepSeek key>');
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
ensureDir(home);
|
|
421
|
+
if (kind === 'vision' || kind === '智谱') {
|
|
422
|
+
fs.writeFileSync(path.join(home, 'vision-key.txt'), value, 'utf8');
|
|
423
|
+
console.log('智谱 key 写好了:' + path.join(home, 'vision-key.txt'));
|
|
424
|
+
console.log('识图和联网下一句就生效,不用重启');
|
|
425
|
+
} else if (kind === 'model' || kind === 'deepseek') {
|
|
426
|
+
fs.writeFileSync(path.join(home, 'key.txt'), value, 'utf8');
|
|
427
|
+
console.log('DeepSeek key 写好了:' + path.join(home, 'key.txt'));
|
|
428
|
+
console.log('要重启桥才生效:dsh-feini stop && dsh-feini start');
|
|
429
|
+
} else {
|
|
430
|
+
console.error(`不认识的 key 类型:${kind}(只认 vision / model)`);
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
349
435
|
/* ---------- 入口 ---------- */
|
|
350
436
|
|
|
351
437
|
const cmd = process.argv[2];
|
|
@@ -358,6 +444,7 @@ try {
|
|
|
358
444
|
else if (cmd === 'status') cmdStatus();
|
|
359
445
|
else if (cmd === 'sea') cmdSea();
|
|
360
446
|
else if (cmd === 'link-pet') cmdLinkPet();
|
|
447
|
+
else if (cmd === 'key') cmdKey(rest);
|
|
361
448
|
else if (cmd === 'path') cmdPath();
|
|
362
449
|
else {
|
|
363
450
|
console.error(`不认识的命令:${cmd}\n`);
|
package/package.json
CHANGED
package/src/bridge.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { readSystemPrompt } from './prompt.mjs';
|
|
|
22
22
|
import { chat, imagePart } from './llm.mjs';
|
|
23
23
|
import { toDisplay, splitParts } from './clean.mjs';
|
|
24
24
|
import { looksLikeCorrection, prevLineInText, correctionHint } from './correct.mjs';
|
|
25
|
-
import { needsSearch, webSearch, searchLine, searchFailLine } from './websearch.mjs';
|
|
25
|
+
import { needsSearch, webSearch, searchLine, searchFailLine, searchNotConfiguredLine } from './websearch.mjs';
|
|
26
26
|
import { recentFacts } from './recent.mjs';
|
|
27
27
|
import { getHistory, getLastTs, lastMessage, appendRound } from './memory.mjs';
|
|
28
28
|
import { startProactive } from './proactive.mjs';
|
|
@@ -43,7 +43,6 @@ import { getBalance, BALANCE_RULE } from './balance.mjs';
|
|
|
43
43
|
import { addUsage, usageSummary, USAGE_RULE } from './usage.mjs';
|
|
44
44
|
import { createDiary } from './diary.mjs';
|
|
45
45
|
import { onUsage } from './llm.mjs';
|
|
46
|
-
import { buildSea } from './build-sea.mjs';
|
|
47
46
|
|
|
48
47
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
49
48
|
const root = process.env.DSH_FEINI_HOME ? path.resolve(process.env.DSH_FEINI_HOME) : path.resolve(here, '..');
|
|
@@ -63,8 +62,6 @@ function loadConfig() {
|
|
|
63
62
|
modelVision: raw.modelVision || {},
|
|
64
63
|
proactive: raw.proactive || { enabled: false },
|
|
65
64
|
stickers: raw.stickers || {},
|
|
66
|
-
// 休眠哨兵/外部通知口子;被占用时只记一行日志,不影响聊天
|
|
67
|
-
notifyPort: Number(raw.notifyPort || 7301),
|
|
68
65
|
};
|
|
69
66
|
if (process.env.BRIDGE_SYSTEM) cfg.systemPromptPath = null; // 测试用:直接给 prompt 文件
|
|
70
67
|
return cfg;
|
|
@@ -156,18 +153,14 @@ const guessFile = path.resolve(root, config.guessPath || 'guesses.json');
|
|
|
156
153
|
const angerFile = path.join(root, 'anger.json');
|
|
157
154
|
const petMemoryDir = config.petMemoryDir || '';
|
|
158
155
|
const interestFile = petMemoryDir ? path.join(petMemoryDir, 'interests.md') : path.join(root, 'interests.md');
|
|
159
|
-
//
|
|
160
|
-
const seaFile =
|
|
161
|
-
? path.
|
|
162
|
-
:
|
|
163
|
-
? path.join(petMemoryDir, '..', 'memory-sea', 'data', 'me.json')
|
|
164
|
-
: path.join(root, 'me.json');
|
|
156
|
+
// 记忆海的数据:pet-memory 的上一级(工作区)里的 memory-sea/data/me.json
|
|
157
|
+
const seaFile = petMemoryDir
|
|
158
|
+
? path.join(petMemoryDir, '..', 'memory-sea', 'data', 'me.json')
|
|
159
|
+
: path.join(root, 'me.json');
|
|
165
160
|
// 笔记本:每轮现读,改完立刻生效(人设里不再抄它)
|
|
166
|
-
const notebookFile =
|
|
167
|
-
? path.
|
|
168
|
-
:
|
|
169
|
-
? path.join(petMemoryDir, '..', 'memory', 'notebook.md')
|
|
170
|
-
: path.join(root, 'notebook.md');
|
|
161
|
+
const notebookFile = petMemoryDir
|
|
162
|
+
? path.join(petMemoryDir, '..', 'memory', 'notebook.md')
|
|
163
|
+
: path.join(root, 'notebook.md');
|
|
171
164
|
|
|
172
165
|
let notebookCache = { mtime: 0, text: '' };
|
|
173
166
|
function notebookText() {
|
|
@@ -184,27 +177,6 @@ function notebookText() {
|
|
|
184
177
|
// 人设里「别硬报时」是给桌面碎碎念写的;QQ 里他问时间必须直接答,所以把这句话改软
|
|
185
178
|
const personaForQQ = baseSystemPrompt.replace(/别硬报时/g, '碎碎念时别反复提时间');
|
|
186
179
|
|
|
187
|
-
/** 记忆海:每两分钟把「对话 + 笔记本 + 日记」重建成索引,启动两秒后先建一次 */
|
|
188
|
-
function rebuildSea() {
|
|
189
|
-
try {
|
|
190
|
-
const r = buildSea({
|
|
191
|
-
memoryPath: config.memoryPath,
|
|
192
|
-
entry: config.bucket.entry,
|
|
193
|
-
pet: config.bucket.pet,
|
|
194
|
-
notebookPath: notebookFile,
|
|
195
|
-
diaryDir: config.diaryDir ? path.resolve(root, config.diaryDir) : path.join(root, 'diary'),
|
|
196
|
-
outFile: seaFile,
|
|
197
|
-
});
|
|
198
|
-
log(`记忆海重建:长期 ${r.longTerm} 条、对话 ${r.messages} 条、日记 ${r.diaries} 篇`);
|
|
199
|
-
} catch (e) {
|
|
200
|
-
log('记忆海重建失败:' + e.message);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
const seaBoot = setTimeout(rebuildSea, 2000);
|
|
204
|
-
seaBoot.unref?.();
|
|
205
|
-
const seaTimer = setInterval(rebuildSea, 2 * 60 * 1000);
|
|
206
|
-
seaTimer.unref?.();
|
|
207
|
-
|
|
208
180
|
// 每次说话都现读人设:sync 改完下一句就生效,不用重启桥
|
|
209
181
|
function freshPersona() {
|
|
210
182
|
if (!config.systemPromptPath) return personaForQQ;
|
|
@@ -514,6 +486,38 @@ async function knownSticker(img) {
|
|
|
514
486
|
}
|
|
515
487
|
}
|
|
516
488
|
|
|
489
|
+
const visionHintFile = path.join(root, 'vision-hint.json');
|
|
490
|
+
|
|
491
|
+
/** 识图的 key 配了没(配了才看得懂图,也才查得了网) */
|
|
492
|
+
function visionKeyReady() {
|
|
493
|
+
try {
|
|
494
|
+
const p = config.modelVision?.keyFile;
|
|
495
|
+
if (!p) return false;
|
|
496
|
+
return fs.existsSync(p) && !!fs.readFileSync(p, 'utf8').trim();
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** 记一次「看不懂图」,返回这是第几次;前三次数要提醒,之后不再提 */
|
|
503
|
+
function visionMiss(hit = true) {
|
|
504
|
+
let s = { count: 0 };
|
|
505
|
+
try {
|
|
506
|
+
s = JSON.parse(fs.readFileSync(visionHintFile, 'utf8'));
|
|
507
|
+
} catch {
|
|
508
|
+
/* 没有就是第一次 */
|
|
509
|
+
}
|
|
510
|
+
const count = Number(s.count) || 0;
|
|
511
|
+
if (!hit) return { count };
|
|
512
|
+
const next = count + 1;
|
|
513
|
+
try {
|
|
514
|
+
fs.writeFileSync(visionHintFile, JSON.stringify({ count: next, lastAt: Date.now() }), 'utf8');
|
|
515
|
+
} catch {
|
|
516
|
+
/* 写不进去就每次都当第一次 */
|
|
517
|
+
}
|
|
518
|
+
return { count: next };
|
|
519
|
+
}
|
|
520
|
+
|
|
517
521
|
/** 把一条 QQ 消息变成模型能吃的 user content */
|
|
518
522
|
async function buildUserContent(text, images, quoted = '') {
|
|
519
523
|
const parts = [];
|
|
@@ -558,7 +562,25 @@ async function buildUserContent(text, images, quoted = '') {
|
|
|
558
562
|
log('图片处理失败:', e.message);
|
|
559
563
|
}
|
|
560
564
|
}
|
|
561
|
-
|
|
565
|
+
// 没配识图(或者识图挂了):看不清就说看不清,但最多提醒三次
|
|
566
|
+
const miss = visionMiss();
|
|
567
|
+
if (miss.count <= 3) {
|
|
568
|
+
const tail =
|
|
569
|
+
miss.count === 3
|
|
570
|
+
? '这是第三次了,顺口说一句「这是最后一次提醒你了」,以后别提。'
|
|
571
|
+
: '';
|
|
572
|
+
parts.push({
|
|
573
|
+
type: 'text',
|
|
574
|
+
text:
|
|
575
|
+
`(他发来一张图片/表情包,但我这边没配识图,看不出是什么。` +
|
|
576
|
+
`顺口跟他说一句「我还没配识图,这张看不懂」,别像系统提示,也别猜内容。${tail})`,
|
|
577
|
+
});
|
|
578
|
+
} else {
|
|
579
|
+
parts.push({
|
|
580
|
+
type: 'text',
|
|
581
|
+
text: '(他发来一张图片/表情包,我看不出内容;已经提醒过三次,这次别再提配置的事)',
|
|
582
|
+
});
|
|
583
|
+
}
|
|
562
584
|
label.push('[图片]');
|
|
563
585
|
}
|
|
564
586
|
if (parts.length === 0) parts.push({ type: 'text', text: '(空消息)' });
|
|
@@ -872,14 +894,19 @@ async function finishSearch(bot, userId) {
|
|
|
872
894
|
const lastUser = [...hist].reverse().find((m) => m.role === 'user');
|
|
873
895
|
const q = String(lastUser?.content || '').replace(/[\[\[]?引用[::][^\]]*[\]\]]?/g, '').trim();
|
|
874
896
|
let note = '';
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
897
|
+
if (!visionKeyReady()) {
|
|
898
|
+
note = searchNotConfiguredLine();
|
|
899
|
+
log('补查:没配搜索功能');
|
|
900
|
+
} else {
|
|
901
|
+
try {
|
|
902
|
+
const keyPath = config.modelVision?.keyFile || path.join(root, 'vision-key.txt');
|
|
903
|
+
const hits = await webSearch(q, { keyPath, baseUrl: config.modelVision?.baseUrl });
|
|
904
|
+
note = hits.length ? searchLine(q, hits) : searchFailLine();
|
|
905
|
+
log(`补查「${q.slice(0, 24)}」:${hits.length} 条`);
|
|
906
|
+
} catch (e) {
|
|
907
|
+
note = searchFailLine();
|
|
908
|
+
log('补查失败:', e.message);
|
|
909
|
+
}
|
|
883
910
|
}
|
|
884
911
|
const reply = await chat({
|
|
885
912
|
baseUrl: config.model.baseUrl,
|
|
@@ -1072,19 +1099,25 @@ async function respond(bot, userId, text, images, quoted, replyToId = 0) {
|
|
|
1072
1099
|
// 问到现在的事(天气、价格、赛果、新闻、版本更新):先现查一把,查到的塞进 system
|
|
1073
1100
|
let searchNote = '';
|
|
1074
1101
|
if (needsSearch(text)) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1102
|
+
if (!visionKeyReady()) {
|
|
1103
|
+
// 没配搜索:就直说没配,别装成「查了没搜到」
|
|
1104
|
+
searchNote = searchNotConfiguredLine();
|
|
1105
|
+
log('要联网,但没配搜索功能');
|
|
1106
|
+
} else {
|
|
1107
|
+
const keyPath = config.modelVision?.keyFile || path.join(root, 'vision-key.txt');
|
|
1108
|
+
try {
|
|
1109
|
+
const hits = await webSearch(text, { keyPath, baseUrl: config.modelVision?.baseUrl });
|
|
1110
|
+
if (hits.length) {
|
|
1111
|
+
searchNote = searchLine(text, hits);
|
|
1112
|
+
log(`联网查了 ${hits.length} 条:${text.slice(0, 24)}`);
|
|
1113
|
+
} else {
|
|
1114
|
+
searchNote = searchFailLine();
|
|
1115
|
+
log('联网查了,没结果');
|
|
1116
|
+
}
|
|
1117
|
+
} catch (e) {
|
|
1082
1118
|
searchNote = searchFailLine();
|
|
1083
|
-
log('
|
|
1119
|
+
log('联网搜索失败:', e.message);
|
|
1084
1120
|
}
|
|
1085
|
-
} catch (e) {
|
|
1086
|
-
searchNote = searchFailLine();
|
|
1087
|
-
log('联网搜索失败:', e.message);
|
|
1088
1121
|
}
|
|
1089
1122
|
}
|
|
1090
1123
|
|
|
@@ -1418,7 +1451,6 @@ const notifyPort = Number(config.notifyPort || 7301);
|
|
|
1418
1451
|
const notifyOff = process.env.BRIDGE_NO_NOTIFY === '1'; // 测试时别抢端口
|
|
1419
1452
|
let lastNotify = { text: '', at: 0 }; // 同一个哨兵可能起了两份,去重
|
|
1420
1453
|
try {
|
|
1421
|
-
if (notifyOff) throw new Error('skip'); // 测试环境:连端口都不抢
|
|
1422
1454
|
http
|
|
1423
1455
|
.createServer((req, res) => {
|
|
1424
1456
|
const u = new URL(req.url || '/', 'http://127.0.0.1');
|
|
@@ -1446,12 +1478,10 @@ try {
|
|
|
1446
1478
|
})
|
|
1447
1479
|
.catch((e) => res.writeHead(500).end(String(e?.message || e)));
|
|
1448
1480
|
})
|
|
1449
|
-
.on('error', (e) =>
|
|
1450
|
-
if (!notifyOff) log('通知口子错误:', e.message);
|
|
1451
|
-
})
|
|
1481
|
+
.on('error', (e) => log('通知口子错误:', e.message))
|
|
1452
1482
|
.listen(notifyPort, '127.0.0.1', () => log('通知口子 127.0.0.1:' + notifyPort));
|
|
1453
1483
|
} catch (e) {
|
|
1454
|
-
|
|
1484
|
+
log('通知口子起不来:', e.message);
|
|
1455
1485
|
}
|
|
1456
1486
|
|
|
1457
1487
|
/* ---------- 拉起休眠哨兵 ---------- */
|
|
@@ -1467,7 +1497,7 @@ try {
|
|
|
1467
1497
|
log('休眠哨兵已启动');
|
|
1468
1498
|
}
|
|
1469
1499
|
} catch (e) {
|
|
1470
|
-
|
|
1500
|
+
log('哨兵启动失败:', e.message);
|
|
1471
1501
|
}
|
|
1472
1502
|
bot.connect();
|
|
1473
1503
|
|
package/src/websearch.mjs
CHANGED
|
@@ -109,3 +109,12 @@ export function searchFailLine() {
|
|
|
109
109
|
'直接说「我这边网好像没搜到」,可以让他再问一次;别的就照你会的答。'
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
/** 压根没配搜索:直说没配,别装成「查了没搜到」 */
|
|
114
|
+
export function searchNotConfiguredLine() {
|
|
115
|
+
return (
|
|
116
|
+
'\n\n【没配搜索】他问的是要联网才知道的事,但我这边没配搜索功能,一次都没去查。' +
|
|
117
|
+
'就照实说「我没配搜索功能」——别说「没查到」「没搜到」,那听着像查过了。' +
|
|
118
|
+
'可以顺口提一句:配个智谱 key 就能查。别编答案。'
|
|
119
|
+
);
|
|
120
|
+
}
|