cortico-world-surfing 0.4.0 → 0.4.1
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/CHANGELOG.md +4 -0
- package/README.md +2 -7
- package/dist/webui.js +19 -28
- package/package.json +1 -1
- package/src/webui/client.ts +17 -16
- package/src/world.ts +8 -6
- package/src/secret-file.ts +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.1 - 2026-11-13
|
|
4
|
+
|
|
5
|
+
- Save the Microlink API key directly in the Surfing panel. The compact password field clears after saving, and leaving it empty preserves the existing key.
|
|
6
|
+
|
|
3
7
|
## 0.4.0 - 2026-11-06
|
|
4
8
|
|
|
5
9
|
- Add `surfing_search_all`, which searches Bilibili, Tieba, Moegirlpedia and Baidu Baike in parallel and returns short results. Open numbered results and further Tieba results with `surfing_read_page`. The WebUI has a switch for each source and a per-source result limit.
|
package/README.md
CHANGED
|
@@ -31,19 +31,14 @@
|
|
|
31
31
|
|
|
32
32
|
无需 API Key 即可使用 Microlink 后备。配置 Key 后使用认证接口及该 Key 的额度;Key 仅发送给 Microlink,不进入模型回执。
|
|
33
33
|
|
|
34
|
-
在网上冲浪的「配置」页找到「Microlink
|
|
35
|
-
|
|
36
|
-
```dotenv
|
|
37
|
-
CORTICO_SURFING_MICROLINK_API_KEY=
|
|
38
|
-
```
|
|
34
|
+
在网上冲浪的「配置」页找到「Microlink 远程渲染」。
|
|
39
35
|
|
|
40
36
|
取得免费 Key 的步骤:
|
|
41
37
|
|
|
42
38
|
1. 打开 [Microlink 注册页](https://dashboard.microlink.io/signup),注册账号并登录,无需购买付费套餐。
|
|
43
39
|
2. 在控制台左侧选择「Overview」,点击「Usage」标签。
|
|
44
40
|
3. 点击「Development」标签右上角的「…」,复制 API Key。
|
|
45
|
-
4. 将 Key
|
|
46
|
-
5. 点击「刷新密钥状态」,状态变为「API Key 已配置」。下次远程读取使用新 Key;清空值并保存可恢复免 Key 调用。
|
|
41
|
+
4. 将 Key 粘贴到 Microlink API Key 输入框并保存,下次远程读取即使用新 Key。
|
|
47
42
|
|
|
48
43
|
免 Key 和免费 Key 的额度不同,当前限制见 [Microlink 额度说明](https://microlink.io/docs/api/basics/rate-limit)。额度耗尽或访问失败时,回执说明原因;已有短静态正文仍返回,并标明未取得完整渲染内容。
|
|
49
44
|
|
package/dist/webui.js
CHANGED
|
@@ -843,7 +843,6 @@ var require_ipaddr = __commonJS({
|
|
|
843
843
|
var import_ipaddr = __toESM(require_ipaddr(), 1);
|
|
844
844
|
|
|
845
845
|
// src/config.ts
|
|
846
|
-
var SURFING_MICROLINK_SECRET = "CORTICO_SURFING_MICROLINK_API_KEY";
|
|
847
846
|
var PROXY_MODE_PATH = "worlds.surfing.network.proxyCompatibility";
|
|
848
847
|
var PROXY_RANGES_PATH = "worlds.surfing.network.customProxyRanges";
|
|
849
848
|
var SURFING_DEFAULTS = {
|
|
@@ -1066,7 +1065,7 @@ async function mount(ctx) {
|
|
|
1066
1065
|
const toggles = ui.h("div", "segwrap umulti");
|
|
1067
1066
|
toggles.style.flexWrap = "wrap";
|
|
1068
1067
|
for (const [path, property] of Object.entries(group.schema.properties)) {
|
|
1069
|
-
const
|
|
1068
|
+
const save2 = (value, apply) => saveValue(path, property.title, saved, value, apply);
|
|
1070
1069
|
if (property.type === "boolean") {
|
|
1071
1070
|
const toggle = ui.h("button", "seg", property.title);
|
|
1072
1071
|
toggle.type = "button";
|
|
@@ -1075,7 +1074,7 @@ async function mount(ctx) {
|
|
|
1075
1074
|
toggle.addEventListener("click", () => {
|
|
1076
1075
|
const value = !toggle.classList.contains("active");
|
|
1077
1076
|
showPressed(toggle, value);
|
|
1078
|
-
|
|
1077
|
+
save2(value);
|
|
1079
1078
|
}, { signal: ctx.signal });
|
|
1080
1079
|
toggles.append(toggle);
|
|
1081
1080
|
continue;
|
|
@@ -1084,7 +1083,7 @@ async function mount(ctx) {
|
|
|
1084
1083
|
note.id = path;
|
|
1085
1084
|
const scale = property["x-scale"] ?? 1;
|
|
1086
1085
|
const input = ui.input({ type: "number", value: String(Number(state.values[path]) / scale), onChange: (value) => {
|
|
1087
|
-
|
|
1086
|
+
save2(Number(value) * scale, (next) => {
|
|
1088
1087
|
if (input.value === value) input.value = String(Number(next) / scale);
|
|
1089
1088
|
});
|
|
1090
1089
|
} });
|
|
@@ -1135,34 +1134,25 @@ async function mount(ctx) {
|
|
|
1135
1134
|
keyStatus.classList.add("bad");
|
|
1136
1135
|
}
|
|
1137
1136
|
}
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
void ctx.refresh().catch(() => void 0);
|
|
1151
|
-
return "\u5DF2\u5237\u65B0\u5BC6\u94A5\u72B6\u6001";
|
|
1152
|
-
});
|
|
1153
|
-
} }),
|
|
1154
|
-
keyStatus
|
|
1155
|
-
);
|
|
1156
|
-
showKey(state.keySet);
|
|
1137
|
+
const keyInput = ui.input({ type: "password", cls: "mono", onCommit: () => save.click() });
|
|
1138
|
+
const save = ui.button("\u4FDD\u5B58\u5BC6\u94A5", { size: "sm", variant: "primary", onClick: () => {
|
|
1139
|
+
if (!keyInput.value.trim()) return;
|
|
1140
|
+
void keyAction(async () => {
|
|
1141
|
+
const next = await ctx.invoke("saveKey", [keyInput.value]);
|
|
1142
|
+
keyInput.value = "";
|
|
1143
|
+
showKey(next.keySet);
|
|
1144
|
+
return "\u5DF2\u4FDD\u5B58";
|
|
1145
|
+
});
|
|
1146
|
+
} });
|
|
1147
|
+
const keyRow = ui.rowbar();
|
|
1148
|
+
keyRow.append(keyInput, save);
|
|
1157
1149
|
const steps = ui.h("ol");
|
|
1158
1150
|
steps.style.cssText = "list-style:decimal;padding-left:24px;margin:6px 0;line-height:1.8";
|
|
1159
|
-
const secret = ui.h("code", "mono", `${SURFING_MICROLINK_SECRET}=`);
|
|
1160
1151
|
for (const parts of [
|
|
1161
1152
|
["\u6253\u5F00", anchor("Microlink \u6CE8\u518C\u9875", "https://dashboard.microlink.io/signup"), "\uFF0C\u6CE8\u518C\u8D26\u53F7\u5E76\u767B\u5F55\uFF0C\u65E0\u9700\u8D2D\u4E70\u4ED8\u8D39\u5957\u9910\u3002"],
|
|
1162
1153
|
["\u5728\u63A7\u5236\u53F0\u5DE6\u4FA7\u9009\u62E9\u300COverview\u300D\uFF0C\u70B9\u51FB\u300CUsage\u300D\u6807\u7B7E\u3002"],
|
|
1163
1154
|
["\u70B9\u51FB\u300CDevelopment\u300D\u6807\u7B7E\u53F3\u4E0A\u89D2\u7684\u300C\u2026\u300D\uFF0C\u590D\u5236 API Key\u3002"],
|
|
1164
|
-
["\
|
|
1165
|
-
["\u4FDD\u5B58\u540E\uFF0C\u4E0B\u6B21\u8FDC\u7A0B\u8BFB\u53D6\u4F7F\u7528\u8BE5 Key \u7684\u989D\u5EA6\u3002\u70B9\u51FB\u300C\u5237\u65B0\u5BC6\u94A5\u72B6\u6001\u300D\u53EF\u67E5\u770B\u914D\u7F6E\u72B6\u6001\u3002\u6E05\u7A7A Key \u7684\u503C\u5E76\u4FDD\u5B58\u53EF\u6062\u590D\u514D Key \u8C03\u7528\u3002"]
|
|
1155
|
+
["\u5728 Microlink API Key \u8F93\u5165\u6846\u7C98\u8D34\u5E76\u4FDD\u5B58\u3002"]
|
|
1166
1156
|
]) {
|
|
1167
1157
|
const step = ui.h("li");
|
|
1168
1158
|
step.append(...parts);
|
|
@@ -1172,11 +1162,12 @@ async function mount(ctx) {
|
|
|
1172
1162
|
limits.append("\u514D Key \u548C\u514D\u8D39 Key \u7684\u989D\u5EA6\u4E0D\u540C\uFF0C\u5F53\u524D\u9650\u5236\u89C1", anchor("Microlink \u989D\u5EA6\u8BF4\u660E", "https://microlink.io/docs/api/basics/rate-limit"), "\u3002");
|
|
1173
1163
|
const microlink = ui.sheet({
|
|
1174
1164
|
title: "Microlink \u8FDC\u7A0B\u6E32\u67D3",
|
|
1175
|
-
desc: "\u672C\u673A Chromium \u4E0D\u53EF\u7528\u65F6\uFF0C\u5C06\u7F51\u9875 URL \u4EA4\u7ED9 Microlink \u6E32\u67D3\u3002API Key \u53EF\u9009\uFF0C\
|
|
1165
|
+
desc: "\u672C\u673A Chromium \u4E0D\u53EF\u7528\u65F6\uFF0C\u5C06\u7F51\u9875 URL \u4EA4\u7ED9 Microlink \u6E32\u67D3\u3002API Key \u53EF\u9009\uFF0C\u672A\u914D\u7F6E\u65F6\u4F7F\u7528\u514D Key \u989D\u5EA6\u3002"
|
|
1176
1166
|
});
|
|
1177
1167
|
const help = ui.foldSheet("surfing-microlink-key", { title: "\u5982\u4F55\u53D6\u5F97 API Key", defaultOpen: false });
|
|
1178
1168
|
help.body.append(steps, limits);
|
|
1179
|
-
microlink.body.append(
|
|
1169
|
+
microlink.body.append(keyPill, ui.field("Microlink API Key", keyRow), keyStatus, help.el);
|
|
1170
|
+
showKey(state.keySet);
|
|
1180
1171
|
layout.append(microlink.el);
|
|
1181
1172
|
ctx.root.replaceChildren(layout);
|
|
1182
1173
|
}
|
package/package.json
CHANGED
package/src/webui/client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebUiClientBundle, WebUiPanelContext } from 'cortico/web/shared/client-panel.ts';
|
|
2
|
-
import { PROXY_MODE_PATH, PROXY_RANGES_PATH, SURFING_CONFIG_GROUPS, SURFING_LIMITS_CONFIG_GROUP,
|
|
2
|
+
import { PROXY_MODE_PATH, PROXY_RANGES_PATH, SURFING_CONFIG_GROUPS, SURFING_LIMITS_CONFIG_GROUP, SURFING_PROXY_CONFIG_GROUP } from '../config.ts';
|
|
3
3
|
|
|
4
4
|
type ConfigValue = number | boolean | string;
|
|
5
5
|
interface ConfigState { values: Record<string, ConfigValue>; keySet: boolean }
|
|
@@ -131,25 +131,25 @@ async function mount(ctx: WebUiPanelContext): Promise<void> {
|
|
|
131
131
|
try { keyStatus.textContent = await run(); keyStatus.classList.remove('bad'); }
|
|
132
132
|
catch (error) { keyStatus.textContent = errorText(error); keyStatus.classList.add('bad'); }
|
|
133
133
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
const keyInput = ui.input({ type: 'password', cls: 'mono', onCommit: () => save.click() });
|
|
135
|
+
const save = ui.button('保存密钥', { size: 'sm', variant: 'primary', onClick: () => {
|
|
136
|
+
if (!keyInput.value.trim()) return;
|
|
137
|
+
void keyAction(async () => {
|
|
138
|
+
const next = await ctx.invoke<ConfigState>('saveKey', [keyInput.value]);
|
|
139
|
+
keyInput.value = '';
|
|
140
|
+
showKey(next.keySet);
|
|
141
|
+
return '已保存';
|
|
142
|
+
});
|
|
143
|
+
} });
|
|
144
|
+
const keyRow = ui.rowbar();
|
|
145
|
+
keyRow.append(keyInput, save);
|
|
144
146
|
const steps = ui.h('ol');
|
|
145
147
|
steps.style.cssText = 'list-style:decimal;padding-left:24px;margin:6px 0;line-height:1.8';
|
|
146
|
-
const secret = ui.h('code', 'mono', `${SURFING_MICROLINK_SECRET}=`);
|
|
147
148
|
for (const parts of [
|
|
148
149
|
['打开', anchor('Microlink 注册页', 'https://dashboard.microlink.io/signup'), ',注册账号并登录,无需购买付费套餐。'],
|
|
149
150
|
['在控制台左侧选择「Overview」,点击「Usage」标签。'],
|
|
150
151
|
['点击「Development」标签右上角的「…」,复制 API Key。'],
|
|
151
|
-
['
|
|
152
|
-
['保存后,下次远程读取使用该 Key 的额度。点击「刷新密钥状态」可查看配置状态。清空 Key 的值并保存可恢复免 Key 调用。'],
|
|
152
|
+
['在 Microlink API Key 输入框粘贴并保存。'],
|
|
153
153
|
]) {
|
|
154
154
|
const step = ui.h('li');
|
|
155
155
|
step.append(...parts);
|
|
@@ -159,11 +159,12 @@ async function mount(ctx: WebUiPanelContext): Promise<void> {
|
|
|
159
159
|
limits.append('免 Key 和免费 Key 的额度不同,当前限制见', anchor('Microlink 额度说明', 'https://microlink.io/docs/api/basics/rate-limit'), '。');
|
|
160
160
|
const microlink = ui.sheet({
|
|
161
161
|
title: 'Microlink 远程渲染',
|
|
162
|
-
desc: '本机 Chromium 不可用时,将网页 URL 交给 Microlink 渲染。API Key
|
|
162
|
+
desc: '本机 Chromium 不可用时,将网页 URL 交给 Microlink 渲染。API Key 可选,未配置时使用免 Key 额度。',
|
|
163
163
|
});
|
|
164
164
|
const help = ui.foldSheet('surfing-microlink-key', { title: '如何取得 API Key', defaultOpen: false });
|
|
165
165
|
help.body.append(steps, limits);
|
|
166
|
-
microlink.body.append(
|
|
166
|
+
microlink.body.append(keyPill, ui.field('Microlink API Key', keyRow), keyStatus, help.el);
|
|
167
|
+
showKey(state.keySet);
|
|
167
168
|
layout.append(microlink.el);
|
|
168
169
|
ctx.root.replaceChildren(layout);
|
|
169
170
|
}
|
package/src/world.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { PageReader, pageKey } from './page-reader.ts';
|
|
|
12
12
|
import { SnapshotCache, estimateTokens, type Material } from './snapshots.ts';
|
|
13
13
|
import { MicrolinkRenderer } from './microlink.ts';
|
|
14
14
|
import { SITE_SOURCES, continueTieba, searchSites } from './sites.ts';
|
|
15
|
-
import { prepareKeyFile, openSecretFile } from './secret-file.ts';
|
|
16
15
|
|
|
17
16
|
const ENV_PROMPT_FILE = fileURLToPath(new URL('./ENV_PROMPT.md', import.meta.url));
|
|
18
17
|
|
|
@@ -80,8 +79,7 @@ export class SurfingWorld implements World {
|
|
|
80
79
|
private stopped = false;
|
|
81
80
|
private last?: { status: string; failed: boolean; estimatedTokens: number; sourceTruncated: boolean };
|
|
82
81
|
|
|
83
|
-
constructor(private readonly ctx: WorldContext<SurfingConfigSection>, private readonly client = new PublicClient()
|
|
84
|
-
private readonly openFile = openSecretFile) {
|
|
82
|
+
constructor(private readonly ctx: WorldContext<SurfingConfigSection>, private readonly client = new PublicClient()) {
|
|
85
83
|
applyConfigDefaults(ctx.cfg);
|
|
86
84
|
this.pages = new PageReader(undefined, new MicrolinkRenderer(() => ctx.secret(SURFING_MICROLINK_SECRET)));
|
|
87
85
|
}
|
|
@@ -244,9 +242,13 @@ export class SurfingWorld implements World {
|
|
|
244
242
|
if (panel !== 'config') throw new Error('未知面板。');
|
|
245
243
|
if (method === 'state') return this.configState();
|
|
246
244
|
if (method === 'save') { this.saveConfig(args[0], args[1]); return this.configState(); }
|
|
247
|
-
if (method === '
|
|
248
|
-
const
|
|
249
|
-
|
|
245
|
+
if (method === 'saveKey') {
|
|
246
|
+
const [value] = args;
|
|
247
|
+
if (typeof value !== 'string') throw new Error('密钥须为文本');
|
|
248
|
+
const key = value.trim();
|
|
249
|
+
if (/\s/.test(key)) throw new Error('密钥不能包含空白字符');
|
|
250
|
+
if (key) this.ctx.storeSecret(SURFING_MICROLINK_SECRET, key);
|
|
251
|
+
return this.configState();
|
|
250
252
|
}
|
|
251
253
|
throw new Error('未知操作。');
|
|
252
254
|
},
|
package/src/secret-file.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { SURFING_MICROLINK_SECRET } from './config.ts';
|
|
5
|
-
|
|
6
|
-
export function prepareKeyFile(botDir: string): string {
|
|
7
|
-
const file = join(botDir, '.env');
|
|
8
|
-
const contents = existsSync(file) ? readFileSync(file, 'utf8') : '';
|
|
9
|
-
if (!new RegExp(`^${SURFING_MICROLINK_SECRET}=`, 'm').test(contents)) {
|
|
10
|
-
const line = `${contents && !contents.endsWith('\n') ? '\n' : ''}${SURFING_MICROLINK_SECRET}=\n`;
|
|
11
|
-
if (existsSync(file)) appendFileSync(file, line);
|
|
12
|
-
else writeFileSync(file, line, { mode: 0o600 });
|
|
13
|
-
}
|
|
14
|
-
return file;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function openSecretFile(file: string): Promise<void> {
|
|
18
|
-
const command = process.platform === 'win32' ? 'notepad.exe' : process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const child = spawn(command, [file], { detached: true, stdio: 'ignore' });
|
|
21
|
-
child.once('error', () => reject(new Error('无法打开密钥文件。')));
|
|
22
|
-
child.once('spawn', () => { child.unref(); resolve(); });
|
|
23
|
-
});
|
|
24
|
-
}
|