dsh-lcx-codex 0.4.2 → 0.4.3-pre.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +116 -202
  2. package/cordis.patch.yml +3 -20
  3. package/lib/client.js +273 -146
  4. package/lib/compact-v2.js +218 -199
  5. package/lib/dsh-compat.js +227 -100
  6. package/lib/dsh-responses.js +445 -277
  7. package/lib/index.js +851 -757
  8. package/lib/json-store.js +50 -31
  9. package/lib/native-checkpoint.js +520 -194
  10. package/lib/responses-request.js +106 -121
  11. package/lib/responses-stream.js +972 -443
  12. package/lib/route.js +229 -355
  13. package/lib/service-mutex.js +73 -64
  14. package/lib/token-budget.js +176 -108
  15. package/lib/transport.js +277 -67
  16. package/lib/types/client/index.d.ts +6 -0
  17. package/lib/types/compact-v2.d.ts +104 -0
  18. package/lib/types/dsh-compat.d.ts +78 -0
  19. package/lib/types/dsh-responses.d.ts +82 -0
  20. package/lib/types/index.d.ts +83 -0
  21. package/lib/types/json-store.d.ts +10 -0
  22. package/lib/types/native-checkpoint.d.ts +213 -0
  23. package/lib/types/responses-request.d.ts +58 -0
  24. package/lib/types/responses-stream.d.ts +51 -0
  25. package/lib/types/route.d.ts +132 -0
  26. package/lib/types/service-mutex.d.ts +14 -0
  27. package/lib/types/token-budget.d.ts +50 -0
  28. package/lib/types/transport.d.ts +20 -0
  29. package/lib/types/web-run-output.d.ts +29 -0
  30. package/lib/types/web-search-alpha.d.ts +286 -0
  31. package/lib/types/web-search-capability.d.ts +26 -0
  32. package/lib/types/web-search-hosted.d.ts +246 -0
  33. package/lib/types/web-search-ref-store.d.ts +22 -0
  34. package/lib/web-run-output.js +167 -18
  35. package/lib/web-search-alpha.js +865 -163
  36. package/lib/web-search-capability.js +55 -65
  37. package/lib/web-search-hosted.js +210 -32
  38. package/lib/web-search-ref-store.js +63 -59
  39. package/package.json +79 -27
  40. package/ARCHITECTURE.md +0 -117
  41. package/CHANGELOG.md +0 -224
  42. package/README_EN.md +0 -277
  43. package/assets/dsh-lcx-codex-banner.jpg +0 -0
  44. package/lib/legacy-v3.js +0 -20
  45. package/lib/responses-replay.js +0 -68
  46. package/scripts/probe-alpha.mjs +0 -43
  47. package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/client.js CHANGED
@@ -1,168 +1,295 @@
1
+ // src/client/index.tsx
1
2
  window.__ModuleLoader__.load({
2
- id: 'dsh-lcx-codex',
3
- factory: (require) => {
4
- const module = { exports: {} }
5
- const exports = module.exports
6
- const React = require('react')
7
- const { createSnapshotStore } = require('@deepseek-ai/dsh-client-runtime/client')
8
- const NAMESPACE = 'lcx-codex'
9
- const DEFAULTS = {
10
- enabled: false,
11
- webSearch: false,
12
- advancedHostedSearch: false,
13
- alphaSearch: false,
14
- remoteCompaction: false,
15
- fallbackToBasicCompaction: true,
16
- autoCompaction: true,
17
- webSearchTimeoutSeconds: 240,
18
- autoCompactionThresholdPercent: 90,
19
- emergencyPruneThresholdPercent: 95,
20
- provider: 'lcx',
21
- baseURL: 'https://api.lcxbot.com/v1',
22
- apiKeyEnv: 'LCX_API_KEY',
23
- model: 'gpt-5.6-sol',
24
- }
25
- const css = `.lcx-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;list-style:none}.lcx-head{width:100%;display:flex;justify-content:space-between;padding:14px 16px;border:0;background:transparent;color:inherit}.lcx-body{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 16px}.lcx-row{display:flex;gap:9px;padding:8px 0}.lcx-row small,.lcx-help,.lcx-field small{display:block;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.lcx-fields{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px}.lcx-field{display:flex;flex-direction:column;gap:4px}.lcx-input{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px;background:transparent;color:inherit}.lcx-foot{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.lcx-foot button{padding:6px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:inherit}`
26
- if (typeof document !== 'undefined' && !document.querySelector('style[data-plugin-css="dsh-lcx-codex"]')) {
27
- const tag = document.createElement('style')
28
- tag.dataset.pluginCss = 'dsh-lcx-codex'
29
- tag.textContent = css
30
- document.head.appendChild(tag)
3
+ id: "dsh-lcx-codex",
4
+ factory: (require2) => {
5
+ const module = { exports: {} };
6
+ const exports = module.exports;
7
+ const React = require2("react");
8
+ const { createSnapshotStore } = require2(
9
+ "@deepseek-ai/dsh-client-store"
10
+ );
11
+ const NAMESPACE = "lcx-codex";
12
+ const FIELDS = [
13
+ "enabled",
14
+ "webSearch",
15
+ "advancedHostedSearch",
16
+ "alphaSearch"
17
+ ];
18
+ const DEFAULTS = Object.fromEntries(
19
+ FIELDS.map((field) => [field, false])
20
+ );
21
+ const css = `.lcx-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;list-style:none}.lcx-head{width:100%;display:flex;justify-content:space-between;padding:14px 16px;border:0;background:transparent;color:inherit}.lcx-body{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 16px}.lcx-row{display:flex;gap:9px;padding:8px 0}.lcx-row small,.lcx-help{display:block;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.lcx-foot{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.lcx-foot button{padding:6px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:inherit}`;
22
+ if (typeof document !== "undefined" && !document.querySelector('style[data-plugin-css="dsh-lcx-codex"]')) {
23
+ const tag = document.createElement("style");
24
+ tag.dataset.pluginCss = "dsh-lcx-codex";
25
+ tag.textContent = css;
26
+ document.head.appendChild(tag);
31
27
  }
32
28
  const copy = {
33
29
  zh: {
34
- title: 'Responses / Codex 能力',
35
- desc: 'LCX 开启后从第一轮普通请求开始接管当前 GPT Responses 会话,并统一管理普通请求、工具、Native V2 压缩和续接。',
36
- enabled: '启用 LCX(接管当前 GPT Responses 会话)',
37
- enabledHelp: '切换 Claude、Gemini、DeepSeek 等非 GPT 模型前先关闭 LCX;模型切换本身不需要重启 DSH。',
38
- web: '使用 GPT Hosted Search 作为 DSH web_search 后端',
39
- webHelp: '不会新增第二个普通搜索工具;普通 web_search 自动跟随当前 Agent GPT Responses 模型。',
40
- searchTimeout: 'web_search 超时(秒)',
41
- searchTimeoutHelp: '只调整 DSH 工具外层超时,不改变模型可见 schema。默认 240 秒;Hosted Search 较慢时避免 60 秒提前中断。',
42
- advanced: '启用高级 Hosted 工具(websearch_gpt_advanced)',
43
- advancedHelp: '只在需要域名过滤、位置、search context、图片等原生 Hosted 参数时使用;默认关闭以保持工具 schema 稳定。',
44
- alpha: '启用 Alpha command(websearch_alpha)',
45
- alphaHelp: '仅 capability probe 对当前 endpoint/provider/model/schema 验证通过后才真正注册。',
46
- autoCompact: '启用 Native-first 自动压缩',
47
- autoCompactHelp: '到主动阈值前不让 DSH 的 80% tool-result prune 改写历史;到主动阈值后优先 Native V2。',
48
- autoThreshold: 'Native 自动压缩阈值(%)',
49
- autoThresholdHelp: '建议 90%。允许 85–95%;262k 窗口在 90% 时仍约剩 26k tokens。',
50
- emergencyThreshold: 'DSH 紧急 prune 阈值(%)',
51
- emergencyThresholdHelp: '建议 95%。达到这里才允许 DSH tool-result-pruner 先救场;必须高于 Native 阈值。',
52
- fallback: 'Native 失败后回退 DSH basic compaction',
53
- fallbackHelp: 'Remote-first:只有 Native 请求失败才调用 basic summary,不并行双跑。',
54
- endpoint: 'Responses 地址',
55
- model: 'GPT 模型',
56
- save: '保存', discard: '放弃修改', saving: '保存中…',
30
+ title: "Responses / Codex \u80FD\u529B",
31
+ desc: "LCX \u8DDF\u968F DSH \u5F53\u524D\u4F1A\u8BDD\u9009\u62E9\u7684 GPT Responses route\uFF1B\u6A21\u578B\u3001endpoint \u548C credential \u7EE7\u7EED\u53EA\u7531 DSH \u7BA1\u7406\u3002",
32
+ enabled: "\u542F\u7528 LCX\uFF08\u63A5\u7BA1\u5F53\u524D GPT Responses \u4F1A\u8BDD\uFF09",
33
+ enabledHelp: "\u5207\u6362 Claude\u3001Gemini\u3001DeepSeek \u7B49\u975E GPT \u6A21\u578B\u524D\u5148\u5173\u95ED LCX\uFF1B\u6A21\u578B\u5207\u6362\u672C\u8EAB\u4E0D\u9700\u8981\u91CD\u542F DSH\u3002",
34
+ web: "\u4F7F\u7528 GPT Hosted Search \u4F5C\u4E3A DSH web_search \u540E\u7AEF",
35
+ webHelp: "\u4E0D\u4F1A\u65B0\u589E\u7B2C\u4E8C\u4E2A\u666E\u901A\u641C\u7D22\u5DE5\u5177\uFF1B\u666E\u901A web_search \u81EA\u52A8\u8DDF\u968F\u5F53\u524D Agent \u7684 GPT Responses route\u3002",
36
+ advanced: "\u542F\u7528\u9AD8\u7EA7 Hosted \u5DE5\u5177\uFF08websearch_gpt_advanced\uFF09",
37
+ advancedHelp: "\u53EA\u5728\u9700\u8981\u57DF\u540D\u8FC7\u6EE4\u3001\u4F4D\u7F6E\u3001search context\u3001\u56FE\u7247\u7B49\u539F\u751F Hosted \u53C2\u6570\u65F6\u4F7F\u7528\uFF1B\u9ED8\u8BA4\u5173\u95ED\u4EE5\u4FDD\u6301\u5DE5\u5177 schema \u7A33\u5B9A\u3002",
38
+ alpha: "\u542F\u7528 Alpha command\uFF08websearch_alpha\uFF09",
39
+ alphaHelp: "\u4EC5 capability probe \u5BF9\u5F53\u524D route/schema \u9A8C\u8BC1\u901A\u8FC7\u540E\u624D\u771F\u6B63\u6CE8\u518C\u3002",
40
+ save: "\u4FDD\u5B58",
41
+ discard: "\u653E\u5F03\u4FEE\u6539",
42
+ saving: "\u4FDD\u5B58\u4E2D\u2026",
43
+ saveError: "\u672A\u80FD\u786E\u8BA4\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u4FEE\u6539\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u91CD\u8BD5\u6216\u653E\u5F03\u4FEE\u6539\u4EE5\u67E5\u770B\u5F53\u524D\u8BBE\u7F6E\u3002"
57
44
  },
58
45
  en: {
59
- title: 'Responses / Codex capabilities',
60
- desc: 'When enabled, LCX owns the selected GPT Responses conversation from the first ordinary turn through tools, Native V2 compaction and continuation.',
61
- enabled: 'Enable LCX (own current GPT Responses conversation)',
62
- enabledHelp: 'Turn LCX off before switching to Claude, Gemini, DeepSeek, or another non-GPT model. Model switching itself does not require a DSH restart.',
63
- web: 'Use GPT Hosted Search as DSH web_search backend',
64
- webHelp: 'Keeps DSH web_search as the single ordinary search tool and follows the active Agent GPT Responses model.',
65
- searchTimeout: 'web_search timeout (seconds)',
66
- searchTimeoutHelp: 'Changes only the DSH tool deadline, not the model-visible schema. Default 240s to avoid premature 60s timeouts.',
67
- advanced: 'Enable advanced Hosted tool (websearch_gpt_advanced)',
68
- advancedHelp: 'Only for native Hosted controls such as domains, location, context size and image search; off by default for stable tool schemas.',
69
- alpha: 'Enable Alpha command (websearch_alpha)',
70
- alphaHelp: 'Registered only after a matching capability probe.',
71
- autoCompact: 'Enable Native-first automatic compaction',
72
- autoCompactHelp: 'Suppresses the stock 80% tool-result prune before the proactive threshold, then prefers Native V2.',
73
- autoThreshold: 'Native auto-compaction threshold (%)',
74
- autoThresholdHelp: 'Default 90%. Allowed 85–95%.',
75
- emergencyThreshold: 'DSH emergency prune threshold (%)',
76
- emergencyThresholdHelp: 'Default 95%. DSH tool-result pruning is allowed only in this emergency zone; must exceed the Native threshold.',
77
- fallback: 'Fall back to DSH basic compaction on Native failure',
78
- fallbackHelp: 'Remote-first: basic summary runs only after Native failure, never in parallel.',
79
- endpoint: 'Responses endpoint', model: 'GPT model',
80
- save: 'Save', discard: 'Discard', saving: 'Saving…',
81
- },
46
+ title: "Responses / Codex capabilities",
47
+ desc: "LCX follows the GPT Responses route selected by the current DSH session. Model, endpoint and credentials remain DSH-owned.",
48
+ enabled: "Enable LCX (own current GPT Responses conversation)",
49
+ enabledHelp: "Turn LCX off before switching to Claude, Gemini, DeepSeek, or another non-GPT model. Model switching itself does not require a DSH restart.",
50
+ web: "Use GPT Hosted Search as DSH web_search backend",
51
+ webHelp: "Keeps DSH web_search as the single ordinary search tool and follows the active Agent GPT Responses route.",
52
+ advanced: "Enable advanced Hosted tool (websearch_gpt_advanced)",
53
+ advancedHelp: "Only for native Hosted controls such as domains, location, context size and image search; off by default for stable tool schemas.",
54
+ alpha: "Enable Alpha command (websearch_alpha)",
55
+ alphaHelp: "Registered only after a matching capability probe.",
56
+ save: "Save",
57
+ discard: "Discard",
58
+ saving: "Saving\u2026",
59
+ saveError: "Could not confirm settings were saved. Changes are kept; retry or discard to view current settings."
60
+ }
61
+ };
62
+ function valueFrom(snapshot) {
63
+ const value = snapshot.value ?? {};
64
+ return {
65
+ ...DEFAULTS,
66
+ ...Object.fromEntries(
67
+ FIELDS.map((field) => [field, Boolean(value[field])])
68
+ )
69
+ };
82
70
  }
83
- const lang = () => String(globalThis.navigator?.language ?? '').toLowerCase().startsWith('zh') ? 'zh' : 'en'
84
- function valueFrom(snapshot) { return { ...DEFAULTS, ...(snapshot.value ?? {}) } }
85
71
  class Controller {
72
+ scope;
73
+ draft;
74
+ dirty;
75
+ saving;
76
+ saveError;
77
+ store;
78
+ stop;
86
79
  constructor(scope) {
87
- this.scope = scope; this.draft = null; this.dirty = false; this.saving = false
88
- this.store = createSnapshotStore(this.projection())
89
- this.stop = scope.subscribe(() => { if (!this.dirty) this.draft = null; this.publish() })
80
+ this.scope = scope;
81
+ this.draft = null;
82
+ this.dirty = false;
83
+ this.saving = false;
84
+ this.saveError = false;
85
+ this.store = createSnapshotStore(this.projection());
86
+ this.stop = scope.subscribe(() => {
87
+ if (!this.dirty) this.draft = null;
88
+ this.publish();
89
+ });
90
+ }
91
+ snapshot() {
92
+ return this.scope.getSnapshot();
93
+ }
94
+ value() {
95
+ return valueFrom(this.snapshot());
96
+ }
97
+ draftValue() {
98
+ return { ...this.value(), ...this.draft ?? {} };
90
99
  }
91
- snapshot() { return this.scope.getSnapshot() }
92
- value() { return valueFrom(this.snapshot()) }
93
- draftValue() { return { ...this.value(), ...(this.draft ?? {}) } }
94
- field(name) { return this.draftValue()[name] }
95
100
  projection() {
96
- const s = this.snapshot()
97
- const bools = ['enabled','webSearch','advancedHostedSearch','alphaSearch','fallbackToBasicCompaction','autoCompaction']
101
+ const s = this.snapshot(), value = this.draftValue(), fields = Object.fromEntries(
102
+ FIELDS.map((field) => [field, { value: Boolean(value[field]) }])
103
+ );
98
104
  return {
99
- available: s.status === 'ready', writable: s.writable, dirty: this.dirty, saving: this.saving,
100
- ...Object.fromEntries(bools.map((f) => [f, { value: f === 'fallbackToBasicCompaction' || f === 'autoCompaction' ? this.field(f) !== false : Boolean(this.field(f)) }])),
101
- webSearchTimeoutSeconds: Number(this.field('webSearchTimeoutSeconds') ?? 240),
102
- autoCompactionThresholdPercent: Number(this.field('autoCompactionThresholdPercent') ?? 90),
103
- emergencyPruneThresholdPercent: Number(this.field('emergencyPruneThresholdPercent') ?? 95),
104
- baseURL: String(this.field('baseURL') ?? ''), model: String(this.field('model') ?? ''),
105
- }
105
+ available: s.status === "ready",
106
+ writable: s.writable,
107
+ dirty: this.dirty,
108
+ saving: this.saving,
109
+ saveError: this.saveError,
110
+ ...fields
111
+ };
112
+ }
113
+ publish() {
114
+ this.store.set(this.projection());
115
+ }
116
+ edit(field, value) {
117
+ if (!FIELDS.includes(field) || this.saving || !this.snapshot().writable) return;
118
+ this.draft = {
119
+ ...this.draft,
120
+ [field]: Boolean(value)
121
+ };
122
+ if (this.value()[field] === Boolean(value)) delete this.draft[field];
123
+ this.dirty = Object.keys(this.draft).length > 0;
124
+ this.saveError = false;
125
+ this.publish();
126
+ }
127
+ discard() {
128
+ if (this.saving) return;
129
+ this.draft = null;
130
+ this.dirty = false;
131
+ this.saveError = false;
132
+ this.publish();
106
133
  }
107
- publish() { this.store.set(this.projection()) }
108
- edit(field, value) { this.draft = { ...(this.draft ?? this.value()), [field]: value }; this.dirty = true; this.publish() }
109
- discard() { this.draft = null; this.dirty = false; this.publish() }
110
134
  async save() {
111
- if (!this.dirty || this.saving || !this.snapshot().writable) return
112
- const next = this.draftValue(), prev = this.value(); this.saving = true; this.publish()
135
+ if (!this.dirty || this.saving || !this.snapshot().writable || this.snapshot().status !== "ready") return;
136
+ const next = this.draftValue(), prev = this.value();
137
+ const fields = FIELDS.filter((field) => next[field] !== prev[field]);
138
+ this.saving = true;
139
+ this.saveError = false;
140
+ this.publish();
113
141
  try {
114
- for (const f of Object.keys(next)) if (next[f] !== prev[f]) await this.scope.set(f, next[f])
115
- this.draft = null; this.dirty = false
116
- } finally { this.saving = false; this.publish() }
142
+ if (fields.length) {
143
+ await this.scope.mutate(fields.map((field) => ({
144
+ op: "set",
145
+ path: [field],
146
+ value: next[field]
147
+ })));
148
+ if (this.snapshot().status !== "ready" || fields.some((field) => this.value()[field] !== next[field]))
149
+ throw new Error("Settings mutation was not confirmed");
150
+ }
151
+ this.draft = null;
152
+ this.dirty = false;
153
+ } catch {
154
+ this.saveError = true;
155
+ } finally {
156
+ this.saving = false;
157
+ this.publish();
158
+ }
159
+ }
160
+ inject() {
161
+ return {
162
+ hooks: { lcxCard: this.store },
163
+ edit: (field, value) => this.edit(field, value),
164
+ save: () => void this.save(),
165
+ discard: () => this.discard()
166
+ };
117
167
  }
118
- inject() { return { hooks: { lcxCard: this.store }, edit: (f,v) => this.edit(f,v), save: () => void this.save(), discard: () => this.discard() } }
119
- }
120
- function Row({ id, label, help, checked, disabled, onChange }) {
121
- return React.createElement('div', { className:'lcx-row' },
122
- React.createElement('input', { id, type:'checkbox', checked, disabled, onChange:e=>onChange(e.target.checked) }),
123
- React.createElement('label', { htmlFor:id }, label, React.createElement('small', null, help)))
124
168
  }
125
- function Field({ label, help, value, disabled, type='text', min, max, step, onChange }) {
126
- return React.createElement('div', { className:'lcx-field' },
127
- React.createElement('label', null, label),
128
- React.createElement('input', { className:'lcx-input', type, value, disabled, min, max, step, onChange }),
129
- help ? React.createElement('small', null, help) : null)
169
+ function Row({
170
+ id,
171
+ label,
172
+ help,
173
+ checked,
174
+ disabled,
175
+ onChange
176
+ }) {
177
+ return React.createElement(
178
+ "div",
179
+ { className: "lcx-row" },
180
+ React.createElement("input", {
181
+ id,
182
+ type: "checkbox",
183
+ checked,
184
+ disabled,
185
+ onChange: (event) => onChange(event.target.checked)
186
+ }),
187
+ React.createElement(
188
+ "label",
189
+ { htmlFor: id },
190
+ label,
191
+ React.createElement("small", null, help)
192
+ )
193
+ );
130
194
  }
131
195
  function Card(props) {
132
- const t = copy[lang()], s = props.useLcxCard(x=>x), [open,setOpen] = React.useState(false)
133
- if (!s.available) return null
134
- const disabled = !s.writable || s.saving
135
- const numeric = (field) => (e) => props.edit(field, Number(e.target.value))
136
- return React.createElement('li', { className:'lcx-card' },
137
- React.createElement('button', { className:'lcx-head', type:'button', onClick:()=>setOpen(!open) }, React.createElement('strong',null,t.title), React.createElement('span',null,open?'⌃':'⌄')),
138
- open ? React.createElement('div', { className:'lcx-body' },
139
- React.createElement('p', { className:'lcx-help' }, t.desc),
140
- React.createElement(Row, { id:'lcx-enabled', label:t.enabled, help:t.enabledHelp, checked:s.enabled.value, disabled, onChange:v=>props.edit('enabled',v) }),
141
- React.createElement(Row, { id:'lcx-web', label:t.web, help:t.webHelp, checked:s.webSearch.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('webSearch',v) }),
142
- React.createElement('div', { className:'lcx-fields' },
143
- React.createElement(Field, { label:t.searchTimeout, help:t.searchTimeoutHelp, type:'number', min:30, max:600, step:30, value:s.webSearchTimeoutSeconds, disabled:disabled||!s.enabled.value||!s.webSearch.value, onChange:numeric('webSearchTimeoutSeconds') })),
144
- React.createElement(Row, { id:'lcx-advanced', label:t.advanced, help:t.advancedHelp, checked:s.advancedHostedSearch.value, disabled:disabled||!s.enabled.value||!s.webSearch.value, onChange:v=>props.edit('advancedHostedSearch',v) }),
145
- React.createElement(Row, { id:'lcx-alpha', label:t.alpha, help:t.alphaHelp, checked:s.alphaSearch.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('alphaSearch',v) }),
146
- React.createElement(Row, { id:'lcx-auto-compact', label:t.autoCompact, help:t.autoCompactHelp, checked:s.autoCompaction.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('autoCompaction',v) }),
147
- React.createElement('div', { className:'lcx-fields' },
148
- React.createElement(Field, { label:t.autoThreshold, help:t.autoThresholdHelp, type:'number', min:85, max:95, step:1, value:s.autoCompactionThresholdPercent, disabled:disabled||!s.enabled.value||!s.autoCompaction.value, onChange:numeric('autoCompactionThresholdPercent') }),
149
- React.createElement(Field, { label:t.emergencyThreshold, help:t.emergencyThresholdHelp, type:'number', min:90, max:99, step:1, value:s.emergencyPruneThresholdPercent, disabled:disabled||!s.enabled.value||!s.autoCompaction.value, onChange:numeric('emergencyPruneThresholdPercent') })),
150
- React.createElement(Row, { id:'lcx-fallback', label:t.fallback, help:t.fallbackHelp, checked:s.fallbackToBasicCompaction.value, disabled:disabled||!s.enabled.value, onChange:v=>props.edit('fallbackToBasicCompaction',v) }),
151
- React.createElement('div', { className:'lcx-fields' },
152
- React.createElement(Field, { label:t.endpoint, value:s.baseURL, disabled, onChange:e=>props.edit('baseURL',e.target.value) }),
153
- React.createElement(Field, { label:t.model, value:s.model, disabled, onChange:e=>props.edit('model',e.target.value) })),
154
- React.createElement('div', { className:'lcx-foot' },
155
- React.createElement('button', { disabled:!s.dirty||disabled, onClick:props.discard }, t.discard),
156
- React.createElement('button', { disabled:!s.dirty||disabled, onClick:props.save }, s.saving?t.saving:t.save))) : null)
196
+ const t = props.t, s = props.useLcxCard((state) => state), [open, setOpen] = React.useState(false);
197
+ if (!s.available) return null;
198
+ const disabled = !s.writable || s.saving;
199
+ return React.createElement(
200
+ "li",
201
+ { className: "lcx-card" },
202
+ React.createElement(
203
+ "button",
204
+ {
205
+ className: "lcx-head",
206
+ type: "button",
207
+ onClick: () => setOpen(!open)
208
+ },
209
+ React.createElement("strong", null, t("title")),
210
+ React.createElement("span", null, open ? "\u2303" : "\u2304")
211
+ ),
212
+ open ? React.createElement(
213
+ "div",
214
+ { className: "lcx-body" },
215
+ React.createElement("p", { className: "lcx-help" }, t("desc")),
216
+ React.createElement(Row, {
217
+ id: "lcx-enabled",
218
+ label: t("enabled"),
219
+ help: t("enabledHelp"),
220
+ checked: s.enabled.value,
221
+ disabled,
222
+ onChange: (value) => props.edit("enabled", value)
223
+ }),
224
+ React.createElement(Row, {
225
+ id: "lcx-web",
226
+ label: t("web"),
227
+ help: t("webHelp"),
228
+ checked: s.webSearch.value,
229
+ disabled: disabled || !s.enabled.value,
230
+ onChange: (value) => props.edit("webSearch", value)
231
+ }),
232
+ React.createElement(Row, {
233
+ id: "lcx-advanced",
234
+ label: t("advanced"),
235
+ help: t("advancedHelp"),
236
+ checked: s.advancedHostedSearch.value,
237
+ disabled: disabled || !s.enabled.value || !s.webSearch.value,
238
+ onChange: (value) => props.edit("advancedHostedSearch", value)
239
+ }),
240
+ React.createElement(Row, {
241
+ id: "lcx-alpha",
242
+ label: t("alpha"),
243
+ help: t("alphaHelp"),
244
+ checked: s.alphaSearch.value,
245
+ disabled: disabled || !s.enabled.value,
246
+ onChange: (value) => props.edit("alphaSearch", value)
247
+ }),
248
+ React.createElement(
249
+ "p",
250
+ { role: "alert", hidden: !s.saveError },
251
+ s.saveError ? t("saveError") : ""
252
+ ),
253
+ React.createElement(
254
+ "div",
255
+ { className: "lcx-foot" },
256
+ React.createElement(
257
+ "button",
258
+ { disabled: !s.dirty || disabled, onClick: props.discard },
259
+ t("discard")
260
+ ),
261
+ React.createElement(
262
+ "button",
263
+ { disabled: !s.dirty || disabled, onClick: props.save },
264
+ s.saving ? t("saving") : t("save")
265
+ )
266
+ )
267
+ ) : null
268
+ );
157
269
  }
158
- const inject = ['slots','locale','settingsScope']
270
+ const inject = ["slots", "locale", "settingsScope"];
159
271
  function apply(ctx) {
160
- const slots = ctx.slots ?? ctx.get('slots'), svc = ctx.settingsScope ?? ctx.get('settingsScope')
161
- if (!slots || !svc) return
162
- const controller = new Controller(svc.bind({ namespace:NAMESPACE }))
163
- slots.inject('settings.plugin.item', () => slots.register({ name:'settings.plugin.item', key:NAMESPACE, inject:()=>controller.inject() }, Card))
164
- ctx.effect(() => () => controller.stop(), 'lcx-codex settings card')
272
+ const slots = ctx.slots ?? ctx.get("slots"), svc = ctx.settingsScope ?? ctx.get("settingsScope"), locale = ctx.locale ?? ctx.get("locale");
273
+ if (!slots || !svc || !locale) return;
274
+ for (const language of ["zh", "en"])
275
+ ctx.effect(() => locale.register(NAMESPACE, language, copy[language]), `lcx-codex ${language} dictionary`);
276
+ const controller = new Controller(svc.bind({ namespace: NAMESPACE }));
277
+ slots.inject(
278
+ "settings.plugin.item",
279
+ () => slots.register(
280
+ {
281
+ name: "settings.plugin.item",
282
+ key: NAMESPACE,
283
+ locale: NAMESPACE,
284
+ inject: () => controller.inject()
285
+ },
286
+ Card
287
+ )
288
+ );
289
+ ctx.effect(() => () => controller.stop(), "lcx-codex settings card");
165
290
  }
166
- exports.apply = apply; exports.inject = inject; return module.exports
167
- },
168
- })
291
+ exports.apply = apply;
292
+ exports.inject = inject;
293
+ return module.exports;
294
+ }
295
+ });