cortico 0.1.0
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 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按 manifest URL 懒加载扩展;进行中的加载去重,成功模块缓存,失败允许重试。
|
|
3
|
+
* 浏览器仍可能缓存失败的 ESM URL;重新构建后的内容 hash URL 可避开该缓存。
|
|
4
|
+
* JS 与 CSS URL 均校验清单规定的路径和字符。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isConsoleClientBundle,
|
|
9
|
+
type ConsoleClientBundle,
|
|
10
|
+
type ConsolePanel,
|
|
11
|
+
} from '../../shared/client-panel.ts';
|
|
12
|
+
import { isSafeAssetUrl, type ConsoleAssetEntry } from '../../shared/console-protocol.ts';
|
|
13
|
+
import { S } from './strings.ts';
|
|
14
|
+
|
|
15
|
+
export interface StyleLink {
|
|
16
|
+
rel: string;
|
|
17
|
+
href: string;
|
|
18
|
+
dataset: { [key: string]: string | undefined };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoaderDeps {
|
|
22
|
+
importModule(url: string): Promise<unknown>;
|
|
23
|
+
/** 注入 `<link rel=stylesheet>` 的宿主(一般是 document.head)。 */
|
|
24
|
+
styleHost: { appendChild(node: unknown): void };
|
|
25
|
+
createLink(): StyleLink;
|
|
26
|
+
log?(message: string, detail?: Record<string, unknown>): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PanelBundleError extends Error {
|
|
30
|
+
readonly pageId: string;
|
|
31
|
+
constructor(pageId: string, message: string, cause?: unknown) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'PanelBundleError';
|
|
34
|
+
this.pageId = pageId;
|
|
35
|
+
if (cause !== undefined) this.cause = cause;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class ConsolePageLoader {
|
|
40
|
+
private readonly deps: LoaderDeps;
|
|
41
|
+
private readonly loaded = new Map<string, ConsoleClientBundle>();
|
|
42
|
+
private readonly inFlight = new Map<string, Promise<ConsoleClientBundle>>();
|
|
43
|
+
private readonly styled = new Set<string>();
|
|
44
|
+
|
|
45
|
+
constructor(deps: LoaderDeps) {
|
|
46
|
+
this.deps = deps;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 已加载的扩展集合,供核对懒加载行为。 */
|
|
50
|
+
get loadedPages(): string[] {
|
|
51
|
+
return [...this.loaded.keys()];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async load(pageId: string, asset: ConsoleAssetEntry | undefined): Promise<ConsoleClientBundle> {
|
|
55
|
+
const cached = this.loaded.get(pageId);
|
|
56
|
+
if (cached) return cached;
|
|
57
|
+
|
|
58
|
+
const pending = this.inFlight.get(pageId);
|
|
59
|
+
if (pending) return pending;
|
|
60
|
+
|
|
61
|
+
const task = this.loadOnce(pageId, asset);
|
|
62
|
+
this.inFlight.set(pageId, task);
|
|
63
|
+
try {
|
|
64
|
+
const bundle = await task;
|
|
65
|
+
this.loaded.set(pageId, bundle);
|
|
66
|
+
return bundle;
|
|
67
|
+
} finally {
|
|
68
|
+
// 成功与否都要摘掉:失败不缓存,下次可以重来。
|
|
69
|
+
this.inFlight.delete(pageId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async loadOnce(
|
|
74
|
+
pageId: string,
|
|
75
|
+
asset: ConsoleAssetEntry | undefined,
|
|
76
|
+
): Promise<ConsoleClientBundle> {
|
|
77
|
+
if (!asset) {
|
|
78
|
+
throw new PanelBundleError(pageId, S.noBundle(pageId));
|
|
79
|
+
}
|
|
80
|
+
if (!isSafeAssetUrl(asset.js)) {
|
|
81
|
+
throw new PanelBundleError(pageId, S.badBundleUrl(pageId));
|
|
82
|
+
}
|
|
83
|
+
let mod: unknown;
|
|
84
|
+
try {
|
|
85
|
+
mod = await this.deps.importModule(asset.js);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
throw new PanelBundleError(pageId, S.bundleLoadFailed(pageId, String(err)), err);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const candidate = (mod as { default?: unknown } | null)?.default;
|
|
91
|
+
if (!isConsoleClientBundle(candidate)) {
|
|
92
|
+
throw new PanelBundleError(pageId, S.badDefaultExport(pageId));
|
|
93
|
+
}
|
|
94
|
+
// 样式在 js 装成之后再注入:装不上的页不该在页面里留下一个 404 的 link。
|
|
95
|
+
this.injectStyle(pageId, asset);
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 每页样式只注入一次,面板卸载时保留。 */
|
|
100
|
+
private injectStyle(pageId: string, asset: ConsoleAssetEntry): void {
|
|
101
|
+
if (!asset.css || this.styled.has(pageId)) return;
|
|
102
|
+
if (!isSafeAssetUrl(asset.css)) {
|
|
103
|
+
this.deps.log?.(`「${pageId}」的样式地址不合法,已跳过`, { href: String(asset.css) });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.styled.add(pageId);
|
|
107
|
+
const link = this.deps.createLink();
|
|
108
|
+
link.rel = 'stylesheet';
|
|
109
|
+
link.href = asset.css;
|
|
110
|
+
link.dataset.provider = pageId;
|
|
111
|
+
this.deps.styleHost.appendChild(link);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 获取声明的面板实现;缺失时错误列出扩展提供的面板键。 */
|
|
115
|
+
async resolvePanel(
|
|
116
|
+
pageId: string,
|
|
117
|
+
panelId: string,
|
|
118
|
+
asset: ConsoleAssetEntry | undefined,
|
|
119
|
+
): Promise<ConsolePanel> {
|
|
120
|
+
const bundle = await this.load(pageId, asset);
|
|
121
|
+
const panel = bundle.panels[panelId];
|
|
122
|
+
// 分别报告键缺失与实现不合法。
|
|
123
|
+
if (!(panelId in bundle.panels)) {
|
|
124
|
+
const known = Object.keys(bundle.panels).join(' / ') || S.none;
|
|
125
|
+
throw new PanelBundleError(pageId, S.noSuchBundlePanel(pageId, panelId, known));
|
|
126
|
+
}
|
|
127
|
+
if (!panel || typeof panel.mount !== 'function') {
|
|
128
|
+
throw new PanelBundleError(pageId, S.badPanelImpl(pageId, panelId));
|
|
129
|
+
}
|
|
130
|
+
return panel;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { pick } from '../core/language.ts';
|
|
2
|
+
|
|
3
|
+
const zh = {
|
|
4
|
+
protocolMismatch: (server: string, page: string) =>
|
|
5
|
+
`控制台协议版本不一致:服务端 ${server},页面 ${page}。请强制刷新页面。`,
|
|
6
|
+
pageFailed: (pageId: string) => `页面「${pageId}」打开失败`,
|
|
7
|
+
noPage: (pageId: string) => `控制台页面不存在:「${pageId}」`,
|
|
8
|
+
noPageHint: '请检查页面地址及模块激活状态。',
|
|
9
|
+
noPanels: (label: string) => `「${label}」没有声明任何面板。`,
|
|
10
|
+
noSuchPanel: (label: string, wanted: string) => `「${label}」没有面板「${wanted}」`,
|
|
11
|
+
provides: (list: string) => '可用面板:' + list,
|
|
12
|
+
panelFailed: (title: string) => `面板「${title}」加载失败`,
|
|
13
|
+
configEmpty: '配置组不可用。',
|
|
14
|
+
configTitle: '配置',
|
|
15
|
+
configDesc: '修改自动保存到 config.json。标注需重启的配置在重启后生效,其余立即生效。',
|
|
16
|
+
assembly: '装配',
|
|
17
|
+
notInstalled: "不可用",
|
|
18
|
+
notActivated: '未激活',
|
|
19
|
+
hidden: '已隐藏',
|
|
20
|
+
reloadPrefix: '前缀待重载 · 点此重载',
|
|
21
|
+
open: '打开',
|
|
22
|
+
configTab: '配置',
|
|
23
|
+
promptsTab: '提示词模板',
|
|
24
|
+
storageTab: '数据',
|
|
25
|
+
toolsTab: '工具表',
|
|
26
|
+
storageTitle: '数据',
|
|
27
|
+
storageDesc: '本页声明的存储项;清除范围与结果由各项的实现决定。',
|
|
28
|
+
reloadTitle: '重载 system 前缀?',
|
|
29
|
+
reloadBody: '重读全部前缀源并替换当前 session 的系统前缀,保留已有对话。',
|
|
30
|
+
prefixReloaded: '前缀已重载',
|
|
31
|
+
noBundle: (pageId: string) =>
|
|
32
|
+
`「${pageId}」缺少面板构建产物。仓内页面请先停止 bot,再运行 pnpm build:web;`
|
|
33
|
+
+ 'extensions/ 下的扩展请在包目录构建,再重启进程。',
|
|
34
|
+
badBundleUrl: (pageId: string) => `「${pageId}」的面板产物地址不合法,已拒绝加载`,
|
|
35
|
+
bundleLoadFailed: (pageId: string, err: string) => `「${pageId}」的面板产物加载失败: ${err}`,
|
|
36
|
+
badDefaultExport: (pageId: string) => `「${pageId}」的面板产物缺少有效的 default 导出,格式应为 { panels: { … } }`,
|
|
37
|
+
none: '(无)',
|
|
38
|
+
noSuchBundlePanel: (pageId: string, panelId: string, known: string) =>
|
|
39
|
+
`「${pageId}」的面板产物中没有「${panelId}」。可用面板:${known}`,
|
|
40
|
+
badPanelImpl: (pageId: string, panelId: string) =>
|
|
41
|
+
`「${pageId}」的面板「${panelId}」缺少 mount 方法`,
|
|
42
|
+
noBuiltinPanel: (name: string, known: string) =>
|
|
43
|
+
`内置面板「${name}」不存在。可用面板:${known}`,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const en: typeof zh = {
|
|
47
|
+
protocolMismatch: (server: string, page: string) =>
|
|
48
|
+
`Console protocol version mismatch: server ${server}, this page ${page}.`
|
|
49
|
+
+ ' Force a page refresh.',
|
|
50
|
+
pageFailed: (pageId: string) => `Could not open "${pageId}"`,
|
|
51
|
+
noPage: (pageId: string) => `Console page not found: "${pageId}"`,
|
|
52
|
+
noPageHint: 'Check the page address and module activation status.',
|
|
53
|
+
noPanels: (label: string) => `"${label}" declares no panels.`,
|
|
54
|
+
noSuchPanel: (label: string, wanted: string) => `"${label}" has no panel "${wanted}"`,
|
|
55
|
+
provides: (list: string) => 'Available panels: ' + list,
|
|
56
|
+
panelFailed: (title: string) => `Panel "${title}" failed to load`,
|
|
57
|
+
configEmpty: 'Configuration groups unavailable.',
|
|
58
|
+
configTitle: 'Config',
|
|
59
|
+
configDesc: 'Changes are saved to config.json automatically. Settings marked as requiring a restart apply after restarting; the rest apply immediately.',
|
|
60
|
+
assembly: 'Assembly',
|
|
61
|
+
notInstalled: "Unavailable",
|
|
62
|
+
notActivated: 'not activated',
|
|
63
|
+
hidden: 'hidden',
|
|
64
|
+
reloadPrefix: 'Prefix drifted · click to reload',
|
|
65
|
+
open: 'Open',
|
|
66
|
+
configTab: 'Config',
|
|
67
|
+
promptsTab: 'Prompt templates',
|
|
68
|
+
storageTab: 'Data',
|
|
69
|
+
toolsTab: 'Tools',
|
|
70
|
+
storageTitle: 'Data',
|
|
71
|
+
storageDesc: 'Storage items declared by this page; what a clear covers and returns is up to each item.',
|
|
72
|
+
reloadTitle: 'Reload the system prefix?',
|
|
73
|
+
reloadBody: 'Re-reads all prefix sources and replaces the current session\'s system prefix. Existing conversation messages are kept.',
|
|
74
|
+
prefixReloaded: 'Prefix reloaded',
|
|
75
|
+
noBundle: (pageId: string) =>
|
|
76
|
+
`Panel bundle missing for "${pageId}". For repository pages, stop the bot first, then run pnpm build:web;`
|
|
77
|
+
+ ' for packages under extensions/, build in the package directory and restart the process.',
|
|
78
|
+
badBundleUrl: (pageId: string) => `Panel bundle URL of "${pageId}" is invalid; refused to load`,
|
|
79
|
+
bundleLoadFailed: (pageId: string, err: string) => `Panel bundle of "${pageId}" failed to load: ${err}`,
|
|
80
|
+
badDefaultExport: (pageId: string) => `Panel bundle of "${pageId}" has no valid default export; expected { panels: { … } }`,
|
|
81
|
+
none: '(none)',
|
|
82
|
+
noSuchBundlePanel: (pageId: string, panelId: string, known: string) =>
|
|
83
|
+
`Panel bundle of "${pageId}" has no panel "${panelId}". Available panels: ${known}`,
|
|
84
|
+
badPanelImpl: (pageId: string, panelId: string) =>
|
|
85
|
+
`Panel "${panelId}" of "${pageId}" is missing the mount method`,
|
|
86
|
+
noBuiltinPanel: (name: string, known: string) =>
|
|
87
|
+
`Built-in panel "${name}" not found. Available panels: ${known}`,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const S = pick({ zh, en });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { pick } from '../../core/language.ts';
|
|
2
|
+
|
|
3
|
+
const zh = {
|
|
4
|
+
ioGroup: (name: string | undefined) => `IO 工具 · ${name}`,
|
|
5
|
+
groupCore: '原生动作 · core',
|
|
6
|
+
groupPersona: '记忆 / 文件工具 · Persona',
|
|
7
|
+
noDescription: '(未填写工具说明)',
|
|
8
|
+
paramCount: (n: number) => `${n} 个参数`,
|
|
9
|
+
paramHeadPath: '参数路径',
|
|
10
|
+
paramHeadType: '类型',
|
|
11
|
+
paramHeadConstraint: '约束',
|
|
12
|
+
paramHeadDesc: '说明',
|
|
13
|
+
required: '必填',
|
|
14
|
+
optional: '可选',
|
|
15
|
+
noParams: '这个工具没有声明参数',
|
|
16
|
+
copySchema: '复制 schema',
|
|
17
|
+
fullSchema: '完整 JSON Schema',
|
|
18
|
+
toolsTitle: '工具库',
|
|
19
|
+
toolsDesc: '当前提供给模型的工具定义,只读。',
|
|
20
|
+
toolCount: (n: number) => `${n} 个`,
|
|
21
|
+
toolsFilter: '筛选工具名或说明…',
|
|
22
|
+
toolsEmpty: '工具表为空——主循环启动后会在这里出现',
|
|
23
|
+
loadFailed: (msg: string) => `工具表接口不可用:${msg}`,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const en: typeof zh = {
|
|
27
|
+
ioGroup: (name: string | undefined) => `IO tools · ${name}`,
|
|
28
|
+
groupCore: 'Native actions · core',
|
|
29
|
+
groupPersona: 'Memory / file tools · Persona',
|
|
30
|
+
noDescription: '(no tool description)',
|
|
31
|
+
paramCount: (n: number) => `${n} params`,
|
|
32
|
+
paramHeadPath: 'Path',
|
|
33
|
+
paramHeadType: 'Type',
|
|
34
|
+
paramHeadConstraint: 'Constraint',
|
|
35
|
+
paramHeadDesc: 'Description',
|
|
36
|
+
required: 'required',
|
|
37
|
+
optional: 'optional',
|
|
38
|
+
noParams: 'This tool declares no parameters',
|
|
39
|
+
copySchema: 'Copy schema',
|
|
40
|
+
fullSchema: 'Full JSON Schema',
|
|
41
|
+
toolsTitle: 'Tool library',
|
|
42
|
+
toolsDesc: 'Current tool definitions provided to the model. Read-only.',
|
|
43
|
+
toolCount: (n: number) => `${n} tools`,
|
|
44
|
+
toolsFilter: 'Filter by tool name or description…',
|
|
45
|
+
toolsEmpty: 'Tool table is empty — it appears here once the main loop starts',
|
|
46
|
+
loadFailed: (msg: string) => `Tool table endpoint unavailable: ${msg}`,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const S = pick({ zh, en });
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 只读工具表与完整 JSON Schema,由 Persona 页的框架页签承载。
|
|
3
|
+
* 按后端 owner 分组并保留装配顺序,前端不维护具体 World 名单;复制由 ui.copyButton 处理。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ConsoleUi } from '../../../shared/client-panel.ts';
|
|
7
|
+
import type { ToolSchemaDoc } from '../../features/live/protocol.ts';
|
|
8
|
+
import { S } from './strings.ts';
|
|
9
|
+
|
|
10
|
+
/** schema 节点的类型名。联合/枚举/组合都给一个能读的词,不返回空。 */
|
|
11
|
+
export function schemaTypeLabel(node: unknown): string {
|
|
12
|
+
if (!node || typeof node !== 'object') return '—';
|
|
13
|
+
const n = node as Record<string, unknown>;
|
|
14
|
+
if (Array.isArray(n.type)) return n.type.join(' | ');
|
|
15
|
+
if (n.type) return String(n.type);
|
|
16
|
+
if (n.enum) return 'enum';
|
|
17
|
+
if (n.oneOf) return 'oneOf';
|
|
18
|
+
if (n.anyOf) return 'anyOf';
|
|
19
|
+
if (n.allOf) return 'allOf';
|
|
20
|
+
return 'schema';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SchemaParamRow {
|
|
24
|
+
path: string;
|
|
25
|
+
type: string;
|
|
26
|
+
required: boolean;
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 参数表:递归展开 `properties`,数组元素记成 `x[]`。 */
|
|
31
|
+
export function schemaParameterRows(parameters: unknown): SchemaParamRow[] {
|
|
32
|
+
const rows: SchemaParamRow[] = [];
|
|
33
|
+
const walk = (node: unknown, prefix: string): void => {
|
|
34
|
+
if (!node || typeof node !== 'object') return;
|
|
35
|
+
const n = node as Record<string, unknown>;
|
|
36
|
+
const props = n.properties as Record<string, unknown> | undefined;
|
|
37
|
+
if (!props) return;
|
|
38
|
+
const required = new Set(Array.isArray(n.required) ? (n.required as string[]) : []);
|
|
39
|
+
for (const [name, child] of Object.entries(props)) {
|
|
40
|
+
const path = prefix ? `${prefix}.${name}` : name;
|
|
41
|
+
const c = (child ?? {}) as Record<string, unknown>;
|
|
42
|
+
rows.push({
|
|
43
|
+
path,
|
|
44
|
+
type: schemaTypeLabel(child),
|
|
45
|
+
required: required.has(name),
|
|
46
|
+
description: typeof c.description === 'string' ? c.description : '',
|
|
47
|
+
});
|
|
48
|
+
walk(child, path);
|
|
49
|
+
if (c.items && typeof c.items === 'object') walk(c.items, `${path}[]`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(parameters ?? {}, '');
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 按 owner 分栏。返回顺序固定:原生动作、Persona、各 World(按出场顺序)。 */
|
|
57
|
+
export function groupTools(
|
|
58
|
+
schemas: readonly ToolSchemaDoc[],
|
|
59
|
+
): Array<[string, ToolSchemaDoc[]]> {
|
|
60
|
+
const core: ToolSchemaDoc[] = [];
|
|
61
|
+
const persona: ToolSchemaDoc[] = [];
|
|
62
|
+
const worlds = new Map<string, ToolSchemaDoc[]>();
|
|
63
|
+
for (const t of schemas) {
|
|
64
|
+
const owner = t.owner ?? ((t.tags ?? []).includes('flow') ? { kind: 'core' } : { kind: 'persona' });
|
|
65
|
+
if (owner.kind === 'core') {
|
|
66
|
+
core.push(t);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (owner.kind !== 'world') {
|
|
70
|
+
persona.push(t);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const name = S.ioGroup(owner.label || owner.id);
|
|
74
|
+
const bucket = worlds.get(name);
|
|
75
|
+
if (bucket) bucket.push(t);
|
|
76
|
+
else worlds.set(name, [t]);
|
|
77
|
+
}
|
|
78
|
+
return [
|
|
79
|
+
[S.groupCore, core],
|
|
80
|
+
[S.groupPersona, persona],
|
|
81
|
+
...worlds.entries(),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 卡片与它的可搜文本(筛选时按这一串比对)。 */
|
|
86
|
+
interface CardEntry {
|
|
87
|
+
el: HTMLElement;
|
|
88
|
+
haystack: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toolSchemaCard(ui: ConsoleUi, schema: ToolSchemaDoc): CardEntry {
|
|
92
|
+
const card = ui.h('details', 'tool-schema-card');
|
|
93
|
+
const summary = ui.h('summary', 'tool-schema-summary');
|
|
94
|
+
const rows = schemaParameterRows(schema.parameters);
|
|
95
|
+
summary.append(
|
|
96
|
+
ui.h('span', 'tool-schema-title', schema.name),
|
|
97
|
+
ui.h('span', 'tool-schema-short', schema.description || S.noDescription),
|
|
98
|
+
ui.pill(S.paramCount(rows.length)),
|
|
99
|
+
);
|
|
100
|
+
card.appendChild(summary);
|
|
101
|
+
|
|
102
|
+
const body = ui.h('div', 'tool-schema-body');
|
|
103
|
+
const rootDesc = (schema.parameters as { description?: unknown } | null)?.description;
|
|
104
|
+
if (typeof rootDesc === 'string' && rootDesc !== '') {
|
|
105
|
+
body.appendChild(ui.h('div', 'schema-root-description', rootDesc));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (rows.length) {
|
|
109
|
+
const wrap = ui.h('div', 'schema-param-wrap');
|
|
110
|
+
const table = ui.h('table', 'schema-param-table');
|
|
111
|
+
const thead = ui.h('thead');
|
|
112
|
+
const htr = ui.h('tr');
|
|
113
|
+
for (const t of [S.paramHeadPath, S.paramHeadType, S.paramHeadConstraint, S.paramHeadDesc]) {
|
|
114
|
+
htr.appendChild(ui.h('th', null, t));
|
|
115
|
+
}
|
|
116
|
+
thead.appendChild(htr);
|
|
117
|
+
const tbody = ui.h('tbody');
|
|
118
|
+
for (const row of rows) {
|
|
119
|
+
const tr = ui.h('tr');
|
|
120
|
+
tr.append(
|
|
121
|
+
ui.h('td', 'schema-param-path', row.path),
|
|
122
|
+
ui.h('td', 'schema-param-type', row.type),
|
|
123
|
+
ui.h('td', 'schema-param-required', row.required ? S.required : S.optional),
|
|
124
|
+
ui.h('td', 'schema-param-description', row.description || '—'),
|
|
125
|
+
);
|
|
126
|
+
tbody.appendChild(tr);
|
|
127
|
+
}
|
|
128
|
+
table.append(thead, tbody);
|
|
129
|
+
wrap.appendChild(table);
|
|
130
|
+
body.appendChild(wrap);
|
|
131
|
+
} else {
|
|
132
|
+
body.appendChild(ui.h('div', 'placeholder schema-empty-params', S.noParams));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const raw = ui.h('details', 'schema-raw');
|
|
136
|
+
const rawText = JSON.stringify(schema, null, 2);
|
|
137
|
+
const tools = ui.h('div', 'schema-raw-tools');
|
|
138
|
+
tools.appendChild(ui.copyButton(() => rawText, { label: S.copySchema }));
|
|
139
|
+
raw.append(ui.h('summary', null, S.fullSchema), tools, ui.h('pre', 'mono', rawText));
|
|
140
|
+
body.appendChild(raw);
|
|
141
|
+
card.appendChild(body);
|
|
142
|
+
|
|
143
|
+
const haystack = `${schema.name} ${schema.description || ''} ${rows
|
|
144
|
+
.map((r) => `${r.path} ${r.description}`)
|
|
145
|
+
.join(' ')}`.toLocaleLowerCase();
|
|
146
|
+
return { el: card, haystack };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ToolsView {
|
|
150
|
+
el: HTMLElement;
|
|
151
|
+
render(schemas: readonly ToolSchemaDoc[]): void;
|
|
152
|
+
/** 拉不到完整工具表时的说明(仍然把当前主循环的那份画出来) */
|
|
153
|
+
note(text: string): void;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createToolsView(ui: ConsoleUi): ToolsView {
|
|
157
|
+
const sheet = ui.sheet({
|
|
158
|
+
title: S.toolsTitle,
|
|
159
|
+
en: 'tool schemas',
|
|
160
|
+
desc: S.toolsDesc,
|
|
161
|
+
});
|
|
162
|
+
const bar = ui.rowbar();
|
|
163
|
+
const count = ui.chip(S.toolCount(0));
|
|
164
|
+
const search = ui.input({
|
|
165
|
+
type: 'search',
|
|
166
|
+
placeholder: S.toolsFilter,
|
|
167
|
+
onInput: (v) => applyFilter(v),
|
|
168
|
+
});
|
|
169
|
+
bar.append(count, search);
|
|
170
|
+
const groups = ui.h('div', 'schema-groups');
|
|
171
|
+
const noteLine = ui.msgline('');
|
|
172
|
+
sheet.body.append(bar, noteLine, groups);
|
|
173
|
+
|
|
174
|
+
/** 分栏与卡片,筛选时按这两张表逐个开关,不重建 DOM。 */
|
|
175
|
+
let sections: Array<{ el: HTMLElement; cards: CardEntry[] }> = [];
|
|
176
|
+
|
|
177
|
+
function applyFilter(raw: string): void {
|
|
178
|
+
const q = raw.trim().toLocaleLowerCase();
|
|
179
|
+
for (const sec of sections) {
|
|
180
|
+
let any = false;
|
|
181
|
+
for (const card of sec.cards) {
|
|
182
|
+
const hit = !q || card.haystack.includes(q);
|
|
183
|
+
card.el.className = hit ? 'tool-schema-card' : 'tool-schema-card hidden';
|
|
184
|
+
any = any || hit;
|
|
185
|
+
}
|
|
186
|
+
sec.el.className = any ? 'toolgroup schema-group' : 'toolgroup schema-group hidden';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
el: sheet.el,
|
|
192
|
+
render(schemas) {
|
|
193
|
+
while (groups.children.length) groups.children[0].remove();
|
|
194
|
+
sections = [];
|
|
195
|
+
count.textContent = S.toolCount(schemas.length);
|
|
196
|
+
if (!schemas.length) {
|
|
197
|
+
groups.appendChild(ui.placeholder(S.toolsEmpty));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
for (const [name, tools] of groupTools(schemas)) {
|
|
201
|
+
if (!tools.length) continue;
|
|
202
|
+
const section = ui.h('section', 'toolgroup schema-group');
|
|
203
|
+
section.appendChild(ui.h('div', 'gt', name));
|
|
204
|
+
const cards = tools.map((t) => toolSchemaCard(ui, t));
|
|
205
|
+
for (const c of cards) section.appendChild(c.el);
|
|
206
|
+
groups.appendChild(section);
|
|
207
|
+
sections.push({ el: section, cards });
|
|
208
|
+
}
|
|
209
|
+
applyFilter(search.value);
|
|
210
|
+
},
|
|
211
|
+
note(text) {
|
|
212
|
+
noteLine.textContent = text;
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 控制台 HTTP 接口;面板通过 ctx.invoke、invokeBinary、pickPath、setConfig 调用。
|
|
3
|
+
* 请求携带界面语言并接受取消信号;panelRoute 负责 page id 的编码。
|
|
4
|
+
* 非 2xx 与解析失败抛 ConsoleInvokeError,消息优先使用服务端 error,附带响应片段。
|
|
5
|
+
* AbortError 原样抛出;空成功响应返回 null,二进制接口返回 Blob。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
CONSOLE_MANIFEST_ROUTE,
|
|
10
|
+
panelRoute,
|
|
11
|
+
type ConsoleManifest,
|
|
12
|
+
} from '../../shared/console-protocol.ts';
|
|
13
|
+
import { ConsoleInvokeError } from '../../shared/client-panel.ts';
|
|
14
|
+
import type { ConfigValue } from '../../../core/config-schema.ts';
|
|
15
|
+
import {
|
|
16
|
+
PATH_PICKER_ROUTE,
|
|
17
|
+
type PathPickerOptions,
|
|
18
|
+
type PathPickerResponse,
|
|
19
|
+
} from '../../shared/path-picker.ts';
|
|
20
|
+
import { languageHeaders, pick } from './language.ts';
|
|
21
|
+
|
|
22
|
+
const zh = {
|
|
23
|
+
httpStatus: (status: number, snippet: string) => `HTTP ${status}:${snippet}`,
|
|
24
|
+
notJson: (status: number, snippet: string) => `响应不是合法 JSON(HTTP ${status}):${snippet}`,
|
|
25
|
+
};
|
|
26
|
+
const en: typeof zh = {
|
|
27
|
+
httpStatus: (status: number, snippet: string) => `HTTP ${status}: ${snippet}`,
|
|
28
|
+
notJson: (status: number, snippet: string) => `Response is not valid JSON (HTTP ${status}): ${snippet}`,
|
|
29
|
+
};
|
|
30
|
+
const S = pick({ zh, en });
|
|
31
|
+
|
|
32
|
+
/** 所有请求都接受一个取消信号;面板一律传 `ctx.signal`。 */
|
|
33
|
+
export interface RequestOptions {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
keepalive?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 错误消息附带的响应片段长度上限。 */
|
|
39
|
+
const SNIPPET_MAX = 200;
|
|
40
|
+
|
|
41
|
+
function snippet(text: string): string {
|
|
42
|
+
const s = text.trim();
|
|
43
|
+
return s.length > SNIPPET_MAX ? `${s.slice(0, SNIPPET_MAX)}…` : s;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 按 name 识别 AbortError,兼容跨 realm 异常。 */
|
|
47
|
+
function isAbortError(err: unknown): boolean {
|
|
48
|
+
return (err as { name?: unknown } | null)?.name === 'AbortError';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 把任意异常收敛成 `ConsoleInvokeError`;abort 与已经归一化过的原样放行。 */
|
|
52
|
+
function normalizeError(err: unknown, status: number): never {
|
|
53
|
+
if (isAbortError(err)) throw err;
|
|
54
|
+
if (err instanceof ConsoleInvokeError) throw err;
|
|
55
|
+
throw new ConsoleInvokeError(String(err), status);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function send(path: string, init: RequestInit, opts?: RequestOptions): Promise<Response> {
|
|
59
|
+
try {
|
|
60
|
+
return await fetch(path, {
|
|
61
|
+
...init,
|
|
62
|
+
headers: { ...languageHeaders(), ...(init.headers as Record<string, string> | undefined) },
|
|
63
|
+
...(opts?.signal ? { signal: opts.signal } : {}),
|
|
64
|
+
...(opts?.keepalive ? { keepalive: true } : {}),
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// fetch reject = 网络层没走到 HTTP,没有状态码可言,记 0。
|
|
68
|
+
normalizeError(err, 0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 读取响应体也可能抛出 AbortError,原样传递。 */
|
|
73
|
+
async function readText(res: Response): Promise<string> {
|
|
74
|
+
try {
|
|
75
|
+
return await res.text();
|
|
76
|
+
} catch (err) {
|
|
77
|
+
normalizeError(err, res.status);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 非 2xx 的统一出口:优先服务端措辞,其次 `HTTP <status>`(带响应片段)。 */
|
|
82
|
+
async function throwHttpError(res: Response): Promise<never> {
|
|
83
|
+
const text = await readText(res);
|
|
84
|
+
let message = `HTTP ${res.status}`;
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(text) as { error?: unknown } | null;
|
|
87
|
+
const err = parsed?.error;
|
|
88
|
+
if (typeof err === 'string' && err !== '') message = err;
|
|
89
|
+
} catch {
|
|
90
|
+
if (text.trim() !== '') message = S.httpStatus(res.status, snippet(text));
|
|
91
|
+
}
|
|
92
|
+
throw new ConsoleInvokeError(message, res.status);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 成功响应解析为 JSON;204 或空正文返回 null,解析失败抛 ConsoleInvokeError。 */
|
|
96
|
+
async function readJson<T>(res: Response): Promise<T> {
|
|
97
|
+
if (!res.ok) await throwHttpError(res);
|
|
98
|
+
if (res.status === 204) return null as T;
|
|
99
|
+
const text = await readText(res);
|
|
100
|
+
if (text.trim() === '') return null as T;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(text) as T;
|
|
103
|
+
} catch {
|
|
104
|
+
throw new ConsoleInvokeError(S.notJson(res.status, snippet(text)), res.status);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function readBlob(res: Response): Promise<Blob> {
|
|
109
|
+
if (!res.ok) await throwHttpError(res);
|
|
110
|
+
try {
|
|
111
|
+
return await res.blob();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
normalizeError(err, res.status);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// 通用动词
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
export async function get<T>(path: string, opts?: RequestOptions): Promise<T> {
|
|
122
|
+
return readJson<T>(await send(path, { method: 'GET' }, opts));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* `post` 只负责序列化传进来的 body —— `{ args }` 这个形状是 panel RPC 的约定,
|
|
127
|
+
* 由 `invokePanel` 负责,不在这里硬编码。
|
|
128
|
+
*/
|
|
129
|
+
export async function post<T>(path: string, body?: unknown, opts?: RequestOptions): Promise<T> {
|
|
130
|
+
const init: RequestInit = {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
body: JSON.stringify(body ?? {}),
|
|
134
|
+
};
|
|
135
|
+
return readJson<T>(await send(path, init, opts));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 上传二进制正文。 */
|
|
139
|
+
export async function postBlob<T>(
|
|
140
|
+
path: string,
|
|
141
|
+
body: Blob,
|
|
142
|
+
opts?: RequestOptions,
|
|
143
|
+
): Promise<T> {
|
|
144
|
+
return readJson<T>(await send(path, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'Content-Type': body.type || 'application/octet-stream' },
|
|
147
|
+
body,
|
|
148
|
+
}, opts));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 打开运行 WebApp 的主机上的本机路径选择器。 */
|
|
152
|
+
export async function pickPath(options: PathPickerOptions, opts?: RequestOptions): Promise<string | null> {
|
|
153
|
+
const response = await post<PathPickerResponse>(PATH_PICKER_ROUTE, options, opts);
|
|
154
|
+
return response.path;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** 控制台页面板写配置的窄入口;服务端仍按组 schema 校验。 */
|
|
158
|
+
export async function setConfig(
|
|
159
|
+
groupId: string,
|
|
160
|
+
values: Record<string, ConfigValue>,
|
|
161
|
+
opts?: RequestOptions,
|
|
162
|
+
): Promise<string> {
|
|
163
|
+
const response = await post<{ result?: string }>('/api/config', { group: groupId, values }, opts);
|
|
164
|
+
return response.result || groupId;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Console 协议
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
export async function fetchManifest(opts?: RequestOptions): Promise<ConsoleManifest> {
|
|
172
|
+
return get<ConsoleManifest>(CONSOLE_MANIFEST_ROUTE, opts);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 面板数据面调用。走 POST,body 形如 `{"args":[...]}`。 */
|
|
176
|
+
export async function invokePanel<T>(
|
|
177
|
+
pageId: string,
|
|
178
|
+
panelId: string,
|
|
179
|
+
method: string,
|
|
180
|
+
args?: unknown[],
|
|
181
|
+
opts?: RequestOptions,
|
|
182
|
+
): Promise<T> {
|
|
183
|
+
return post<T>(panelRoute(pageId, panelId, method), { args: args ?? [] }, opts);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 同上,但按二进制取回(音频试听、图片这类)。 */
|
|
187
|
+
export async function invokePanelBinary(
|
|
188
|
+
pageId: string,
|
|
189
|
+
panelId: string,
|
|
190
|
+
method: string,
|
|
191
|
+
args?: unknown[],
|
|
192
|
+
opts?: RequestOptions,
|
|
193
|
+
): Promise<Blob> {
|
|
194
|
+
const init: RequestInit = {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({ args: args ?? [] }),
|
|
198
|
+
};
|
|
199
|
+
return readBlob(await send(panelRoute(pageId, panelId, method), init, opts));
|
|
200
|
+
}
|