@lynn123411/dsh-chat-translate 1.8.0 → 2.0.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.
@@ -1,39 +1,79 @@
1
+ import type { Volatile } from '@deepseek-ai/cordis';
1
2
  import type { PluginConfig } from './types.ts';
2
3
  import type { CredentialsReader } from './credentials.ts';
3
- /** Hard cap for the translation concurrency pool. */
4
- export declare const MAX_CONCURRENCY = 100;
5
- /** Bounds for the AI channel request timeout. */
6
- export declare const AI_TIMEOUT_MIN = 500;
7
- export declare const AI_TIMEOUT_MAX = 120000;
8
4
  /**
9
- * Bounds for the think-chain request timeout. A full reasoning block can take
10
- * minutes on a local model, so this budget is far larger than the short-text
11
- * AI timeout and is bounded separately.
5
+ * Bounds for the AI channel request timeout. One request carries a packed run
6
+ * of whole reply blocks, and a local model can take minutes on that much
7
+ * prose, so the budget is long-form rather than the old short-text scale.
12
8
  */
13
- export declare const THINK_TIMEOUT_MIN = 500;
14
- export declare const THINK_TIMEOUT_MAX = 900000;
15
- /** Entries kept in the think-chain cache pool, separate from the title pool. */
16
- export declare const THINK_CACHE_ENTRIES = 300;
9
+ export declare const AI_TIMEOUT_MIN = 500;
10
+ export declare const AI_TIMEOUT_MAX = 900000;
17
11
  /**
18
- * The settings namespace this plugin owns. The user-editable layer lives in
19
- * the DSH-managed document (~/.dsh/settings.yaml) under this key; the
20
- * standalone ~/.dsh/dsh-chat-translate-config.json file is legacy (<=1.1).
12
+ * The settings namespace this plugin owns. Since 0.1.7 a namespace IS the
13
+ * Profile entry id, so the user-editable layer is the `config` row of
14
+ * `dsh-chat-translate` in the active profile's patch
15
+ * (`~/.dsh/profiles/<profile>/cordis.patch.yml`). The standalone
16
+ * ~/.dsh/dsh-chat-translate-config.json file is legacy (<=1.1) and is migrated
17
+ * once at boot.
21
18
  */
22
19
  export declare const SETTINGS_NAMESPACE = "dsh-chat-translate";
23
20
  export declare const DEFAULT_CONFIG: PluginConfig;
24
21
  /**
25
- * Minimal shape of the owner scope returned by `ctx.settings.register()`.
26
- * Keeping this structural (instead of importing the DSH package) lets tests
27
- * inject an in-memory fake and keeps the bundle free of host-service code.
22
+ * Config keys an earlier release declared and this one no longer reads. DSH
23
+ * resolves unknown keys through a non-strict schema and simply ignores them, so
24
+ * a leftover from an upgraded profile would linger in the patch forever unless
25
+ * it is actively unset once.
26
+ *
27
+ * `channels` predates 1.1 and was already retired then; it rides the same list
28
+ * so one pass cleans any generation of an old profile.
29
+ */
30
+ export declare const RETIRED_CONFIG_KEYS: readonly ["channels", "concurrency", "timeoutMs", "thinkTimeoutMs", "aiEnabled", "bingEnabled", "thinkEnabled"];
31
+ /**
32
+ * Live read face of this plugin's own Config, as `ConfigManager` consumes it.
33
+ *
34
+ * Keeping this structural (instead of importing the DSH packages) lets tests
35
+ * inject an in-memory fake and keeps the host bundle free of service code. The
36
+ * plugin has no write face of its own: an accepted edit is written by the
37
+ * browser configuration form into the profile patch, and DSH commits the new
38
+ * value into the `Volatile` refs this source reads.
28
39
  */
29
- export interface SettingsScopeLike {
40
+ export interface ConfigSourceLike {
30
41
  /** Resolved value: schema defaults, then composition base, then user layer. */
31
42
  get(): PluginConfig;
32
- /** Observe resolved-value changes; returns the disposer. */
43
+ /** Observe committed live edits; returns the disposer. */
33
44
  watch(listener: (config: PluginConfig) => void): () => void;
34
- /** Merge a patch into the user layer and persist through the provider. */
35
- update(patch: Partial<PluginConfig>): Promise<unknown>;
36
45
  }
46
+ /**
47
+ * Fields of this plugin's Config as the runtime resolved them. Every field is
48
+ * declared `volatile()`, so each one is a stable ref DSH commits a live edit
49
+ * into rather than a snapshot taken at apply time.
50
+ */
51
+ export interface PluginConfigRefs {
52
+ readonly enabled: Volatile<boolean>;
53
+ readonly aiTimeoutMs: Volatile<number>;
54
+ readonly baseUrl: Volatile<string>;
55
+ readonly model: Volatile<string>;
56
+ readonly targetLang: Volatile<string>;
57
+ }
58
+ /**
59
+ * Read the current committed value of every Config field.
60
+ * @param refs - the runtime-resolved Config object.
61
+ * @returns A detached plain config value.
62
+ */
63
+ export declare function readPluginConfig(refs: PluginConfigRefs): PluginConfig;
64
+ /**
65
+ * Adapt this plugin's runtime-resolved Config into the live source
66
+ * {@link ConfigManager} consumes.
67
+ *
68
+ * DSH commits an accepted live edit into the `Volatile` refs in place, then
69
+ * emits `loader/volatile-update` on the owning fiber — the listener is scoped
70
+ * to this plugin's own entry, so one subscription covers every field.
71
+ *
72
+ * @param onVolatileUpdate - subscribes to this plugin's volatile-commit event.
73
+ * @param refs - the runtime-resolved Config object.
74
+ * @returns The live read/watch source.
75
+ */
76
+ export declare function createLiveConfigSource(onVolatileUpdate: (listener: () => void) => () => void, refs: PluginConfigRefs): ConfigSourceLike;
37
77
  /**
38
78
  * One path-addressed edit to a settings namespace's user section, matching
39
79
  * DSH's `SettingsPathOp` wire shape.
@@ -47,9 +87,8 @@ export type SettingsPathOpLike = {
47
87
  path: string[];
48
88
  };
49
89
  /**
50
- * Provider-level write face the legacy migration needs: the settings service
51
- * itself (not the per-namespace owner scope), whose `mutate` applies path ops
52
- * under one revision fence.
90
+ * Provider-level write face the legacy migration needs: DSH's `ctx.settings`
91
+ * service itself, whose `mutate` applies path ops under one revision fence.
53
92
  */
54
93
  export interface SettingsMigrationTarget {
55
94
  describe(): Array<{
@@ -60,29 +99,18 @@ export interface SettingsMigrationTarget {
60
99
  mutate(ns: string, ops: readonly SettingsPathOpLike[], expectedRevision?: number): Promise<unknown>;
61
100
  }
62
101
  /**
63
- * Config facade over the DSH `ctx.settings` service. No file I/O lives here
102
+ * Config facade over this plugin's own live Config. No file I/O lives here
64
103
  * anymore: persistence, atomic writes, external-edit hot reload and the
65
- * browser-facing describe/mutate API are all owned by DSH itself.
104
+ * browser-facing edit API are all owned by DSH itself.
66
105
  */
67
106
  export declare class ConfigManager {
68
- private scope;
107
+ private source;
69
108
  private credentials;
70
- constructor(scope: SettingsScopeLike, credentials: CredentialsReader);
109
+ constructor(source: ConfigSourceLike, credentials: CredentialsReader);
71
110
  getConfig(): PluginConfig;
72
111
  /** Whether the AI channel has every required piece: baseUrl, model and key. */
73
112
  isAiConfigured(): boolean;
74
113
  onConfigChange(listener: (config: PluginConfig) => void): () => void;
75
- /**
76
- * Merge a partial update into the settings namespace. Values are sanitized
77
- * here (bounds, trimming) so the schema's own constraints act as a second
78
- * line of defence rather than the only one.
79
- *
80
- * @internal Test-only. No production path calls this: the host writes the
81
- * settings namespace from the browser through the DSH settings service, and
82
- * this facade is only driven by `scripts/test-*.mjs` (regression suite).
83
- * Kept (with this marker) so those tests keep exercising the sanitizer.
84
- */
85
- updateConfig(partial: Partial<PluginConfig>): Promise<PluginConfig>;
86
114
  }
87
115
  /**
88
116
  * Coerce a raw record (legacy config file, HTTP-era partials) into a
@@ -91,6 +119,19 @@ export declare class ConfigManager {
91
119
  * so one bad field never takes down a whole migration.
92
120
  */
93
121
  export declare function sanitizePatch(input: Record<string, unknown>): Partial<PluginConfig>;
122
+ /**
123
+ * One-shot retirement of config keys an earlier release declared. DSH hands
124
+ * unknown keys through rather than rejecting them, so without this pass a
125
+ * profile that predates the reply-body rewrite keeps dead keys forever.
126
+ *
127
+ * A tolerant, per-key unset: only keys the user layer actually carries are
128
+ * written, and a failed write is logged and retried on the next boot rather
129
+ * than taking down plugin load.
130
+ *
131
+ * @param settings - DSH's own settings service (the `mutate` write face).
132
+ * @returns whether any retired key was removed.
133
+ */
134
+ export declare function retireRemovedConfigKeys(settings: SettingsMigrationTarget): Promise<boolean>;
94
135
  /**
95
136
  * One-shot migration from the pre-1.2 standalone config file. Runs only while
96
137
  * the settings namespace has no user layer yet, so values the user edited
@@ -1,66 +1,53 @@
1
- import type { ThinkBlockResult, TranslateItemResult } from './types.ts';
1
+ import type { ReplyBlockResult } from './types.ts';
2
2
  import type { ConfigManager } from './config.ts';
3
3
  import { LruDiskCache } from './cache.ts';
4
4
  import type { KeyReader } from './credentials.ts';
5
5
  export declare class TranslationDispatcher {
6
6
  private configManager;
7
7
  private cache;
8
- /** 思考正文译文的独立缓存池,与工具标题的池互不挤占。 */
9
- private thinkCache;
10
8
  private credentials;
11
9
  private masking;
12
10
  private adapters;
13
11
  private circuitStates;
14
- private inFlightMap;
15
- private activeCount;
16
- private queue;
17
- /** 思考链请求的串行队列尾,保证同时最多一个在途请求。 */
18
- private thinkTail;
19
- constructor(configManager: ConfigManager, cache: LruDiskCache, credentials?: KeyReader, thinkCache?: LruDiskCache);
12
+ /** 正文请求的串行队列尾,保证同时最多一个在途请求。 */
13
+ private replyTail;
14
+ constructor(configManager: ConfigManager, cache: LruDiskCache, credentials?: KeyReader);
20
15
  private registerAdapter;
21
16
  /**
22
- * Decide which channels are active for the current config, in priority order.
17
+ * 翻译回答正文的块:本次唯一的翻译入口。
23
18
  *
24
- * Truth table (user contract):
25
- * - AI on + configured + Bing on -> [openai, bing] (AI first, Bing fallback)
26
- * - AI on + NOT configured + Bing on -> [bing]
27
- * - AI on + NOT configured + Bing off -> [] (no translation)
28
- * - AI off + Bing on -> [bing]
29
- * - AI off + Bing off -> [] (no translation)
30
- */
31
- private computeChannels;
32
- translateBatch(texts: string[], forceRefresh?: boolean): Promise<TranslateItemResult[]>;
33
- translateOne(rawText: string, forceRefresh?: boolean): Promise<TranslateItemResult>;
34
- /**
35
- * 翻译思考正文的块。整条链路与工具标题翻译分离:只走 AI 通道、独立串行
36
- * 队列、独立超时、独立缓存池,Bing 从不参与。
19
+ * 每个块先按输入上限切成片段,再各自掩码、相邻片段打包成一个请求;整批失败
20
+ * 时退回逐片段单发。调用方传进来的每个块要么整块译出、要么整块保持原文:
21
+ * 任一片段缺失(含掩码还原不通过)都让该块作废,避免半中半英的段落。
37
22
  *
38
- * 每个块先按输入上限切成片段,再各自掩码、相邻片段打包成一个请求;整批
39
- * 失败时退回逐片段单发,仍失败的片段保留原文。块内任一片段失败即整块不译,
40
- * 避免半中半英的段落。
23
+ * 客户端按 2048 估算 token 分块,所以一个 markdown 块可能跨多次调用;每次
24
+ * 调用都独立决定成败,不会出现「前一段已挂译文、后一段失败」的半截结果。
41
25
  */
42
- translateThinkBlocks(blocks: string[]): Promise<ThinkBlockResult[]>;
26
+ translateReplyBlocks(blocks: string[]): Promise<ReplyBlockResult[]>;
43
27
  /**
44
28
  * 一整批一次请求;失败则该批逐片段单发重试一次,仍失败的片段直接放弃
45
- * (保留原文),不在界面上留提示。
29
+ * (保留原文),不在界面上留提示。失败的片段不进缓存,因此下一次滚回视口
30
+ * 时会自然重试。
31
+ *
32
+ * 熔断记账按「批」算:一次批请求失败记一次,逐片段重试失败再各记一次。
46
33
  */
47
- private translateThinkBatch;
48
- /** 发出一次思考链请求并把结果还原成每个片段的最终译文。 */
49
- private requestThinkBatch;
50
- /** 串行执行器:同时最多一个在途请求,与工具标题的并发池完全独立。 */
51
- private runThinkSerial;
34
+ private translateReplyBatch;
35
+ /** 发出一次正文请求并把结果还原成每个片段的最终译文。 */
36
+ private requestReplyBatch;
37
+ /** 串行执行器:同时最多一个在途请求。 */
38
+ private runReplySerial;
52
39
  /**
53
- * 只读判断某通道是否还在熔断冷却期。思考链翻译只借用这个判断避开已经
54
- * 出问题的服务,不改动熔断状态本身(那是工具标题通道的记账)。
40
+ * 通道探针:设置面板的「测试 AI 通道」按钮走这里。探测文本用一句英文正文,
41
+ * 与真实请求同形。
42
+ *
43
+ * 探针是用户主动发起的连通性检查,因此不写也不读熔断账本:连点几次测试
44
+ * 不该把正文翻译挡在冷却期外,正文的失败也不该让按钮变哑。
55
45
  */
56
- private isChannelCoolingDown;
57
46
  testChannel(channelId: string): Promise<{
58
47
  ok: boolean;
59
48
  latencyMs: number;
60
49
  error?: string;
61
50
  }>;
62
- private enqueueTask;
63
- private processNext;
64
51
  private isCircuitOpen;
65
52
  private recordSuccess;
66
53
  private recordFailure;
@@ -1,10 +1,10 @@
1
1
  /**
2
- * 思考正文翻译的请求预算。每次请求的输入估算 token 上限,与本地服务的
2
+ * 回答正文翻译的请求预算。每次请求的输入估算 token 上限,与本地服务的
3
3
  * prefill 批次(MAX_NUM_BATCHED_TOKENS)保持同一量级;输出上限写进请求体的
4
4
  * max_tokens,避免长文本把剩余上下文全部花在生成上。
5
5
  */
6
- export declare const THINK_MAX_INPUT_TOKENS = 4096;
7
- export declare const THINK_MAX_OUTPUT_TOKENS = 8192;
6
+ export declare const REPLY_MAX_INPUT_TOKENS = 4096;
7
+ export declare const REPLY_MAX_OUTPUT_TOKENS = 8192;
8
8
  /**
9
9
  * 保守估算一段文本占用的 token 数:汉字一字一 token,其余每三个字符一 token。
10
10
  * 实测本机 hy-mt2-1.8b 的英文语料约为每 3.3 字符一 token,这里取 3 作为上界,
@@ -17,7 +17,7 @@ export declare function estimateTokens(text: string): number;
17
17
  */
18
18
  export declare function splitOversizedBlock(text: string, maxTokens?: number): string[];
19
19
  /** 一个待翻译的片段:属于哪个块、块内第几段、以及该片段的掩码结果。 */
20
- export interface ThinkPieceShell<TMask> {
20
+ export interface ReplyPieceShell<TMask> {
21
21
  block: number;
22
22
  index: number;
23
23
  text: string;
@@ -31,19 +31,19 @@ export declare function packPieces<T extends {
31
31
  * 块分隔标记:`⟪<4 letters><index>⟫`。外括号刻意与掩码占位符的 ⟦⟧ 不同形,
32
32
  * 掩码残留检测(只认 ⟦⟧)因此不会把这个标记当成泄漏。
33
33
  */
34
- export interface ThinkBatchFormat {
34
+ export interface BatchFormat {
35
35
  id: string;
36
36
  token: (index: number) => string;
37
37
  }
38
- export declare function createThinkBatchFormat(): ThinkBatchFormat;
38
+ export declare function createBatchFormat(): BatchFormat;
39
39
  /** 每个片段以单独一行标记开头,段与段之间空一行。 */
40
- export declare function buildBatchPayload(pieces: string[], format: ThinkBatchFormat): string;
40
+ export declare function buildBatchPayload(pieces: string[], format: BatchFormat): string;
41
41
  /**
42
42
  * 按标记把整批译文切回每个片段。
43
43
  *
44
44
  * 序号必须恰好出现一次、按 0..count-1 升序、每段非空,且不得混入其它批次
45
45
  * 的标记;任何一条不满足都返回 null,由调用方作废整批并退回单块重试。
46
46
  */
47
- export declare function splitBatchTranslation(translated: string, format: ThinkBatchFormat, count: number): string[] | null;
47
+ export declare function splitBatchTranslation(translated: string, format: BatchFormat, count: number): string[] | null;
48
48
  /** 翻译结果里是否还留着块标记(用于判定整批作废)。 */
49
- export declare function hasThinkBatchResidue(text: string): boolean;
49
+ export declare function hasBatchResidue(text: string): boolean;
@@ -5,17 +5,15 @@ import type { TranslationDispatcher } from './dispatcher.ts';
5
5
  * the shared `/api` channel.
6
6
  *
7
7
  * Config and credentials have no HTTP endpoints: since 1.2 the settings panel
8
- * reads and writes through DSH's own channels — the client `SettingsScope`
9
- * service and the `credentials` Remote API — so the plugin owns exactly three
10
- * routes: short-text batch translation, think-chain block translation, and the
11
- * channel probe.
8
+ * reads and writes through DSH's own channels — the shared configuration form
9
+ * (`ctx.configForms`) over the profile entry, and the `credentials` Remote API
10
+ * for the key — so the plugin owns exactly two routes: reply-body block
11
+ * translation, and the channel probe.
12
12
  */
13
- /** Batch-translation route path. */
14
- export declare const TRANSLATE_ROUTE_PATH = "/api/dsh-chat-translate/translate";
13
+ /** Reply-body block translation route path. */
14
+ export declare const REPLY_ROUTE_PATH = "/api/dsh-chat-translate/translate-reply";
15
15
  /** Single-channel probe route path. */
16
16
  export declare const TEST_CHANNEL_ROUTE_PATH = "/api/dsh-chat-translate/test-channel";
17
- /** Think-chain block translation route path. */
18
- export declare const THINK_ROUTE_PATH = "/api/dsh-chat-translate/translate-think";
19
17
  /**
20
18
  * Build the plugin's exact Fetch routes.
21
19
  * @param dispatcher - translation coordinator the routes proxy to.
@@ -1,12 +1,6 @@
1
1
  export interface PluginConfig {
2
2
  enabled: boolean;
3
- concurrency: number;
4
- timeoutMs: number;
5
3
  aiTimeoutMs: number;
6
- thinkTimeoutMs: number;
7
- aiEnabled: boolean;
8
- bingEnabled: boolean;
9
- thinkEnabled: boolean;
10
4
  baseUrl: string;
11
5
  model: string;
12
6
  targetLang: string;
@@ -35,8 +29,8 @@ export interface ITranslationAdapter {
35
29
  isAvailable(config: PluginConfig): boolean;
36
30
  translate(text: string, signal: AbortSignal, config: PluginConfig, options?: TranslateAdapterOptions): Promise<string>;
37
31
  }
38
- /** One reasoning block's outcome, aligned by index with the request's block list. */
39
- export interface ThinkBlockResult {
32
+ /** One reply block's outcome, aligned by index with the request's block list. */
33
+ export interface ReplyBlockResult {
40
34
  original: string;
41
35
  translated: string;
42
36
  ok: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lynn123411/dsh-chat-translate",
3
- "version": "1.8.0",
4
- "description": "Tool-call & think-chain translation with dual AI (OpenAI-compatible) + Bing channels for the DeepSeek Harness Web UI",
3
+ "version": "2.0.0",
4
+ "description": "Assistant reply-body translation for the DeepSeek Harness Web UI (OpenAI-compatible channel)",
5
5
  "license": "MIT",
6
6
  "author": "lynn123411",
7
7
  "repository": {
@@ -61,23 +61,23 @@
61
61
  "scripts": {
62
62
  "build": "node build.mjs",
63
63
  "typecheck": "tsc -p tsconfig.json --noEmit",
64
- "test": "node scripts/test-client-wiring.mjs && node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs && node scripts/test-think-pipeline.mjs && node scripts/test-think-client.mjs",
64
+ "test": "node scripts/test-client-wiring.mjs && node scripts/test-regression-suite.mjs && node scripts/test-channel-logic.mjs && node scripts/test-store.mjs && node scripts/test-toggle-restore.mjs && node scripts/test-reply-pipeline.mjs && node scripts/test-reply-client.mjs",
65
65
  "assert:lib-untracked": "node scripts/assert-lib-untracked.mjs",
66
66
  "check": "pnpm typecheck && pnpm build && pnpm test && pnpm assert:lib-untracked"
67
67
  },
68
68
  "engines": {
69
69
  "node": "^22.19 || >=24",
70
- "dsh": "0.1.6-alpha.2"
70
+ "dsh": "0.1.7-rc.2"
71
71
  },
72
72
  "devDependencies": {
73
- "@deepseek-ai/cordis": "4.0.2",
74
- "@deepseek-ai/dsh-client-connection": "0.1.6-alpha.2",
75
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
76
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.6-alpha.2",
77
- "@deepseek-ai/dsh-client-ui-settings": "0.1.6-alpha.2",
78
- "@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.2",
79
- "@deepseek-ai/dsh-home-paths": "0.1.6-alpha.2",
80
- "@deepseek-ai/schemastery": "^3.18.2",
73
+ "@deepseek-ai/cordis": "^4.0.4",
74
+ "@deepseek-ai/dsh-client-connection": "0.1.7-rc.2",
75
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
76
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
77
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.2",
78
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
79
+ "@deepseek-ai/dsh-home-paths": "0.1.7-rc.2",
80
+ "@deepseek-ai/schemastery": "^3.18.4",
81
81
  "@eslint/js": "^9.30.0",
82
82
  "@types/node": "^22.19.0",
83
83
  "@types/react": "^19.2.18",
@@ -86,8 +86,8 @@
86
86
  "typescript": "^6.0.0"
87
87
  },
88
88
  "peerDependencies": {
89
- "@deepseek-ai/dsh-client-ui-primitives": "*",
90
- "@deepseek-ai/dsh-home-paths": "0.1.6-alpha.2"
89
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2",
90
+ "@deepseek-ai/dsh-home-paths": "0.1.7-rc.2"
91
91
  },
92
92
  "peerDependenciesMeta": {
93
93
  "@deepseek-ai/dsh-client-ui-primitives": {
@@ -1,19 +0,0 @@
1
- /**
2
- * 思考卡标题右侧的翻译按钮。
3
- *
4
- * 三态:idle(描边「译」字,点击开始翻译)、working(圈内圆环旋转,点击忽略)、
5
- * translated(实心高亮,点击整条切回原文)。样式只在首次使用注入一次,配色沿用
6
- * 内核的 dsw-* 变量;系统开启「减弱动态效果」时圆环不旋转。
7
- */
8
- export type ThinkButtonState = 'idle' | 'working' | 'translated';
9
- export declare const THINK_BUTTON_CLASS = "dsh-tidy-think-button";
10
- /** 注入按钮样式,重复调用无副作用。 */
11
- export declare function ensureThinkButtonStyles(): void;
12
- export interface ThinkButtonHandle {
13
- element: HTMLButtonElement;
14
- getState(): ThinkButtonState;
15
- setState(state: ThinkButtonState): void;
16
- setDisabled(disabled: boolean, reason: string): void;
17
- }
18
- /** 创建一个按钮实例;onActivate 由调用方决定点下去做什么。 */
19
- export declare function createThinkButton(onActivate: () => void): ThinkButtonHandle;
@@ -1,55 +0,0 @@
1
- /** 稳定的翻译单位:一块级元素,或一段行内节点。 */
2
- export interface ThinkUnit {
3
- element?: HTMLElement;
4
- nodes?: Node[];
5
- text: string;
6
- anchor: Node;
7
- }
8
- /** 收集正文里全部可翻译单位,跳过已挂译文的部分与代码块。 */
9
- export declare function collectThinkUnits(body: HTMLElement): ThinkUnit[];
10
- /** 按估算 token 把单位切成若干次客户端请求,顺序不变。 */
11
- export declare function chunkUnits(units: ThinkUnit[], maxTokens?: number): ThinkUnit[][];
12
- declare class ThinkTranslateController {
13
- /** 总开关与设置里的思考链开关同时打开。 */
14
- private enabled;
15
- /** AI 通道开关打开且已配置;缺任何一条都不注入按钮。 */
16
- private configured;
17
- private states;
18
- private syncTimers;
19
- /** 只处理当前会话的容器;由观察器在会话切换时更新。 */
20
- private scope;
21
- /** 观察器把当前会话的根交给控制器,插入按钮时只看这一棵子树。 */
22
- setScope(scope: ParentNode): void;
23
- isEnabled(): boolean;
24
- setEnabled(enabled: boolean): void;
25
- setConfigured(configured: boolean): void;
26
- /** 按钮是否可以存在:两个开关与 AI 通道配置缺一不可。 */
27
- private usable;
28
- private applyVisibility;
29
- /** 扫描当前会话里的全部思考卡(开关、配置或会话变化后调用)。 */
30
- syncAll(): void;
31
- /** 防抖后同步一张卡片。 */
32
- syncCard(card: HTMLElement): void;
33
- private stateFor;
34
- private runSync;
35
- private updateDisabled;
36
- private ensureButton;
37
- /** 折叠或停用时撤掉按钮,卡片的翻译状态不变。 */
38
- private removeButton;
39
- /** 插件停用:撤掉按钮并还原这张卡片上的全部译文。 */
40
- private discardCard;
41
- /** 卡片当前显示的是译文(至少有一个译文块可见)。 */
42
- private isShowingTranslation;
43
- private bodyOf;
44
- /** 展开时若整条都命中缓存,直接显示中文,不点按钮也不发请求。 */
45
- private showCachedWhenComplete;
46
- /** 按钮被点击;按钮只在展开的卡片上存在。 */
47
- private activate;
48
- /** 翻译这张卡片的全部单位:命中缓存的先挂上,其余按块请求。 */
49
- private translateCard;
50
- private translateUnits;
51
- /** 把译文挂到单位上;期间文本或节点已经变动的单位直接放弃(结果已进缓存)。 */
52
- private applyUnit;
53
- }
54
- export declare const thinkTranslator: ThinkTranslateController;
55
- export {};
@@ -1,43 +0,0 @@
1
- /**
2
- * StreamDebounceViewportObserver
3
- *
4
- * Combines:
5
- * 1. True IntersectionObserver for viewport visibility detection (rootMargin: '150px 0px').
6
- * 2. Typing/streaming debounce (default 400ms) to avoid translating partial streaming sentences.
7
- * 3. Batch queuing to group multiple visible elements into efficient batch requests.
8
- *
9
- */
10
- export interface ViewportObserverOptions {
11
- rootMargin?: string;
12
- debounceMs?: number;
13
- onVisibleBatch: (items: Array<{
14
- element: HTMLElement;
15
- text: string;
16
- }>) => void;
17
- }
18
- export declare class StreamDebounceViewportObserver {
19
- private intersectionObserver;
20
- /**
21
- * Pending per-element streaming debounce timers. A Map (not a WeakMap)
22
- * because disconnect() must enumerate and clear every pending timer: a
23
- * WeakMap cannot be iterated, so a debounce armed just before the switch was
24
- * turned off would still fire register() afterwards.
25
- */
26
- private streamingTimers;
27
- private pendingQueue;
28
- private batchFlushTimer;
29
- private options;
30
- constructor(options: ViewportObserverOptions);
31
- private initIntersectionObserver;
32
- /**
33
- * Observe an element with streaming debounce.
34
- * If streaming updates characterData repeatedly within debounceMs, the timer resets.
35
- */
36
- observeWithDebounce(element: HTMLElement, text: string, immediate?: boolean): void;
37
- private armDebounce;
38
- private register;
39
- private enqueueBatch;
40
- private flushQueue;
41
- unobserve(element: HTMLElement): void;
42
- disconnect(): void;
43
- }
@@ -1,15 +0,0 @@
1
- import type { ITranslationAdapter, PluginConfig } from './base.ts';
2
- interface BingTokens {
3
- ig: string;
4
- key: string;
5
- token: string;
6
- }
7
- export declare function fetchTokens(signal: AbortSignal, forceRefresh?: boolean): Promise<BingTokens>;
8
- export declare class BingWebAdapter implements ITranslationAdapter {
9
- readonly id = "bing";
10
- readonly name = "\u5FAE\u8F6F Bing \u7F51\u9875\u7FFB\u8BD1 (\u514DKey\u76F4\u8FDE)";
11
- isAvailable(_config: PluginConfig): boolean;
12
- translate(text: string, signal: AbortSignal, config: PluginConfig): Promise<string>;
13
- private executeTranslate;
14
- }
15
- export {};