dsh-openai-subscription 0.1.4 → 0.1.6
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/README.md +22 -1
- package/README.zh.md +22 -1
- package/THIRD_PARTY_NOTICES.md +59 -0
- package/dist/client.d.ts +19 -1
- package/dist/client.js +219 -28
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +2 -1
- package/dist/host.d.ts +16 -0
- package/dist/host.js +100 -7
- package/dist/models.d.ts +33 -0
- package/dist/models.js +73 -2
- package/docs/DSH_SETTINGS_ICONS.md +134 -0
- package/package.json +8 -3
- package/scripts/patch-dsh-settings-icons.mjs +325 -0
package/README.md
CHANGED
|
@@ -53,7 +53,22 @@ Restart `dsh web`, then refresh the existing page. DSH registers the plugin auto
|
|
|
53
53
|
4. Return to DSH after authorization completes; the plugin fetches and syncs models automatically.
|
|
54
54
|
5. Select an available model from the DSH model picker.
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
The main area keeps **Sync models / Update models**: fetch the model list while preserving local configuration, without switching accounts. Expand **Connection management** for infrequent operations:
|
|
57
|
+
|
|
58
|
+
- **Refresh authorization / Reconnect**: recover from authorization problems. `llm-pi-ai` automatically renews refreshable credentials during requests; routine manual refresh is unnecessary. Recovery is also shown directly for expired or unknown authorization.
|
|
59
|
+
- **Reload status**: read local state only; does not renew authorization or sync models.
|
|
60
|
+
- **Disconnect**: remove the local connection after confirmation; does not delete your ChatGPT account.
|
|
61
|
+
|
|
62
|
+
### 1M and custom context windows
|
|
63
|
+
|
|
64
|
+
Under **Model context**, select a model, choose **Use model default / 1M / Custom**, then **Save context**. 1M means `1,000,000` tokens for input and output combined, not a fixed request size or a higher output-token limit.
|
|
65
|
+
|
|
66
|
+
- Only the selected model’s `llm-pi-ai.providers.openai-codex.models[].contextWindow` changes. Other models are not forced to 1M.
|
|
67
|
+
- Catalog defaults and maximum windows are separate. The 1M option is disabled below a known 1M maximum, and custom values cannot exceed known maxima.
|
|
68
|
+
- When no maximum is declared, an explicit override is allowed with a warning. Local configuration does not guarantee model/account capacity or unlock upstream access.
|
|
69
|
+
- Later model syncs preserve this local edit. Choose **Use model default** and save to restore the latest synchronized default field.
|
|
70
|
+
- Saving changes local settings only, without login or discovery. Concurrent edits produce a settings conflict; reload before saving again.
|
|
71
|
+
- Older snapshots may lack default/maximum metadata; **Update models** fills in facts the catalog supplies. After upgrading, load both the new host and client plugin, restart DSH, and refresh the page.
|
|
57
72
|
|
|
58
73
|
### Status and recovery
|
|
59
74
|
|
|
@@ -120,6 +135,12 @@ OpenAI's terms, privacy policy, model availability, and usage limits still apply
|
|
|
120
135
|
|
|
121
136
|
After installing dependencies, run `npm run check` (or `pnpm run check`) for typechecking, build, and regression tests. Use `npm pack --dry-run --ignore-scripts` to inspect published files. Tests mock OAuth, network, and credential services; they do not access real accounts. After editing the plugin, rebuild and reinstall/load that version, restart `dsh web`, and refresh the existing page. Editing source alone does not update the installed GUI plugin.
|
|
122
137
|
|
|
138
|
+
### Settings UI and navigation icons
|
|
139
|
+
|
|
140
|
+
Buttons use theme-aware, high-contrast `1px` borders. The header and navigation reuse one local OpenAI SVG, with no additional runtime dependency or remote image requests; see [third-party notices](THIRD_PARTY_NOTICES.md).
|
|
141
|
+
|
|
142
|
+
The currently tested upstream DSH build does not expose custom settings navigation icons. Unextended hosts keep the default gear without affecting other plugin features. This project includes an **opt-in, build-guarded, reversible** local DSH extension; installing the plugin does not patch the host automatically. It preserves string labels and passes icons through `icon({ size, className })`, not DOM/CSS substitution. See [DSH settings icon extension](docs/DSH_SETTINGS_ICONS.md) for usage and upgrade caveats.
|
|
143
|
+
|
|
123
144
|
## License
|
|
124
145
|
|
|
125
146
|
MIT — see [LICENSE](LICENSE).
|
package/README.zh.md
CHANGED
|
@@ -53,7 +53,22 @@ dsh plugin --profile web add dsh-openai-subscription
|
|
|
53
53
|
4. 授权完成后返回 DSH;插件会自动获取并同步模型。
|
|
54
54
|
5. 在 DSH 的模型选择器中选择可用模型。
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
常用区域只保留 **同步模型 / 更新模型**:重新获取模型列表,保留本地配置,不切换账号。展开 **连接管理** 才会看到低频操作:
|
|
57
|
+
|
|
58
|
+
- **刷新授权 / 重新连接**:用于授权异常恢复。`llm-pi-ai` 会在模型请求期间自动续期可刷新的凭证,无需日常手动刷新;过期或有效性未知时会直接显示恢复按钮。
|
|
59
|
+
- **重新读取状态**:只读取本机状态,不刷新授权、不同步模型。
|
|
60
|
+
- **断开连接**:清理本机连接,操作前需确认,不会删除 ChatGPT 账号。
|
|
61
|
+
|
|
62
|
+
### 1M 与自定义上下文
|
|
63
|
+
|
|
64
|
+
在 **模型上下文** 中选择模型,再选择 **跟随模型默认 / 1M / 自定义**,点击 **保存上下文**。1M 表示 `1,000,000` tokens,是输入与输出合计窗口;不是每次固定发送 1M tokens,也不会提高单次输出上限。
|
|
65
|
+
|
|
66
|
+
- 仅修改选中模型的 `llm-pi-ai.providers.openai-codex.models[].contextWindow`,不把所有模型强制提升到 1M。
|
|
67
|
+
- 目录的默认窗口与最大窗口分开处理;已知上限低于 1M 时禁用 1M 选项,自定义也不能超过已知上限。
|
|
68
|
+
- 未知上限时允许明确设置,但本机配置不代表模型或账号支持该容量,不会解锁上游权限。
|
|
69
|
+
- 后续模型同步会保留本地上下文编辑;选择 **跟随模型默认** 并保存可恢复最近一次同步的默认字段。
|
|
70
|
+
- 保存仅操作本机设置,不触发登录或模型发现;其他页面同时改动时会报设置冲突,请重新读取后再保存。
|
|
71
|
+
- 旧版记录可能缺少默认/上限信息;点击 **更新模型** 可补齐目录提供的元数据。升级后需同时加载新版主机端与客户端,并重启 DSH、刷新页面。
|
|
57
72
|
|
|
58
73
|
### 状态与恢复
|
|
59
74
|
|
|
@@ -120,6 +135,12 @@ dsh plugin --profile web remove dsh-openai-subscription
|
|
|
120
135
|
|
|
121
136
|
安装依赖后运行 `npm run check`(或 `pnpm run check`),完成类型检查、构建和回归测试;`npm pack --dry-run --ignore-scripts` 检查发布文件。测试使用模拟 OAuth、网络与凭证服务,不访问真实账号。修改插件后需要重新构建并重新安装/加载该版本,再重启 `dsh web`、刷新现有页面;仅修改源码不会更新已安装的 GUI 插件。
|
|
122
137
|
|
|
138
|
+
### 设置界面与导航图标
|
|
139
|
+
|
|
140
|
+
按钮使用主题自适应的 `1px` 高对比度边框。顶部和导航复用本地 OpenAI SVG,无额外运行时依赖或远程图片请求;来源及许可证见 [第三方声明](THIRD_PARTY_NOTICES.md)。
|
|
141
|
+
|
|
142
|
+
当前官方 DSH 设置导航尚未提供自定义图标接口,未扩展的宿主仍显示默认齿轮,插件其他功能不受影响。本项目提供**显式执行、限定构建版本、可还原**的本机 DSH 扩展,不在安装时自动修改宿主。它保留字符串标签,通过 `icon({ size, className })` 传递图标,不使用 DOM/CSS 替换技巧。用法和升级注意事项见 [DSH 图标接口扩展](docs/DSH_SETTINGS_ICONS.md)。
|
|
143
|
+
|
|
123
144
|
## 许可证
|
|
124
145
|
|
|
125
146
|
MIT——见 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## OpenAI / ChatGPT icon — Lobe Icons
|
|
4
|
+
|
|
5
|
+
The monochrome SVG geometry embedded in `src/client.ts` is from
|
|
6
|
+
[`@lobehub/icons-static-svg` 1.95.0, `icons/openai.svg`](https://www.npmjs.com/package/@lobehub/icons-static-svg/v/1.95.0).
|
|
7
|
+
Upstream: [Lobe Icons](https://github.com/lobehub/lobe-icons),
|
|
8
|
+
[icon preview](https://lobehub.com/icons/openai).
|
|
9
|
+
|
|
10
|
+
The path geometry is unchanged. The wrapper is adapted to React, accepts a size
|
|
11
|
+
and CSS class, inherits `currentColor`, and is hidden from assistive technology
|
|
12
|
+
because adjacent text provides the label. The same component is used in the
|
|
13
|
+
settings header and navigation; no icon library or remote image is loaded at runtime.
|
|
14
|
+
|
|
15
|
+
OpenAI and ChatGPT names and marks belong to their respective owners. Use here
|
|
16
|
+
identifies the supported service and does not imply affiliation or endorsement.
|
|
17
|
+
The software license below does not grant trademark rights.
|
|
18
|
+
|
|
19
|
+
## DeepSeek Harness patch anchors
|
|
20
|
+
|
|
21
|
+
The optional `scripts/patch-dsh-settings-icons.mjs` includes small source anchors
|
|
22
|
+
from DeepSeek Harness's MIT-licensed settings and slots packages.
|
|
23
|
+
|
|
24
|
+
Copyright (c) 2026 DeepSeek
|
|
25
|
+
|
|
26
|
+
The MIT permission and disclaimer below also apply to these anchors; preserve
|
|
27
|
+
this copyright notice when distributing them. The extension is local to this
|
|
28
|
+
project and is not an upstream DeepSeek release.
|
|
29
|
+
|
|
30
|
+
## MIT license text
|
|
31
|
+
|
|
32
|
+
Reproduced from `@lobehub/icons` 1.95.0, `LICENSE` (the static SVG package
|
|
33
|
+
also declares MIT). The DeepSeek Harness packages use the same MIT terms with
|
|
34
|
+
the DeepSeek copyright notice above:
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
MIT License
|
|
39
|
+
|
|
40
|
+
Copyright (c) 2023 LobeHub
|
|
41
|
+
|
|
42
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
43
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
44
|
+
in the Software without restriction, including without limitation the rights
|
|
45
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
46
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
47
|
+
furnished to do so, subject to the following conditions:
|
|
48
|
+
|
|
49
|
+
The above copyright notice and this permission notice shall be included in all
|
|
50
|
+
copies or substantial portions of the Software.
|
|
51
|
+
|
|
52
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
53
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
54
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
55
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
56
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
57
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
58
|
+
SOFTWARE.
|
|
59
|
+
```
|
package/dist/client.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ interface ModelSyncInfo {
|
|
|
30
30
|
count: number;
|
|
31
31
|
warningCode?: string;
|
|
32
32
|
}
|
|
33
|
+
interface ModelContextInfo {
|
|
34
|
+
id: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
contextWindow?: number;
|
|
37
|
+
defaultContextWindow?: number;
|
|
38
|
+
maxContextWindow?: number;
|
|
39
|
+
customized: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface ModelContextsInfo {
|
|
42
|
+
revision: number;
|
|
43
|
+
models: ModelContextInfo[];
|
|
44
|
+
}
|
|
33
45
|
interface RemoteResult<T> {
|
|
34
46
|
ok?: boolean;
|
|
35
47
|
error?: {
|
|
@@ -67,6 +79,7 @@ interface SettingsSectionMeta {
|
|
|
67
79
|
id: string;
|
|
68
80
|
order: number;
|
|
69
81
|
label: string | (() => string);
|
|
82
|
+
icon?: IconComponent;
|
|
70
83
|
locale?: string;
|
|
71
84
|
}
|
|
72
85
|
interface SlotsService {
|
|
@@ -100,8 +113,13 @@ interface SectionProps {
|
|
|
100
113
|
subscribeReset?: (listener: () => void) => () => void;
|
|
101
114
|
}
|
|
102
115
|
type SectionComponent = (props: SectionProps) => ReactElement;
|
|
116
|
+
interface IconProps {
|
|
117
|
+
size?: number;
|
|
118
|
+
className?: string;
|
|
119
|
+
}
|
|
120
|
+
type IconComponent = (props: IconProps) => ReactElement;
|
|
103
121
|
interface ReactModule {
|
|
104
|
-
createElement(type: string | SectionComponent, props: Record<string, unknown> | null, ...children: ReactNode[]): ReactElement;
|
|
122
|
+
createElement(type: string | SectionComponent | IconComponent, props: Record<string, unknown> | null, ...children: ReactNode[]): ReactElement;
|
|
105
123
|
useState<S>(initial: S): [S, (update: S | ((previous: S) => S)) => void];
|
|
106
124
|
useEffect(effect: () => void | (() => void), deps?: ReadonlyArray<unknown>): void;
|
|
107
125
|
useRef<T>(initial: T): {
|
package/dist/client.js
CHANGED
|
@@ -17,7 +17,35 @@ const ZH = {
|
|
|
17
17
|
'model.synced': '模型已同步',
|
|
18
18
|
'model.attention': '模型需要同步',
|
|
19
19
|
'model.count': '{count} 个可用模型',
|
|
20
|
-
'model.synced.detail': '
|
|
20
|
+
'model.synced.detail': '已合并账号模型与 DSH 内置模型,保留你的本地编辑。',
|
|
21
|
+
'model.sync.help': '重新获取可用模型列表,不会更换账号或重置本地配置。',
|
|
22
|
+
'context.title': '模型上下文',
|
|
23
|
+
'context.help': '按模型设置总上下文窗口(输入 + 输出)。同步模型会保留此设置。',
|
|
24
|
+
'context.model': '模型',
|
|
25
|
+
'context.window': '上下文窗口',
|
|
26
|
+
'context.default': '跟随模型默认',
|
|
27
|
+
'context.million': '1M · 1,000,000 tokens',
|
|
28
|
+
'context.custom': '自定义',
|
|
29
|
+
'context.tokens': '自定义 tokens 数',
|
|
30
|
+
'context.current': '当前:{count} tokens',
|
|
31
|
+
'context.maximum': '目录声明上限:{count} tokens',
|
|
32
|
+
'context.unknown': '目录未声明最大窗口;本机配置不会解锁上游权限,请确认模型和账号支持。',
|
|
33
|
+
'context.limit.help': '超过目录声明上限的配置不可保存;实际可用性仍取决于账号和上游。',
|
|
34
|
+
'context.empty': '同步模型后即可配置上下文。',
|
|
35
|
+
'context.loading': '正在读取模型配置…',
|
|
36
|
+
'context.save': '保存上下文',
|
|
37
|
+
'context.saving': '正在保存…',
|
|
38
|
+
'context.saved': '上下文配置已保存,后续模型请求会使用新设置。',
|
|
39
|
+
'context.error': '无法读取上下文配置。请确认插件主机端已更新并重启 DSH 后重试。',
|
|
40
|
+
'error.invalid-context-window': '请输入正整数 tokens 数。',
|
|
41
|
+
'error.context-window-exceeded': '此设置超过了模型目录声明的最大上下文窗口。',
|
|
42
|
+
'error.model-not-found': '模型已不在当前列表中,请重新读取配置。',
|
|
43
|
+
'action.more': '连接管理',
|
|
44
|
+
'action.less': '收起连接管理',
|
|
45
|
+
'manage.refresh.help': '请求时会自动续期;仅在授权异常时手动刷新,无需重新登录。',
|
|
46
|
+
'manage.reconnect.help': '重新进行设备验证,以恢复此账号的授权。',
|
|
47
|
+
'manage.reload.help': '只重新读取本机状态,不刷新授权,也不同步模型。',
|
|
48
|
+
'manage.disconnect.help': '删除本机授权和未修改的插件模型项,不影响 ChatGPT 账号。',
|
|
21
49
|
'model.attention.detail': '确认同步后会保留本地新增项与已编辑字段。',
|
|
22
50
|
'action.connect': '连接 ChatGPT',
|
|
23
51
|
'action.connecting': '正在连接…',
|
|
@@ -120,7 +148,35 @@ const EN = {
|
|
|
120
148
|
'model.synced': 'Models synced',
|
|
121
149
|
'model.attention': 'Models need syncing',
|
|
122
150
|
'model.count': '{count} models available',
|
|
123
|
-
'model.synced.detail': 'Account
|
|
151
|
+
'model.synced.detail': 'Account and DSH models are merged, keeping your local edits.',
|
|
152
|
+
'model.sync.help': 'Fetch the available model list without switching accounts or resetting local settings.',
|
|
153
|
+
'context.title': 'Model context',
|
|
154
|
+
'context.help': 'Set each model’s total context window (input + output). Model sync preserves this setting.',
|
|
155
|
+
'context.model': 'Model',
|
|
156
|
+
'context.window': 'Context window',
|
|
157
|
+
'context.default': 'Use model default',
|
|
158
|
+
'context.million': '1M · 1,000,000 tokens',
|
|
159
|
+
'context.custom': 'Custom',
|
|
160
|
+
'context.tokens': 'Custom token count',
|
|
161
|
+
'context.current': 'Current: {count} tokens',
|
|
162
|
+
'context.maximum': 'Catalog limit: {count} tokens',
|
|
163
|
+
'context.unknown': 'The catalog does not declare a maximum. Local settings do not unlock upstream access; confirm model and account support.',
|
|
164
|
+
'context.limit.help': 'Values above the catalog limit cannot be saved. Availability still depends on your account and upstream service.',
|
|
165
|
+
'context.empty': 'Sync models to configure their context windows.',
|
|
166
|
+
'context.loading': 'Reading model settings…',
|
|
167
|
+
'context.save': 'Save context',
|
|
168
|
+
'context.saving': 'Saving…',
|
|
169
|
+
'context.saved': 'Context settings saved. Subsequent model requests will use the new setting.',
|
|
170
|
+
'context.error': 'Could not read context settings. Ensure the host plugin is updated and restart DSH before retrying.',
|
|
171
|
+
'error.invalid-context-window': 'Enter a positive whole number of tokens.',
|
|
172
|
+
'error.context-window-exceeded': 'This exceeds the maximum context window declared by the model catalog.',
|
|
173
|
+
'error.model-not-found': 'The model is no longer in the current list. Reload settings.',
|
|
174
|
+
'action.more': 'Connection management',
|
|
175
|
+
'action.less': 'Hide connection management',
|
|
176
|
+
'manage.refresh.help': 'Authorization renews automatically during requests. Refresh manually only to recover from authorization errors.',
|
|
177
|
+
'manage.reconnect.help': 'Repeat device verification to restore authorization for this account.',
|
|
178
|
+
'manage.reload.help': 'Read local status only. Does not renew authorization or sync models.',
|
|
179
|
+
'manage.disconnect.help': 'Remove local authorization and unchanged plugin models, not your ChatGPT account.',
|
|
124
180
|
'model.attention.detail': 'Syncing preserves local additions and fields you edited.',
|
|
125
181
|
'action.connect': 'Connect ChatGPT',
|
|
126
182
|
'action.connecting': 'Connecting…',
|
|
@@ -222,8 +278,26 @@ window.__ModuleLoader__.load({
|
|
|
222
278
|
const exports = module.exports;
|
|
223
279
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
224
280
|
const React = require('react');
|
|
281
|
+
// Lobe Icons @lobehub/icons-static-svg 1.95.0, icons/openai.svg (MIT).
|
|
282
|
+
// Geometry is unchanged; see THIRD_PARTY_NOTICES.md for attribution.
|
|
283
|
+
// One local SVG component serves both the host navigation and this header:
|
|
284
|
+
// no extra React runtime, icon-library bundle, or external image request.
|
|
285
|
+
function OpenAIIcon({ size = 24, className }) {
|
|
286
|
+
return React.createElement('svg', {
|
|
287
|
+
xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24',
|
|
288
|
+
width: size, height: size, fill: 'currentColor', fillRule: 'evenodd',
|
|
289
|
+
className, 'aria-hidden': true, focusable: false,
|
|
290
|
+
}, React.createElement('path', {
|
|
291
|
+
d: 'M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z',
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
225
294
|
const CSS = `
|
|
226
295
|
.oasub-wrap {
|
|
296
|
+
--oasub-section-inset: 20px;
|
|
297
|
+
--oasub-card-border-width: 1px;
|
|
298
|
+
/* Control boundaries need more contrast than the host's decorative hairlines. */
|
|
299
|
+
--oasub-control-border: color-mix(in srgb, var(--dsw-alias-label-primary, #0f1115) 48%, transparent);
|
|
300
|
+
--oasub-control-border-hover: color-mix(in srgb, var(--dsw-alias-label-primary, #0f1115) 68%, transparent);
|
|
227
301
|
box-sizing: border-box;
|
|
228
302
|
display: flex;
|
|
229
303
|
flex-direction: column;
|
|
@@ -232,6 +306,8 @@ window.__ModuleLoader__.load({
|
|
|
232
306
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
233
307
|
}
|
|
234
308
|
.oasub-wrap, .oasub-wrap * { box-sizing: border-box; }
|
|
309
|
+
/* The header stays flush with the card's outer frame. */
|
|
310
|
+
.oasub-header { padding-inline: 0; }
|
|
235
311
|
.oasub-header { display: flex; align-items: center; gap: 12px; }
|
|
236
312
|
.oasub-mark {
|
|
237
313
|
display: grid;
|
|
@@ -242,19 +318,17 @@ window.__ModuleLoader__.load({
|
|
|
242
318
|
border-radius: 13px;
|
|
243
319
|
color: var(--dsw-alias-label-primary-inverted, #fff);
|
|
244
320
|
background: var(--dsw-alias-brand-primary, #4176e6);
|
|
245
|
-
font-size: 20px;
|
|
246
|
-
font-weight: 700;
|
|
247
|
-
line-height: 1;
|
|
248
321
|
}
|
|
249
|
-
.oasub-
|
|
322
|
+
.oasub-mark > svg { display: block; flex: none; }
|
|
323
|
+
.oasub-heading { min-width: 0; overflow-wrap: anywhere; }
|
|
250
324
|
.oasub-title { margin: 0; font-size: 18px; font-weight: 600; line-height: 26px; }
|
|
251
325
|
.oasub-subtitle { margin: 2px 0 0; color: var(--dsw-alias-label-tertiary, #81858c); font-size: 13px; line-height: 20px; }
|
|
252
326
|
.oasub-card {
|
|
253
327
|
display: flex;
|
|
254
328
|
flex-direction: column;
|
|
255
329
|
gap: 16px;
|
|
256
|
-
padding:
|
|
257
|
-
border:
|
|
330
|
+
padding: var(--oasub-section-inset);
|
|
331
|
+
border: var(--oasub-card-border-width) solid var(--dsw-alias-border-l2, rgba(15, 17, 21, .14));
|
|
258
332
|
border-radius: 16px;
|
|
259
333
|
background: var(--dsw-alias-bg-layer-1, #fff);
|
|
260
334
|
box-shadow: var(--dsw-shadow-lv1, 0 2px 8px rgba(0, 0, 0, .04));
|
|
@@ -289,11 +363,21 @@ window.__ModuleLoader__.load({
|
|
|
289
363
|
.oasub-model-title { font-size: 13px; font-weight: 500; line-height: 19px; }
|
|
290
364
|
.oasub-model-detail { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 11px; line-height: 17px; }
|
|
291
365
|
.oasub-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
366
|
+
.oasub-management { display: flex; flex-direction: column; gap: 16px; border-top: 1px solid var(--dsw-alias-border-l2, rgba(15, 17, 21, .14)); padding-top: 16px; }
|
|
367
|
+
.oasub-manage-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
368
|
+
.oasub-manage-row > .oasub-button { flex: 0 0 auto; }
|
|
369
|
+
.oasub-button.disclosure { border-color: transparent; border-radius: 8px; color: var(--dsw-alias-label-secondary, #61666b); padding: 4px 0; min-height: 32px; align-self: flex-start; }
|
|
370
|
+
.oasub-context { display: flex; flex-direction: column; gap: 10px; }
|
|
371
|
+
.oasub-field { display: flex; flex-direction: column; gap: 6px; flex: 1 1 180px; min-width: 0; font-size: 12px; }
|
|
372
|
+
.oasub-context > .oasub-field { flex: none; }
|
|
373
|
+
.oasub-select { width: 100%; min-height: 36px; border: 1px solid var(--oasub-control-border); border-radius: 8px; padding: 7px 10px; font: inherit; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); }
|
|
374
|
+
.oasub-select:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4176e6); outline-offset: 2px; }
|
|
375
|
+
.oasub-select:disabled { opacity: .55; }
|
|
292
376
|
.oasub-button {
|
|
293
377
|
appearance: none;
|
|
294
378
|
min-height: 36px;
|
|
295
379
|
padding: 7px 15px;
|
|
296
|
-
border:
|
|
380
|
+
border: 1px solid var(--oasub-control-border);
|
|
297
381
|
border-radius: 999px;
|
|
298
382
|
color: var(--dsw-alias-label-primary, #0f1115);
|
|
299
383
|
background: transparent;
|
|
@@ -306,7 +390,7 @@ window.__ModuleLoader__.load({
|
|
|
306
390
|
text-align: center;
|
|
307
391
|
transition: background .16s ease, border-color .16s ease, opacity .16s ease, transform .16s ease;
|
|
308
392
|
}
|
|
309
|
-
.oasub-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, .06)); }
|
|
393
|
+
.oasub-button:not(.primary):not(.danger):hover:not(:disabled) { border-color: var(--oasub-control-border-hover); background: var(--dsw-alias-interactive-bg-hover, rgba(15, 17, 21, .06)); }
|
|
310
394
|
.oasub-button:active:not(:disabled) { transform: translateY(1px); }
|
|
311
395
|
.oasub-button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4176e6); outline-offset: 2px; }
|
|
312
396
|
.oasub-button:disabled { cursor: default; opacity: .45; }
|
|
@@ -357,10 +441,19 @@ window.__ModuleLoader__.load({
|
|
|
357
441
|
.oasub-dialog p { margin: 8px 0 18px; color: var(--dsw-alias-label-secondary, #61666b); font-size: 13px; line-height: 20px; }
|
|
358
442
|
.oasub-dialog .oasub-actions { justify-content: flex-end; }
|
|
359
443
|
@media (max-width: 520px) {
|
|
360
|
-
.oasub-
|
|
444
|
+
.oasub-wrap { --oasub-section-inset: 16px; }
|
|
361
445
|
.oasub-status-line { flex-direction: column; }
|
|
362
446
|
.oasub-button { flex: 1 1 auto; }
|
|
363
447
|
.oasub-button.quiet { margin-left: 0; }
|
|
448
|
+
.oasub-manage-row { align-items: flex-start; flex-direction: column; gap: 8px; }
|
|
449
|
+
}
|
|
450
|
+
@media (forced-colors: active) {
|
|
451
|
+
.oasub-button { border-color: ButtonText; }
|
|
452
|
+
.oasub-button.primary, .oasub-button.danger { border-color: ButtonText; color: ButtonText; background: ButtonFace; }
|
|
453
|
+
.oasub-button:not(.primary):not(.danger):hover:not(:disabled),
|
|
454
|
+
.oasub-button.primary:hover:not(:disabled), .oasub-button.danger:hover:not(:disabled) { border-color: Highlight; filter: none; }
|
|
455
|
+
.oasub-button:focus-visible { outline-color: Highlight; }
|
|
456
|
+
.oasub-button:disabled { border-color: GrayText; color: GrayText; opacity: 1; }
|
|
364
457
|
}
|
|
365
458
|
@media (prefers-reduced-motion: reduce) {
|
|
366
459
|
.oasub-button { transition: none; }
|
|
@@ -388,6 +481,7 @@ window.__ModuleLoader__.load({
|
|
|
388
481
|
'timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed',
|
|
389
482
|
'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required',
|
|
390
483
|
'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'cancelled', 'unknown',
|
|
484
|
+
'invalid-context-window', 'context-window-exceeded', 'model-not-found',
|
|
391
485
|
]);
|
|
392
486
|
function allowedCode(value) {
|
|
393
487
|
return typeof value === 'string' && ERROR_CODES.has(value) ? value : undefined;
|
|
@@ -534,6 +628,25 @@ window.__ModuleLoader__.load({
|
|
|
534
628
|
}
|
|
535
629
|
return { synced: true, count: raw.count, warningCode: raw.warningCode == null ? undefined : allowedCode(raw.warningCode) ?? 'unknown' };
|
|
536
630
|
}
|
|
631
|
+
function positiveInteger(value) {
|
|
632
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
633
|
+
}
|
|
634
|
+
function parseModelContexts(value) {
|
|
635
|
+
const raw = recordOf(value);
|
|
636
|
+
if (raw === null || typeof raw.revision !== 'number' || !Number.isSafeInteger(raw.revision) || raw.revision < 0 || !Array.isArray(raw.models)) {
|
|
637
|
+
throw new ClientFailure('invalid-response');
|
|
638
|
+
}
|
|
639
|
+
const models = [];
|
|
640
|
+
for (const item of raw.models) {
|
|
641
|
+
const row = recordOf(item);
|
|
642
|
+
if (!row || typeof row.id !== 'string' || !row.id || models.some((model) => model.id === row.id))
|
|
643
|
+
throw new ClientFailure('invalid-response');
|
|
644
|
+
models.push({ id: row.id, name: typeof row.name === 'string' ? row.name : undefined,
|
|
645
|
+
contextWindow: positiveInteger(row.contextWindow), defaultContextWindow: positiveInteger(row.defaultContextWindow),
|
|
646
|
+
maxContextWindow: positiveInteger(row.maxContextWindow), customized: row.customized === true });
|
|
647
|
+
}
|
|
648
|
+
return { revision: raw.revision, models };
|
|
649
|
+
}
|
|
537
650
|
function safeVerificationUrl(value) {
|
|
538
651
|
if (typeof value !== 'string' || value !== value.trim())
|
|
539
652
|
return null;
|
|
@@ -565,6 +678,14 @@ window.__ModuleLoader__.load({
|
|
|
565
678
|
const [cancelPending, setCancelPending] = React.useState(false);
|
|
566
679
|
const [confirm, setConfirm] = React.useState(null);
|
|
567
680
|
const [statusRevision, setStatusRevision] = React.useState(0);
|
|
681
|
+
const [managementOpen, setManagementOpen] = React.useState(false);
|
|
682
|
+
const [contexts, setContexts] = React.useState(null);
|
|
683
|
+
const [contextLoading, setContextLoading] = React.useState(false);
|
|
684
|
+
const [contextError, setContextError] = React.useState(null);
|
|
685
|
+
const [modelId, setModelId] = React.useState('');
|
|
686
|
+
// null means no pending edit; '' selects the synchronized model default.
|
|
687
|
+
const [contextDraft, setContextDraft] = React.useState(null);
|
|
688
|
+
const [contextChoice, setContextChoice] = React.useState(null);
|
|
568
689
|
const actionLock = React.useRef(false);
|
|
569
690
|
const cancelAcknowledged = React.useRef(false);
|
|
570
691
|
const flowObserved = React.useRef(false);
|
|
@@ -623,6 +744,32 @@ window.__ModuleLoader__.load({
|
|
|
623
744
|
controller.abort();
|
|
624
745
|
};
|
|
625
746
|
}, [connection, statusRevision]);
|
|
747
|
+
React.useEffect(() => {
|
|
748
|
+
if (!configured) {
|
|
749
|
+
setContexts(null);
|
|
750
|
+
setContextDraft(null);
|
|
751
|
+
setContextChoice(null);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const controller = new AbortController();
|
|
755
|
+
setContextLoading(true);
|
|
756
|
+
setContextError(null);
|
|
757
|
+
remoteCall(connection, 'getModelContexts', {}, controller.signal).then(parseModelContexts).then((result) => {
|
|
758
|
+
if (controller.signal.aborted)
|
|
759
|
+
return;
|
|
760
|
+
setContexts(result);
|
|
761
|
+
setModelId((id) => result.models.some((model) => model.id === id) ? id : result.models[0]?.id ?? '');
|
|
762
|
+
setContextDraft(null);
|
|
763
|
+
setContextChoice(null);
|
|
764
|
+
}).catch((error) => {
|
|
765
|
+
if (!controller.signal.aborted) {
|
|
766
|
+
setContexts(null);
|
|
767
|
+
setContextError(errorKey(error, 'context.error'));
|
|
768
|
+
}
|
|
769
|
+
}).finally(() => { if (!controller.signal.aborted)
|
|
770
|
+
setContextLoading(false); });
|
|
771
|
+
return () => controller.abort();
|
|
772
|
+
}, [connection, configured, statusRevision]);
|
|
626
773
|
React.useEffect(() => {
|
|
627
774
|
if (props.subscribeReset === undefined)
|
|
628
775
|
return;
|
|
@@ -941,6 +1088,41 @@ window.__ModuleLoader__.load({
|
|
|
941
1088
|
}
|
|
942
1089
|
});
|
|
943
1090
|
};
|
|
1091
|
+
const selectedModel = contexts?.models.find((model) => model.id === modelId);
|
|
1092
|
+
const savedContext = selectedModel?.customized && selectedModel.contextWindow ? String(selectedModel.contextWindow) : '';
|
|
1093
|
+
const draft = contextDraft ?? savedContext;
|
|
1094
|
+
const contextMode = contextChoice ?? (draft === '' ? 'default' : draft === '1000000' ? 'million' : 'custom');
|
|
1095
|
+
const requestedContext = draft === '' ? null : /^\d+$/.test(draft) ? positiveInteger(Number(draft)) : undefined;
|
|
1096
|
+
const contextValidation = requestedContext === undefined ? 'error.invalid-context-window'
|
|
1097
|
+
: requestedContext !== null && selectedModel?.maxContextWindow !== undefined && requestedContext > selectedModel.maxContextWindow
|
|
1098
|
+
? 'error.context-window-exceeded' : null;
|
|
1099
|
+
const contextDirty = contextDraft !== null && (draft !== savedContext || (draft === '' && selectedModel?.customized === true));
|
|
1100
|
+
const saveContext = () => {
|
|
1101
|
+
const signal = lifetime.current?.signal;
|
|
1102
|
+
if (phaseRef.current !== 'idle' || actionLock.current || !signal || signal.aborted || !contexts || !selectedModel || !contextDirty || contextValidation || contextLoading || statusLoading)
|
|
1103
|
+
return;
|
|
1104
|
+
actionLock.current = true;
|
|
1105
|
+
changePhase('saving-context');
|
|
1106
|
+
setNotice(null);
|
|
1107
|
+
remoteCall(connection, 'setModelContext', {
|
|
1108
|
+
modelId: selectedModel.id, contextWindow: requestedContext, revision: contexts.revision,
|
|
1109
|
+
}, signal, MUTATION_TIMEOUT_MS).then((result) => {
|
|
1110
|
+
if (signal.aborted)
|
|
1111
|
+
return;
|
|
1112
|
+
if (result?.saved !== true)
|
|
1113
|
+
throw new ClientFailure('invalid-response');
|
|
1114
|
+
setNotice({ tone: 'success', key: 'context.saved' });
|
|
1115
|
+
}).catch((error) => {
|
|
1116
|
+
if (!signal.aborted)
|
|
1117
|
+
setNotice({ tone: 'error', key: failureCode(error) === 'timeout' ? 'error.long-action' : errorKey(error, 'error.settings-write-failed') });
|
|
1118
|
+
}).finally(() => {
|
|
1119
|
+
if (!signal.aborted) {
|
|
1120
|
+
actionLock.current = false;
|
|
1121
|
+
changePhase('idle');
|
|
1122
|
+
reloadStatus();
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
};
|
|
944
1126
|
const selectCode = () => {
|
|
945
1127
|
codeRef.current?.focus();
|
|
946
1128
|
codeRef.current?.select();
|
|
@@ -988,6 +1170,7 @@ window.__ModuleLoader__.load({
|
|
|
988
1170
|
type: 'button',
|
|
989
1171
|
className: 'oasub-button ' + (info.modelsSynced ? '' : 'primary'),
|
|
990
1172
|
disabled: busy,
|
|
1173
|
+
'aria-describedby': 'oasub-sync-help',
|
|
991
1174
|
onClick: requestModelSync,
|
|
992
1175
|
}, phase === 'syncing' ? t('action.syncing') : t(info.modelsSynced ? 'action.updateModels' : 'action.sync'))
|
|
993
1176
|
: el('button', {
|
|
@@ -995,25 +1178,32 @@ window.__ModuleLoader__.load({
|
|
|
995
1178
|
className: 'oasub-button primary',
|
|
996
1179
|
disabled: busy || !ready,
|
|
997
1180
|
onClick: () => startAuthorization('device_code'),
|
|
998
|
-
}, phase === 'starting' || phase === 'authorizing' ? t('action.connecting') : t('action.connect')), configured
|
|
1181
|
+
}, phase === 'starting' || phase === 'authorizing' ? t('action.connecting') : t('action.connect')), configured && info.credentialState !== 'valid'
|
|
999
1182
|
? el('button', {
|
|
1000
|
-
type: 'button',
|
|
1001
|
-
|
|
1002
|
-
disabled: busy || !ready,
|
|
1183
|
+
type: 'button', className: 'oasub-button primary', disabled: busy || !ready,
|
|
1184
|
+
title: t(info.refreshable ? 'manage.refresh.help' : 'manage.reconnect.help'),
|
|
1003
1185
|
onClick: () => startAuthorization(info.refreshable ? 'refresh' : 'device_code'),
|
|
1004
|
-
}, phase === 'starting' || phase === 'authorizing'
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
? el('div', { className: 'oasub-
|
|
1186
|
+
}, phase === 'starting' || phase === 'authorizing' ? t('action.refreshing') : t(info.refreshable ? 'action.refresh' : 'action.reconnect'))
|
|
1187
|
+
: null), configured ? el('div', { id: 'oasub-sync-help', className: 'oasub-status-detail' }, t('model.sync.help')) : null);
|
|
1188
|
+
return el('div', { className: 'oasub-wrap' }, el('header', { className: 'oasub-header' }, el('div', { className: 'oasub-mark', 'aria-hidden': true }, el(OpenAIIcon, { size: 24 })), el('div', { className: 'oasub-heading' }, el('h2', { className: 'oasub-title' }, t('title')), el('p', { className: 'oasub-subtitle' }, t('subtitle')))), statusCard, configured ? el('section', { className: 'oasub-card oasub-context', 'aria-labelledby': 'oasub-context-title' }, el('div', { id: 'oasub-context-title', className: 'oasub-status-title' }, t('context.title')), el('div', { className: 'oasub-status-detail' }, t('context.help')), contextLoading ? el('div', { role: 'status', className: 'oasub-status-detail' }, t('context.loading')) : null, contextError ? el('div', { role: 'alert', className: 'oasub-notice error' }, t(contextError), el('button', { type: 'button', className: 'oasub-button quiet', disabled: contextLoading, onClick: reloadStatus }, t('action.retry'))) : null, !contextLoading && contexts?.models.length === 0 ? el('div', { className: 'oasub-status-detail' }, t('context.empty')) : null, selectedModel ? el('div', { className: 'oasub-context' }, el('div', { className: 'oasub-actions' }, el('label', { className: 'oasub-field' }, t('context.model'), el('select', { className: 'oasub-select', 'aria-label': t('context.model'), value: modelId, disabled: busy || contextLoading || statusLoading,
|
|
1189
|
+
onChange: (event) => { setModelId(event.currentTarget.value); setContextDraft(null); setContextChoice(null); } }, contexts?.models.map((model) => el('option', { key: model.id, value: model.id }, model.name && model.name !== model.id ? model.name + ' (' + model.id + ')' : model.id)))), el('label', { className: 'oasub-field' }, t('context.window'), el('select', { className: 'oasub-select', 'aria-label': t('context.window'), value: contextMode, disabled: busy || contextLoading || statusLoading,
|
|
1190
|
+
'aria-describedby': 'oasub-context-limit',
|
|
1191
|
+
onChange: (event) => {
|
|
1192
|
+
const choice = event.currentTarget.value === 'default' ? 'default' : event.currentTarget.value === 'million' ? 'million' : 'custom';
|
|
1193
|
+
setContextChoice(choice);
|
|
1194
|
+
setContextDraft(choice === 'default' ? '' : choice === 'million' ? '1000000' : '-');
|
|
1195
|
+
} }, el('option', { value: 'default' }, t('context.default') + (selectedModel.defaultContextWindow ? ' · ' + selectedModel.defaultContextWindow.toLocaleString() : '')), el('option', { value: 'million', disabled: selectedModel.maxContextWindow !== undefined && selectedModel.maxContextWindow < 1_000_000 }, t('context.million')), el('option', { value: 'custom' }, t('context.custom'))))), contextMode === 'custom' ? el('label', { className: 'oasub-field' }, t('context.tokens'), el('input', { className: 'oasub-select', type: 'text', inputMode: 'numeric', 'aria-label': t('context.tokens'),
|
|
1196
|
+
value: draft === '-' ? '' : draft, disabled: busy || contextLoading || statusLoading,
|
|
1197
|
+
'aria-invalid': contextValidation !== null, 'aria-describedby': 'oasub-context-limit',
|
|
1198
|
+
onChange: (event) => setContextDraft(event.currentTarget.value || '-') })) : null, selectedModel.contextWindow ? el('div', { className: 'oasub-status-detail' }, t('context.current', { count: selectedModel.contextWindow.toLocaleString() })) : null, el('div', { id: 'oasub-context-limit', className: 'oasub-status-detail' }, selectedModel.maxContextWindow ? t('context.maximum', { count: selectedModel.maxContextWindow.toLocaleString() }) + ' · ' + t('context.limit.help') : t('context.unknown')), contextDirty && contextValidation ? el('div', { role: 'alert', className: 'oasub-status-detail' }, t(contextValidation)) : null, contextDirty ? el('div', { className: 'oasub-actions' }, el('button', { type: 'button', className: 'oasub-button primary', disabled: busy || contextLoading || statusLoading || contextValidation !== null,
|
|
1199
|
+
onClick: saveContext }, t(phase === 'saving-context' ? 'context.saving' : 'context.save'))) : null) : null) : null, statusError !== null
|
|
1200
|
+
? el('div', { className: 'oasub-notice error', role: 'alert' }, t(statusError), info !== null ? ' ' + t('status.stale') : null, el('button', { type: 'button', className: 'oasub-button quiet', disabled: statusLoading, onClick: reloadStatus }, t('action.retry'))) : null, el('button', { ref: reloadRef, type: 'button', className: 'oasub-button disclosure',
|
|
1201
|
+
'aria-expanded': managementOpen, 'aria-controls': managementOpen ? 'oasub-management' : undefined,
|
|
1202
|
+
onClick: () => setManagementOpen((open) => !open) }, t(managementOpen ? 'action.less' : 'action.more')), managementOpen ? el('section', { id: 'oasub-management', className: 'oasub-management', 'aria-label': t('action.more') }, configured && info?.credentialState === 'valid' ? el('div', { className: 'oasub-manage-row' }, el('div', { className: 'oasub-status-detail', id: 'oasub-refresh-help' }, t(info.refreshable ? 'manage.refresh.help' : 'manage.reconnect.help')), el('button', { type: 'button', className: 'oasub-button', disabled: busy || !ready,
|
|
1203
|
+
'aria-describedby': 'oasub-refresh-help',
|
|
1204
|
+
onClick: () => startAuthorization(info.refreshable ? 'refresh' : 'device_code') }, phase === 'starting' || phase === 'authorizing' ? t('action.refreshing') : t(info.refreshable ? 'action.refresh' : 'action.reconnect'))) : null, el('div', { className: 'oasub-manage-row' }, el('div', { className: 'oasub-status-detail', id: 'oasub-reload-help' }, t('manage.reload.help')), el('button', { type: 'button', className: 'oasub-button', disabled: statusLoading,
|
|
1205
|
+
'aria-describedby': 'oasub-reload-help', onClick: reloadStatus }, t(statusLoading ? 'status.reloading' : 'action.reload'))), info?.cleanupAvailable ? el('div', { className: 'oasub-manage-row' }, el('div', { className: 'oasub-status-detail', id: 'oasub-disconnect-help' }, t('manage.disconnect.help')), el('button', { type: 'button', className: 'oasub-button', disabled: busy,
|
|
1206
|
+
'aria-describedby': 'oasub-disconnect-help', onClick: () => openConfirm('disconnect') }, t(phase === 'disconnecting' ? 'action.disconnecting' : 'action.disconnect'))) : null) : null, device !== null
|
|
1017
1207
|
? el('section', { className: 'oasub-card oasub-device', 'aria-label': t('device.title') }, el('div', { className: 'oasub-device-head' }, el('div', { className: 'oasub-status-title' }, t('device.title')), el('div', { className: 'oasub-status-detail' }, t('device.detail'))), el('input', { ref: codeRef, className: 'oasub-code', type: 'text', readOnly: true, value: device.code,
|
|
1018
1208
|
'aria-label': t('device.code'), autoComplete: 'off', spellCheck: false, onFocus: (event) => event.currentTarget.select() }), el('div', { className: 'oasub-actions' }, device.url !== null
|
|
1019
1209
|
? el('a', { className: 'oasub-button primary', href: device.url, target: '_blank', rel: 'noopener noreferrer',
|
|
@@ -1060,6 +1250,7 @@ window.__ModuleLoader__.load({
|
|
|
1060
1250
|
id: 'openai-subscription',
|
|
1061
1251
|
order: 25,
|
|
1062
1252
|
label: () => t('nav'),
|
|
1253
|
+
icon: OpenAIIcon,
|
|
1063
1254
|
locale: NS,
|
|
1064
1255
|
}, () => React.createElement(Section, { connection: ctx.connection, timer: ctx.timer, t, subscribeReset })));
|
|
1065
1256
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Stable, non-sensitive failures shared by the host's public boundaries. */
|
|
2
|
-
export declare const FAILURE_CODES: readonly ['credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable', 'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable', 'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited', 'network', 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required', 'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'cancelled', 'unknown'];
|
|
2
|
+
export declare const FAILURE_CODES: readonly ['credentials-unavailable', 'shell-unavailable', 'timer-unavailable', 'component-unavailable', 'runtime-unsupported', 'busy', 'invalid-method', 'not-connected', 'not-refreshable', 'device-auth-disabled', 'access-denied', 'authorization-expired', 'rate-limited', 'network', 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed', 'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty', 'models-confirmation-required', 'settings-conflict', 'settings-write-failed', 'ownership-save-failed', 'invalid-context-window', 'context-window-exceeded', 'model-not-found', 'cancelled', 'unknown'];
|
|
3
3
|
export type FailureCode = typeof FAILURE_CODES[number];
|
|
4
4
|
/** The marker survives RPC implementations which serialize only Error.message. */
|
|
5
5
|
export declare class SubscriptionError extends Error {
|
package/dist/errors.js
CHANGED
|
@@ -6,7 +6,8 @@ export const FAILURE_CODES = [
|
|
|
6
6
|
'network', 'timeout', 'invalid-response', 'process-exited', 'credential-write-failed',
|
|
7
7
|
'credential-changed', 'settings-unavailable', 'models-unavailable', 'models-empty',
|
|
8
8
|
'models-confirmation-required', 'settings-conflict', 'settings-write-failed',
|
|
9
|
-
'ownership-save-failed', '
|
|
9
|
+
'ownership-save-failed', 'invalid-context-window', 'context-window-exceeded', 'model-not-found',
|
|
10
|
+
'cancelled', 'unknown',
|
|
10
11
|
];
|
|
11
12
|
/** The marker survives RPC implementations which serialize only Error.message. */
|
|
12
13
|
export class SubscriptionError extends Error {
|