dsh-bundle-antigravity-host 0.1.35 → 0.1.36
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 +7 -5
- package/dist/adapter/antigravity-adapter.js +13 -3
- package/dist/adapter/antigravity-adapter.js.map +1 -1
- package/dist/adapter/session-reuse.d.ts +4 -1
- package/dist/adapter/session-reuse.js +3 -1
- package/dist/adapter/session-reuse.js.map +1 -1
- package/dist/client.js +213 -187
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/proxy/account-service.js +6 -1
- package/dist/proxy/account-service.js.map +1 -1
- package/dist/proxy/quota-source.js +2 -1
- package/dist/proxy/quota-source.js.map +1 -1
- package/dist/quota/quota-remote.d.ts +18 -3
- package/dist/quota/quota-remote.js +47 -7
- package/dist/quota/quota-remote.js.map +1 -1
- package/dist/runtime/quota.d.ts +3 -0
- package/dist/runtime/quota.js.map +1 -1
- package/dist/settings.d.ts +11 -1
- package/dist/settings.js +3 -1
- package/dist/settings.js.map +1 -1
- package/docs/changelog.md +8 -0
- package/docs/configuration.md +7 -6
- package/docs/development.md +8 -8
- package/docs/reference.md +11 -10
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -28,26 +28,27 @@ window.__ModuleLoader__.load({
|
|
|
28
28
|
const ENTRY_ID = 'llm-antigravity-acp'
|
|
29
29
|
/** Live fields owned by this card; ordinary fields stay in the host config editor. */
|
|
30
30
|
const FIELDS = [
|
|
31
|
-
{ key: 'proxyMode', group: 'mode', checkbox: true, label: '使用代理模式', hint: '
|
|
32
|
-
{ key: '
|
|
33
|
-
{ key: '
|
|
34
|
-
{ key: '
|
|
35
|
-
{ key: '
|
|
36
|
-
{ key: '
|
|
37
|
-
{ key: '
|
|
38
|
-
{ key: '
|
|
39
|
-
{ key: '
|
|
40
|
-
{ key: '
|
|
41
|
-
{ key: '
|
|
42
|
-
{ key: '
|
|
43
|
-
{ key: '
|
|
44
|
-
{ key: '
|
|
45
|
-
{ key: '
|
|
46
|
-
{ key: '
|
|
47
|
-
{ key: '
|
|
48
|
-
{ key: '
|
|
49
|
-
{ key: '
|
|
50
|
-
{ key: '
|
|
31
|
+
{ key: 'proxyMode', group: 'mode', checkbox: true, label: '使用代理模式', hint: '直连上游;需启用代理路由并连接代理账户。' },
|
|
32
|
+
{ key: 'cliQuotaEnabled', group: 'quota', checkbox: true, label: '使用 agy CLI 补充额度', hint: '默认不启动 agy;开启后才读取 AI Credits。' },
|
|
33
|
+
{ key: 'autoWarmup', group: 'general', checkbox: true, defaultChecked: true, acpOnly: true, label: '启动时自动预热', hint: '不发送消息;下次启用插件时生效。' },
|
|
34
|
+
{ key: 'reuseSessions', group: 'general', checkbox: true, defaultChecked: true, acpOnly: true, label: '复用 ACP 会话', hint: '只发送新增消息;上下文不一致时自动重建。' },
|
|
35
|
+
{ key: 'proxyUrl', group: 'network', label: '出网代理', placeholder: 'http://127.0.0.1:7890', hint: '留空继承宿主代理环境。' },
|
|
36
|
+
{ key: 'agyCliPath', group: 'network', label: '官方 CLI 路径', placeholder: '留空自动查找 agy', hint: '仅用于模型目录和可选的 CLI 额度查询。' },
|
|
37
|
+
{ key: 'resumeSessions', group: 'advanced', checkbox: true, acpOnly: true, label: '重启后恢复 ACP 会话', hint: '需开启会话复用;恢复失败时自动重建。' },
|
|
38
|
+
{ key: 'keepProcessAfterCancel', group: 'advanced', checkbox: true, acpOnly: true, label: '取消后保留进程', hint: '仅在官方进程确认停止后保留。' },
|
|
39
|
+
{ key: 'cancelGraceMs', group: 'advanced', numeric: true, defaultValue: 30000, min: 0, max: 60000, acpOnly: true, label: '取消确认宽限(毫秒)', hint: '0 表示立即回收。' },
|
|
40
|
+
{ key: 'idleRecycleMs', group: 'advanced', numeric: true, defaultValue: 300000, min: 0, max: 3600000, acpOnly: true, label: '进程空闲回收(毫秒)', hint: '默认 5 分钟;0 表示禁用。' },
|
|
41
|
+
{ key: 'binaryPath', group: 'launch', acpOnly: true, label: 'ACP Server 路径', placeholder: '留空自动解析或下载', hint: '优先于环境变量与缓存。' },
|
|
42
|
+
{ key: 'autoDownload', group: 'launch', checkbox: true, defaultChecked: true, acpOnly: true, label: '自动下载 ACP Server', hint: '仅从官方 dl.google.com 下载。' },
|
|
43
|
+
{ key: 'acpVersion', group: 'launch', acpOnly: true, label: 'ACP Server 版本', placeholder: '1.4.0', hint: '程序版本,不是 ACP 协议版本。' },
|
|
44
|
+
{ key: 'extraArgs', group: 'launch', lines: true, acpOnly: true, label: 'ACP 额外启动参数', placeholder: '每行一个参数', hint: '按行原样传给官方进程。' },
|
|
45
|
+
{ key: 'maxConcurrentSessions', group: 'pool', numeric: true, defaultValue: 2, min: 1, max: 4, acpOnly: true, label: '最大 ACP 连接数', hint: '范围 1–4。' },
|
|
46
|
+
{ key: 'queueTimeoutMs', group: 'pool', numeric: true, defaultValue: 120000, min: 1000, acpOnly: true, label: '连接排队超时(毫秒)', hint: '最小 1000。' },
|
|
47
|
+
{ key: 'requestTimeoutMs', group: 'timeouts', numeric: true, defaultValue: 30000, min: 0, acpOnly: true, label: '普通请求超时(毫秒)' },
|
|
48
|
+
{ key: 'handshakeTimeoutMs', group: 'timeouts', numeric: true, defaultValue: 120000, min: 1000, acpOnly: true, label: '握手超时(毫秒)', hint: '冷启动建议至少 120000。' },
|
|
49
|
+
{ key: 'authTimeoutMs', group: 'timeouts', numeric: true, defaultValue: 300000, min: 0, acpOnly: true, label: '浏览器登录超时(毫秒)' },
|
|
50
|
+
{ key: 'promptTimeoutMs', group: 'timeouts', numeric: true, defaultValue: 1800000, min: 0, acpOnly: true, label: '单轮生成超时(毫秒)', hint: '默认 30 分钟。' },
|
|
51
|
+
{ key: 'bridgeParkSettleMs', group: 'timeouts', numeric: true, defaultValue: 50, min: 0, acpOnly: true, label: '工具调用聚合窗口(毫秒)', hint: '不是工具执行超时。' },
|
|
51
52
|
]
|
|
52
53
|
|
|
53
54
|
/** 逐模型设置:键是 DSH 选择器里的模型 ID,值是开关、默认档位、显示名与官方 ID 映射。 */
|
|
@@ -149,7 +150,7 @@ window.__ModuleLoader__.load({
|
|
|
149
150
|
: account?.runtime?.phase === 'error'
|
|
150
151
|
? account.runtime.error ?? account?.message ?? 'ACP 连接异常,请重试。'
|
|
151
152
|
: registered === false ? '请确认插件已启用,Host 与 Client 均已加载。'
|
|
152
|
-
: startupHint(account) ?? account?.message ?? '
|
|
153
|
+
: startupHint(account) ?? account?.message ?? '连接后可读取模型目录,不发送聊天消息。'
|
|
153
154
|
return { label, description, ready, busy, error, color: error ? '#d97757' : ready ? '#25b879' : busy ? '#d99b28' : '#8b909a' }
|
|
154
155
|
}
|
|
155
156
|
function StatusDot({ color }) {
|
|
@@ -182,105 +183,130 @@ window.__ModuleLoader__.load({
|
|
|
182
183
|
runtime.error ? h('p', { role: 'alert', style: { margin: '4px 0 0', color: '#b42318', overflowWrap: 'anywhere' } }, runtime.error) : null)
|
|
183
184
|
}
|
|
184
185
|
function SectionHeading({ eyebrow, title, description, action }) {
|
|
185
|
-
return h('div', { style: { display: 'flex', alignItems: '
|
|
186
|
+
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '12px' } },
|
|
186
187
|
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
187
|
-
eyebrow ? h('div', { style: { fontSize: '10px', fontWeight: 650, letterSpacing: '.08em', opacity: .5, marginBottom: '
|
|
188
|
+
eyebrow ? h('div', { style: { fontSize: '10px', fontWeight: 650, letterSpacing: '.08em', opacity: .5, marginBottom: '3px' } }, eyebrow) : null,
|
|
188
189
|
h('h3', { style: { fontSize: '14px', lineHeight: 1.4, fontWeight: 650, margin: 0 } }, title),
|
|
189
|
-
description ? h('p', { style: { fontSize: '
|
|
190
|
+
description ? h('p', { style: { fontSize: '11px', lineHeight: 1.5, opacity: .6, margin: '3px 0 0' } }, description) : null),
|
|
190
191
|
action ?? null)
|
|
191
192
|
}
|
|
192
193
|
function TextField({ field, value, disabled, invalid, onChange }) {
|
|
193
194
|
const inputId = 'agy-setting-' + (React.useId?.() ?? field.key) + '-' + field.key
|
|
194
|
-
return h('div', { style: { display: 'grid', gap: '
|
|
195
|
+
return h('div', { style: { display: 'grid', gap: '4px', minWidth: 0 } },
|
|
195
196
|
h('label', { htmlFor: inputId, style: { fontSize: '12px', fontWeight: 550 } }, field.label),
|
|
196
197
|
h(field.lines ? 'textarea' : 'input', {
|
|
197
198
|
id: inputId, name: field.key, type: field.numeric ? 'number' : 'text',
|
|
198
199
|
...(field.numeric ? { min: field.min, max: field.max, step: 1 } : {}),
|
|
199
|
-
...(field.lines ? { rows:
|
|
200
|
+
...(field.lines ? { rows: 2 } : {}),
|
|
200
201
|
disabled, value: value ?? '', placeholder: field.placeholder,
|
|
201
|
-
'aria-invalid': invalid || undefined, 'aria-describedby': inputId + '-hint',
|
|
202
|
+
'aria-invalid': invalid || undefined, 'aria-describedby': field.hint ? inputId + '-hint' : undefined,
|
|
202
203
|
style: { ...ACTION, cursor: disabled ? 'not-allowed' : 'text', width: '100%', boxSizing: 'border-box', background: 'rgba(128,128,128,.035)', opacity: disabled ? .6 : 1 },
|
|
203
204
|
onChange: (event) => onChange(field.numeric && event.target.value !== '' ? Number(event.target.value) : event.target.value),
|
|
204
205
|
}),
|
|
205
|
-
h('small', { id: inputId + '-hint', style: { fontSize: '
|
|
206
|
+
field.hint ? h('small', { id: inputId + '-hint', style: { fontSize: '10px', lineHeight: 1.45, opacity: .58 } }, field.hint) : null)
|
|
206
207
|
}
|
|
207
208
|
function SwitchField({ field, checked, disabled, onChange }) {
|
|
208
209
|
return h('label', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', cursor: disabled ? 'default' : 'pointer', padding: '2px 0', opacity: disabled ? .6 : 1 } },
|
|
209
|
-
h('input', { name: field.key, type: 'checkbox', checked: checked === true, disabled, onChange: (event) => onChange(event.target.checked), style: { width: '16px', height: '16px', flex: '0 0 auto', margin: '
|
|
210
|
-
h('span', { style: { display: 'grid', gap: '
|
|
210
|
+
h('input', { name: field.key, type: 'checkbox', checked: checked === true, disabled, onChange: (event) => onChange(event.target.checked), style: { width: '16px', height: '16px', flex: '0 0 auto', margin: '1px 0 0', accentColor: '#177c55', cursor: disabled ? 'not-allowed' : 'pointer' } }),
|
|
211
|
+
h('span', { style: { display: 'grid', gap: '2px', minWidth: 0 } },
|
|
211
212
|
h('span', { style: { fontSize: '12px', fontWeight: 550 } }, field.label),
|
|
212
|
-
h('small', { style: { fontSize: '
|
|
213
|
+
field.hint ? h('small', { style: { fontSize: '10px', lineHeight: 1.45, opacity: .6 } }, field.hint) : null))
|
|
213
214
|
}
|
|
214
215
|
function SettingsFields({ fields, draft, dirtyKeys, conflicts, disabled, editField }) {
|
|
215
|
-
|
|
216
|
+
const numericGrid = fields.length > 1 && fields.every((field) => field.numeric)
|
|
217
|
+
return h('div', { style: { display: 'grid', gap: '12px', gridTemplateColumns: numericGrid ? 'repeat(auto-fit, minmax(180px, 1fr))' : undefined } }, ...fields.map((field) => {
|
|
216
218
|
const onChange = (value) => editField(field.key, value)
|
|
217
219
|
return field.checkbox
|
|
218
220
|
? h(SwitchField, { key: field.key, field, checked: draft[field.key], disabled, onChange })
|
|
219
221
|
: h(TextField, { key: field.key, field, value: draft[field.key], disabled, invalid: dirtyKeys.includes(field.key) && field.numeric && !Number.isSafeInteger(draft[field.key]), onChange })
|
|
220
222
|
}))
|
|
221
223
|
}
|
|
224
|
+
function SettingsGroup({ title, fields, fieldProps, open = false }) {
|
|
225
|
+
return h('details', { style: PANEL, 'aria-label': title + '设置', open: open || undefined },
|
|
226
|
+
h('summary', { style: SUMMARY },
|
|
227
|
+
h('span', { style: { fontSize: '12px', fontWeight: 650 } }, title),
|
|
228
|
+
h('small', { style: { fontSize: '10px', opacity: .55 } }, fields.length + ' 项')),
|
|
229
|
+
h('div', { style: { marginTop: '14px', paddingTop: '12px', borderTop: '1px solid rgba(128,128,128,.12)' } },
|
|
230
|
+
h(SettingsFields, { ...fieldProps, fields })))
|
|
231
|
+
}
|
|
222
232
|
|
|
223
|
-
/**
|
|
233
|
+
/** 紧凑列表行;完整 ID、输入能力、改名和映射集中在单条详情里。 */
|
|
224
234
|
function ModelOverrideRow({ row, override, disabled, onEdit }) {
|
|
225
235
|
const enabled = override.enabled !== false
|
|
226
236
|
const inputId = 'agy-model-' + row.id.replace(/[^\w.-]/g, '_')
|
|
227
237
|
const labelStyle = { fontSize: '11px', opacity: .6 }
|
|
228
238
|
const inputStyle = { ...ACTION, width: '100%', minWidth: 0, boxSizing: 'border-box', background: 'rgba(128,128,128,.035)', cursor: disabled ? 'not-allowed' : 'text' }
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
239
|
+
const displayName = override.name?.trim() || row.name
|
|
240
|
+
return h('div', { role: 'listitem', style: { padding: '9px 0', borderBottom: '1px solid rgba(128,128,128,.12)', opacity: enabled ? 1 : .58 } },
|
|
241
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', alignItems: 'center', gap: '8px' } },
|
|
242
|
+
h('label', { title: displayName, style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0, cursor: disabled ? 'default' : 'pointer' } },
|
|
232
243
|
h('input', { name: 'model-enabled-' + row.id, type: 'checkbox', checked: enabled, disabled,
|
|
233
244
|
onChange: (event) => onEdit(row.id, { enabled: event.target.checked }),
|
|
234
|
-
style: { width: '15px', height: '15px', flex: '0 0 auto', margin:
|
|
235
|
-
h('span', { style: {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
row.inCatalog
|
|
239
|
-
? (Array.isArray(row.inputModalities) && row.inputModalities.length
|
|
240
|
-
? h('span', { style: { flex: '0 0 auto', fontSize: '10px', padding: '2px 7px', borderRadius: '99px', background: TRACK } }, row.inputModalities.map((modality) => MODALITY_LABELS[modality] ?? modality).join('、'))
|
|
241
|
-
: null)
|
|
242
|
-
: h('span', { title: '该 ID 来自已保存的设置,当前目录里没有;仍然可以修改或清除', style: { flex: '0 0 auto', fontSize: '10px', padding: '2px 7px', borderRadius: '99px', color: '#946200', background: 'rgba(217,155,40,.12)' } }, '不在当前目录')),
|
|
243
|
-
h('div', { style: { display: 'grid', gap: '6px', gridTemplateColumns: 'minmax(120px, 160px) minmax(0, 1fr)' } },
|
|
244
|
-
h('div', { style: { display: 'grid', gap: '4px', minWidth: 0 } },
|
|
245
|
-
h('label', { htmlFor: inputId + '-effort', style: labelStyle }, '默认思考档位'),
|
|
245
|
+
style: { width: '15px', height: '15px', flex: '0 0 auto', margin: 0, accentColor: '#177c55', cursor: disabled ? 'not-allowed' : 'pointer' } }),
|
|
246
|
+
h('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: '12px', fontWeight: 600 } }, displayName)),
|
|
247
|
+
h('label', { htmlFor: inputId + '-effort', style: { display: 'flex', alignItems: 'center', gap: '5px', fontSize: '11px' } },
|
|
248
|
+
h('span', { style: { opacity: .65 } }, '思考'),
|
|
246
249
|
h('select', { id: inputId + '-effort', name: 'model-effort-' + row.id, disabled, value: EFFORT_IDS.has(override.effort) ? override.effort : '',
|
|
250
|
+
'aria-label': displayName + ' 默认思考档位',
|
|
247
251
|
onChange: (event) => onEdit(row.id, { effort: event.target.value }),
|
|
248
|
-
style: { ...inputStyle, cursor: disabled ? 'not-allowed' : 'pointer' } },
|
|
249
|
-
...EFFORT_OPTIONS.map((option) => h('option', { key: option.value, value: option.value }, option.label)))),
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
252
|
+
style: { ...inputStyle, width: 'auto', minHeight: '29px', padding: '3px 7px', cursor: disabled ? 'not-allowed' : 'pointer' } },
|
|
253
|
+
...EFFORT_OPTIONS.map((option) => h('option', { key: option.value, value: option.value }, option.label))))),
|
|
254
|
+
h('details', { style: { marginLeft: '23px', marginTop: '5px' } },
|
|
255
|
+
h('summary', { style: { ...SUMMARY, justifyContent: 'flex-start', fontSize: '10px', opacity: .6 } }, '模型设置'),
|
|
256
|
+
h('div', { style: { display: 'grid', gap: '10px', marginTop: '10px', padding: '10px', borderRadius: '8px', background: 'rgba(128,128,128,.04)' } },
|
|
257
|
+
h('div', { style: { display: 'grid', gap: '3px' } },
|
|
258
|
+
h('span', { style: labelStyle }, '模型 ID'),
|
|
259
|
+
h('code', { style: { fontSize: '10px', overflowWrap: 'anywhere', opacity: .75 } }, row.id)),
|
|
260
|
+
h('div', { style: { display: 'grid', gap: '10px', gridTemplateColumns: 'repeat(auto-fit, minmax(170px, 1fr))' } },
|
|
261
|
+
h('div', { style: { display: 'grid', gap: '4px', minWidth: 0 } },
|
|
262
|
+
h('label', { htmlFor: inputId + '-name', style: labelStyle }, '显示名'),
|
|
263
|
+
h('input', { id: inputId + '-name', name: 'model-name-' + row.id, type: 'text', disabled, value: override.name ?? '',
|
|
264
|
+
placeholder: row.name, onChange: (event) => onEdit(row.id, { name: event.target.value }), style: inputStyle })),
|
|
265
|
+
h('div', { style: { display: 'grid', gap: '4px', minWidth: 0 } },
|
|
266
|
+
h('label', { htmlFor: inputId + '-target', style: labelStyle }, '官方模型 ID'),
|
|
267
|
+
h('input', { id: inputId + '-target', name: 'model-target-' + row.id, type: 'text', disabled, value: override.target ?? '',
|
|
268
|
+
placeholder: '留空自动路由', onChange: (event) => onEdit(row.id, { target: event.target.value }), style: inputStyle }))),
|
|
269
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px 12px', fontSize: '10px', opacity: .6 } },
|
|
270
|
+
h('span', null, row.inCatalog ? '当前目录' : '不在当前目录'),
|
|
271
|
+
Array.isArray(row.inputModalities) && row.inputModalities.length
|
|
272
|
+
? h('span', null, row.inputModalities.map((modality) => MODALITY_LABELS[modality] ?? modality).join(' · ')) : null))))
|
|
258
273
|
}
|
|
259
274
|
|
|
260
|
-
/**
|
|
261
|
-
* 模型列表:一次列出目录中的每个模型,改动在保存时合并成一份
|
|
262
|
-
* `modelOverrides` 提交,空项自动清除,不留空壳配置。
|
|
263
|
-
*/
|
|
264
275
|
function ModelOverrideList({ rows, overrides, disabled, onEdit }) {
|
|
276
|
+
const [query, setQuery] = React.useState('')
|
|
277
|
+
const [filter, setFilter] = React.useState('all')
|
|
265
278
|
if (rows.length === 0) {
|
|
266
|
-
return h('p', { style: { margin: 0, fontSize: '12px', lineHeight: 1.
|
|
267
|
-
'还没有模型目录。连接 / 登录 ACP 或刷新模型目录后,可在此逐项设置显示开关、默认思考档位与映射。')
|
|
279
|
+
return h('p', { role: 'status', style: { margin: 0, fontSize: '12px', lineHeight: 1.5, opacity: .6 } }, '暂无模型。请先连接当前账户,再刷新目录。')
|
|
268
280
|
}
|
|
269
281
|
const hidden = rows.filter((row) => overrideFor(overrides, row.id).enabled === false).length
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
282
|
+
const keyword = query.trim().toLowerCase()
|
|
283
|
+
const visible = rows.filter((row) => {
|
|
284
|
+
const override = overrideFor(overrides, row.id)
|
|
285
|
+
if (filter === 'enabled' && override.enabled === false) return false
|
|
286
|
+
if (filter === 'hidden' && override.enabled !== false) return false
|
|
287
|
+
return !keyword || (row.name + ' ' + row.id + ' ' + (override.name ?? '')).toLowerCase().includes(keyword)
|
|
288
|
+
})
|
|
289
|
+
const setVisible = (enabled) => { for (const row of visible) onEdit(row.id, { enabled }) }
|
|
290
|
+
return h('div', { style: { display: 'grid', gap: '8px' } },
|
|
291
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: '8px' } },
|
|
292
|
+
h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索模型', 'aria-label': '搜索模型',
|
|
293
|
+
style: { ...ACTION, width: '100%', minWidth: 0, boxSizing: 'border-box', cursor: 'text' } }),
|
|
294
|
+
h('select', { value: filter, onChange: (event) => setFilter(event.target.value), 'aria-label': '筛选模型', style: ACTION },
|
|
295
|
+
h('option', { value: 'all' }, '全部 ' + rows.length),
|
|
296
|
+
h('option', { value: 'enabled' }, '已启用 ' + (rows.length - hidden)),
|
|
297
|
+
h('option', { value: 'hidden' }, '已隐藏 ' + hidden))),
|
|
298
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '10px', opacity: .7 } },
|
|
299
|
+
h('span', { style: { marginRight: 'auto' } }, '显示 ' + visible.length + ' / ' + rows.length),
|
|
300
|
+
h('button', { type: 'button', disabled: disabled || visible.length === 0, onClick: () => setVisible(true), style: { ...ACTION, minHeight: '26px', padding: '2px 7px', fontSize: '10px' } }, '全部启用'),
|
|
301
|
+
h('button', { type: 'button', disabled: disabled || visible.length === 0, onClick: () => setVisible(false), style: { ...ACTION, minHeight: '26px', padding: '2px 7px', fontSize: '10px' } }, '全部隐藏')),
|
|
276
302
|
hidden === rows.length
|
|
277
|
-
? h('p', { role: 'status', style: { margin: 0, fontSize: '11px', lineHeight: 1.
|
|
278
|
-
'所有模型都已隐藏:保存后 Antigravity 将不出现在 DSH 模型选择器中,重新启用任一项即可恢复。')
|
|
303
|
+
? h('p', { role: 'status', style: { margin: 0, fontSize: '11px', lineHeight: 1.5, color: '#946200' } }, '全部隐藏后,Antigravity 将从模型选择器消失。')
|
|
279
304
|
: null,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
305
|
+
visible.length
|
|
306
|
+
? h('div', { role: 'list', style: { display: 'grid' } }, ...visible.map((row) => h(ModelOverrideRow, {
|
|
307
|
+
key: row.id, row, override: overrideFor(overrides, row.id), disabled, onEdit,
|
|
308
|
+
})))
|
|
309
|
+
: h('p', { role: 'status', style: { margin: 0, fontSize: '12px', opacity: .6 } }, '没有符合条件的模型。'))
|
|
284
310
|
}
|
|
285
311
|
|
|
286
312
|
/**
|
|
@@ -288,7 +314,7 @@ window.__ModuleLoader__.load({
|
|
|
288
314
|
* 状态与授权 URL 经 Host 的 remote.agyProxyAccount 读取;授权 URL 只在
|
|
289
315
|
* 点击「连接代理账户」后返回一次,展示为可打开的链接。
|
|
290
316
|
*/
|
|
291
|
-
function ProxyAccountPanel({ control,
|
|
317
|
+
function ProxyAccountPanel({ control, disabled, connectDisabled, onAccountChanged }) {
|
|
292
318
|
const [snapshot, setSnapshot] = React.useState(undefined)
|
|
293
319
|
const [authUrl, setAuthUrl] = React.useState(undefined)
|
|
294
320
|
const [message, setMessage] = React.useState('')
|
|
@@ -356,13 +382,9 @@ window.__ModuleLoader__.load({
|
|
|
356
382
|
phase === 'signed-in' ? h('button', { type: 'button', style: ACTION, disabled: disabled || busy, onClick: () => act('logout') }, '退出登录') : null),
|
|
357
383
|
authUrl && stage !== 'finishing' ? h('div', { style: { display: 'grid', gap: '6px', justifyItems: 'start' } },
|
|
358
384
|
h('a', { href: authUrl, target: '_blank', rel: 'noopener noreferrer', style: { ...PRIMARY, textDecoration: 'none', display: 'inline-flex', alignItems: 'center' } }, '打开 Google 授权页'),
|
|
359
|
-
h('small', { style: { fontSize: '
|
|
360
|
-
phase === 'pending-login' && stage !== 'finishing' && !authUrl && !busy ? h('p', { role: 'status', style: { fontSize: '11px', margin: 0, opacity: .7 } }, '
|
|
361
|
-
h('p', { style: { fontSize: '
|
|
362
|
-
h('p', { style: { opacity: .55, fontSize: '11px', lineHeight: 1.6, margin: 0 } },
|
|
363
|
-
proxyMode
|
|
364
|
-
? '代理模式已开启:生成与额度都使用该账户;官方 ACP 路由已停用。'
|
|
365
|
-
: '代理模式关闭时该账户待命;连接时优先导入本机官方 CLI 登录态,缺失时走浏览器授权(回调端口 51121)。'))
|
|
385
|
+
h('small', { style: { fontSize: '10px', opacity: .65, lineHeight: 1.5 } }, '请在运行 DSH 的设备上完成授权(5 分钟内有效)。')) : null,
|
|
386
|
+
phase === 'pending-login' && stage !== 'finishing' && !authUrl && !busy ? h('p', { role: 'status', style: { fontSize: '11px', margin: 0, opacity: .7 } }, '授权页已打开;找不到时请取消后重试。') : null,
|
|
387
|
+
h('p', { style: { opacity: .55, fontSize: '10px', lineHeight: 1.5, margin: 0 } }, '生成与套餐额度使用此账户 · 无需 agy CLI · ACP 已停用'))
|
|
366
388
|
}
|
|
367
389
|
|
|
368
390
|
function AntigravitySettingsCard(props) {
|
|
@@ -386,7 +408,7 @@ window.__ModuleLoader__.load({
|
|
|
386
408
|
const value = unwrap(await props.control('models', force ? { force: true } : undefined))
|
|
387
409
|
if (!alive.current || version !== modelVersion.current) return
|
|
388
410
|
setModels(value.models ?? [])
|
|
389
|
-
setModelMessage(value.status === 'error' ? value.message :
|
|
411
|
+
setModelMessage(value.status === 'error' ? value.message : '')
|
|
390
412
|
} catch (error) { if (alive.current && version === modelVersion.current) setModelMessage(error instanceof Error ? error.message : String(error)) }
|
|
391
413
|
finally {
|
|
392
414
|
if (force) manualModelPull.current = false
|
|
@@ -429,6 +451,8 @@ window.__ModuleLoader__.load({
|
|
|
429
451
|
const [dirtyKeys, setDirtyKeys] = React.useState([])
|
|
430
452
|
const [conflicts, setConflicts] = React.useState([])
|
|
431
453
|
const [savedProxyMode, setSavedProxyMode] = React.useState(false)
|
|
454
|
+
// 设置分页。非活动页只隐藏、不卸载:草稿、保存与控件覆盖测试都依赖字段始终挂载。
|
|
455
|
+
const [settingsPage, setSettingsPage] = React.useState('connection')
|
|
432
456
|
const settingsVersion = React.useRef(0)
|
|
433
457
|
const savingRef = React.useRef(false)
|
|
434
458
|
const refreshAfterSave = React.useRef(false)
|
|
@@ -588,8 +612,8 @@ window.__ModuleLoader__.load({
|
|
|
588
612
|
setDirtyKeys([...dirty.current])
|
|
589
613
|
setConflicts([])
|
|
590
614
|
setStatus({ kind: 'ok', text: ops.some((operation) => operation.path[0] === 'proxyMode')
|
|
591
|
-
? '
|
|
592
|
-
: '
|
|
615
|
+
? '已保存并切换模式,请在会话中选择对应模型。'
|
|
616
|
+
: '已保存;进行中的会话不会被中断。' })
|
|
593
617
|
await reload(true)
|
|
594
618
|
void accountAction('status')
|
|
595
619
|
void pullModels(true)
|
|
@@ -632,104 +656,94 @@ window.__ModuleLoader__.load({
|
|
|
632
656
|
setDraft(draftRef.current)
|
|
633
657
|
setDirtyKeys([])
|
|
634
658
|
setConflicts([])
|
|
635
|
-
setStatus({ kind: 'ok', text: '
|
|
659
|
+
setStatus({ kind: 'ok', text: '已撤销未保存修改。' })
|
|
636
660
|
}
|
|
637
661
|
|
|
638
662
|
const connection = connectionStatus(account, registered)
|
|
639
663
|
const fieldsFor = (group) => FIELDS.filter((field) => field.group === group && (!effectiveProxyMode || !field.acpOnly))
|
|
640
664
|
const fieldProps = { draft, dirtyKeys, conflicts, disabled: editingDisabled, editField }
|
|
665
|
+
const pageStyle = (id) => ({ display: settingsPage === id ? 'flex' : 'none', flexDirection: 'column', gap: '14px' })
|
|
666
|
+
const pageButton = (id, label) => h('button', {
|
|
667
|
+
type: 'button', role: 'tab', 'aria-selected': settingsPage === id, 'aria-controls': 'agy-settings-page-' + id,
|
|
668
|
+
onClick: () => setSettingsPage(id),
|
|
669
|
+
style: { ...ACTION, minHeight: '32px', padding: '5px 12px', fontWeight: settingsPage === id ? 650 : 450, borderColor: settingsPage === id ? '#177c55' : 'rgba(128,128,128,.22)', background: settingsPage === id ? 'rgba(23,124,85,.12)' : 'transparent' },
|
|
670
|
+
}, label)
|
|
641
671
|
const busy = ['connecting', 'cancelling'].includes(account?.state) || runtimeBusy(account?.runtime)
|
|
642
672
|
const readonly = !loading && view && !writable
|
|
643
|
-
const
|
|
644
|
-
h('span', { style: { display: 'grid', gap: '4px' } },
|
|
645
|
-
h('span', { style: { fontSize: '13px', fontWeight: 650 } }, '高级设置'),
|
|
646
|
-
h('small', { style: { fontSize: '11px', fontWeight: 400, opacity: .6 } }, effectiveProxyMode ? '代理网络设置' : '网络、启动、并发、超时与进程生命周期')))
|
|
673
|
+
const saveVisible = dirtyKeys.length > 0 || loading || readonly || conflicts.length > 0 || Boolean(status)
|
|
647
674
|
|
|
648
|
-
return h('div', { className: 'antigravity-settings-card', style: { padding: '
|
|
649
|
-
h('div', { role: 'region', 'aria-label': '设置保存操作', style: { position: 'sticky', top: 0, zIndex: 20, flexShrink: 0, background: 'var(--dsh-surface, Canvas)', color: 'var(--dsh-text, CanvasText)', padding: '
|
|
650
|
-
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '
|
|
651
|
-
h('button', { type: 'button', onClick: save, disabled: editingDisabled || dirtyKeys.length === 0, style: PRIMARY }, saving ? '保存中…' : '保存'),
|
|
652
|
-
dirtyKeys.length ? h('button', { type: 'button', disabled: loading || saving, onClick: discardDraft, style: ACTION }, '
|
|
653
|
-
h('span', { role: 'status', 'aria-live': 'polite', style: { fontSize: '11px', opacity: .7 } }, dirtyKeys.length ? dirtyKeys.length + ' 项未保存' : loading ? '
|
|
654
|
-
h('button', { type: 'button', title: '
|
|
655
|
-
conflicts.length ? h('p', { role: 'alert', style: { margin: 0, fontSize: '12px', lineHeight: 1.
|
|
675
|
+
return h('div', { className: 'antigravity-settings-card', style: { padding: '12px', display: 'flex', flexDirection: 'column', gap: '10px', maxWidth: '760px', boxSizing: 'border-box', lineHeight: 1.5 } },
|
|
676
|
+
h('div', { role: 'region', 'aria-label': '设置保存操作', style: { position: 'sticky', top: 0, zIndex: 20, flexShrink: 0, background: 'var(--dsh-surface, Canvas)', color: 'var(--dsh-text, CanvasText)', padding: '4px 0', display: saveVisible ? 'grid' : 'none', gap: '6px' } },
|
|
677
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '7px', alignItems: 'center' } },
|
|
678
|
+
h('button', { type: 'button', onClick: save, disabled: editingDisabled || dirtyKeys.length === 0, style: { ...PRIMARY, minHeight: '30px', padding: '4px 10px' } }, saving ? '保存中…' : '保存'),
|
|
679
|
+
dirtyKeys.length ? h('button', { type: 'button', disabled: loading || saving, onClick: discardDraft, style: { ...ACTION, minHeight: '30px', padding: '4px 9px' } }, '撤销') : null,
|
|
680
|
+
h('span', { role: 'status', 'aria-live': 'polite', style: { fontSize: '11px', opacity: .7 } }, dirtyKeys.length ? dirtyKeys.length + ' 项未保存' : loading ? '正在读取…' : readonly ? '只读设置' : ''),
|
|
681
|
+
h('button', { type: 'button', title: '重新读取配置并保留草稿', disabled: loading || saving, onClick: () => reload(), style: { ...ACTION, minHeight: '30px', padding: '4px 8px', borderColor: 'transparent', background: 'transparent', opacity: .7, marginLeft: 'auto' } }, loading ? '刷新中…' : '刷新')),
|
|
682
|
+
conflicts.length ? h('p', { role: 'alert', style: { margin: 0, fontSize: '12px', lineHeight: 1.5 } }, '以下内容已在其他位置更新,已保留你的输入:' + conflicts.map((key) => key === OVERRIDES_KEY ? OVERRIDES_LABEL : FIELDS.find((field) => field.key === key)?.label ?? key).join('、')) : null,
|
|
656
683
|
h(StatusNotice, { status })),
|
|
657
|
-
h('header', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'flex-start', gap: '10px', padding: '0 2px 2px' } },
|
|
658
|
-
h('div', { style: { flex: '1 1 220px', minWidth: 0 } },
|
|
659
|
-
h('div', { style: { fontSize: '10px', fontWeight: 650, letterSpacing: '.1em', opacity: .5, marginBottom: '5px' } }, 'MODEL PROVIDER'),
|
|
660
|
-
h('h2', { style: { margin: 0, fontSize: '20px', lineHeight: 1.35, fontWeight: 650, letterSpacing: '-.015em' } }, 'Antigravity ACP'),
|
|
661
|
-
h('p', { style: { margin: '6px 0 0', fontSize: '12px', opacity: .6 } }, '连接官方 Antigravity,模型与工具仍由 DSH 管理。')),
|
|
662
|
-
h('span', { title: 'Provider 路由:' + (effectiveProxyMode ? PROXY_PROVIDER : PROVIDER), style: { flex: '0 0 auto', padding: '4px 9px', borderRadius: '99px', fontSize: '11px', color: registered === false ? '#b42318' : registered === true ? '#177c55' : 'inherit', background: registered === false ? 'rgba(217,119,87,.10)' : registered === true ? 'rgba(37,184,121,.10)' : TRACK } }, registered === false ? '未注册' : registered === true ? 'Provider 已注册' : '正在检查路由'),
|
|
663
|
-
h('span', { title: effectiveProxyMode ? '生成直连上游,官方 ACP 路由停用' : '生成走官方 ACP 进程', style: { flex: '0 0 auto', padding: '4px 9px', borderRadius: '99px', fontSize: '11px', color: effectiveProxyMode ? '#946200' : 'inherit', background: effectiveProxyMode ? 'rgba(217,159,4,.12)' : TRACK } }, effectiveProxyMode ? '代理模式' : 'ACP 模式')),
|
|
664
|
-
|
|
665
|
-
readonly ? h(StatusNotice, { status: { kind: 'busy', text: '当前设置为只读。你仍可查看连接状态与诊断;请在有写入权限的宿主中修改配置。' } }) : null,
|
|
666
|
-
connectionSettingsDirty ? h(StatusNotice, { status: { kind: 'busy', text: '网络设置尚未保存。请先点击顶部「保存」,再连接或登录;取消和退出仍可使用。' } }) : null,
|
|
667
|
-
|
|
668
|
-
h('section', { style: PANEL, 'aria-label': '运行模式' },
|
|
669
|
-
h(SectionHeading, { title: '运行模式', description: '保存后切换生成方式,再连接对应账户并选择模型。' }),
|
|
670
|
-
h(SettingsFields, { ...fieldProps, fields: fieldsFor('mode') }),
|
|
671
|
-
dirtyKeys.includes('proxyMode') ? h('p', { role: 'status', style: { fontSize: '12px', color: '#946200' } }, '模式修改尚未保存,当前仍使用' + (effectiveProxyMode ? '代理模式。' : ' ACP 模式。')) : null,
|
|
672
|
-
proxyUnavailable ? h(StatusNotice, { status: { kind: 'error', text: '代理路由组件尚未就绪,ACP 功能保持停用。请在插件页启用 Antigravity Proxy Router 组件,再连接代理账户。' } }) : null),
|
|
673
|
-
|
|
674
|
-
!effectiveProxyMode ? h('section', { style: PANEL, 'aria-label': '账户连接' },
|
|
675
|
-
h(SectionHeading, { eyebrow: '01 · 连接', title: '账户与连接', description: '先预热减少等待,或验证账户并读取模型目录。' }),
|
|
676
|
-
h('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', padding: '12px', borderRadius: '10px', background: 'rgba(128,128,128,.045)', marginBottom: '14px' } },
|
|
677
|
-
h(StatusDot, { color: connection.color }),
|
|
678
|
-
h('div', { style: { minWidth: 0 } },
|
|
679
|
-
h('div', { style: { fontSize: '12px', fontWeight: 650, color: connection.color } }, connection.label),
|
|
680
|
-
h('p', { role: 'status', style: { fontSize: '12px', lineHeight: 1.6, opacity: .7, overflowWrap: 'anywhere', margin: '3px 0 0' } }, connection.description))),
|
|
681
|
-
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px' } },
|
|
682
|
-
h('button', { type: 'button', style: PRIMARY, disabled: !props.control || busy || connectionSettingsDirty, onClick: () => accountAction('login') }, account?.state === 'connecting' ? '正在连接…' : '连接 / 登录 ACP'),
|
|
683
|
-
h('button', { type: 'button', style: ACTION, disabled: !props.control || busy || connection.ready || connectionSettingsDirty, onClick: () => accountAction('warmup') }, connection.ready ? '已预热' : '立即预热'),
|
|
684
|
-
['connecting', 'cancelling'].includes(account?.state) ? h('button', { type: 'button', style: ACTION, disabled: account?.state === 'cancelling', onClick: () => accountAction('cancel') }, account?.state === 'cancelling' ? '正在取消…' : '取消连接') : null),
|
|
685
|
-
h('p', { style: { opacity: .55, fontSize: '11px', lineHeight: 1.6, margin: '10px 0 0' } }, '预热不会发送聊天消息。仅在需要登录时打开官方授权页面;思考强度仍由会话菜单设置。')) : null,
|
|
686
|
-
|
|
687
|
-
h('section', { style: PANEL, 'aria-label': '代理账户' },
|
|
688
|
-
h(SectionHeading, { eyebrow: '01b · 代理模式', title: '代理账户', description: '使用 Google 账户授权,不需要安装 agy CLI。' }),
|
|
689
|
-
h(ProxyAccountPanel, { control: props.proxyControl, proxyMode: effectiveProxyMode, onAccountChanged: onProxyAccountChanged, disabled: !props.proxyControl || !view || !writable, connectDisabled: editingDisabled || connectionSettingsDirty })),
|
|
690
|
-
|
|
691
|
-
h('section', { style: PANEL, 'aria-label': '模型列表' },
|
|
692
|
-
h(SectionHeading, { eyebrow: '02 · 模型', title: '模型列表', description: '目录中的模型是否出现在 DSH 选择器,以及各自的默认思考档位与官方 ID 映射。',
|
|
693
|
-
action: h('button', { type: 'button', title: '按当前已保存的模式重新读取模型目录', 'aria-busy': modelBusy || undefined, style: { ...ACTION, flex: '0 0 auto', whiteSpace: 'nowrap' }, disabled: modelBusy || !props.control, onClick: () => pullModels(true) }, modelBusy ? '正在刷新…' : '刷新模型目录') }),
|
|
694
|
-
h(ModelOverrideList, { rows: rows, overrides: draft[OVERRIDES_KEY], disabled: editingDisabled, onEdit: editOverride }),
|
|
695
|
-
modelMessage ? h('p', { role: 'status', style: { fontSize: '11px', lineHeight: 1.6, opacity: .65, margin: '10px 0 0', overflowWrap: 'anywhere' } }, modelMessage) : null),
|
|
696
684
|
|
|
697
|
-
h(
|
|
698
|
-
|
|
685
|
+
connectionSettingsDirty ? h(StatusNotice, { status: { kind: 'busy', text: '请先保存网络设置,再连接或登录。' } }) : null,
|
|
686
|
+
proxyUnavailable ? h(StatusNotice, { status: { kind: 'error', text: '代理路由未就绪,ACP 保持停用。请启用 Antigravity Proxy Router 组件。' } }) : null,
|
|
687
|
+
h('div', { role: 'tablist', 'aria-label': '设置分页', style: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '6px' } },
|
|
688
|
+
pageButton('connection', '连接'),
|
|
689
|
+
pageButton('models', '模型'),
|
|
690
|
+
pageButton('quota', '额度'),
|
|
691
|
+
pageButton('advanced', '高级'),
|
|
692
|
+
h('span', { style: { marginLeft: 'auto', padding: '3px 7px', borderRadius: '99px', fontSize: '10px', color: registered === false ? '#b42318' : effectiveProxyMode ? '#946200' : 'inherit', background: effectiveProxyMode ? 'rgba(217,159,4,.12)' : TRACK } }, registered === false ? '路由未注册' : effectiveProxyMode ? '代理模式' : 'ACP 模式')),
|
|
699
693
|
|
|
700
|
-
|
|
701
|
-
h(
|
|
702
|
-
|
|
694
|
+
h('div', { id: 'agy-settings-page-connection', role: 'tabpanel', style: pageStyle('connection') },
|
|
695
|
+
h('section', { style: PANEL, 'aria-label': '运行模式' },
|
|
696
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('mode') }),
|
|
697
|
+
dirtyKeys.includes('proxyMode') ? h('p', { role: 'status', style: { margin: '8px 0 0', fontSize: '11px', color: '#946200' } }, '保存后切换到' + (draft.proxyMode ? '代理模式' : 'ACP 模式')) : null),
|
|
698
|
+
effectiveProxyMode
|
|
699
|
+
? h('section', { style: PANEL, 'aria-label': '代理账户' },
|
|
700
|
+
h(SectionHeading, { title: '代理账户' }),
|
|
701
|
+
h(ProxyAccountPanel, { control: props.proxyControl, onAccountChanged: onProxyAccountChanged, disabled: !props.proxyControl || !view || !writable, connectDisabled: editingDisabled || connectionSettingsDirty }))
|
|
702
|
+
: h('section', { style: PANEL, 'aria-label': '账户连接' },
|
|
703
|
+
h(SectionHeading, { title: 'ACP 账户与连接' }),
|
|
704
|
+
h('div', { style: { display: 'flex', alignItems: 'flex-start', gap: '10px', marginBottom: '12px' } },
|
|
705
|
+
h(StatusDot, { color: connection.color }),
|
|
706
|
+
h('div', { style: { minWidth: 0 } },
|
|
707
|
+
h('div', { style: { fontSize: '12px', fontWeight: 650, color: connection.color } }, connection.label),
|
|
708
|
+
h('p', { role: 'status', style: { fontSize: '11px', lineHeight: 1.5, opacity: .7, overflowWrap: 'anywhere', margin: '3px 0 0' } }, connection.description))),
|
|
709
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px' } },
|
|
710
|
+
h('button', { type: 'button', style: PRIMARY, disabled: !props.control || busy || connectionSettingsDirty, onClick: () => accountAction('login') }, account?.state === 'connecting' ? '正在连接…' : '连接 / 登录 ACP'),
|
|
711
|
+
h('button', { type: 'button', style: ACTION, disabled: !props.control || busy || connection.ready || connectionSettingsDirty, onClick: () => accountAction('warmup') }, connection.ready ? '已预热' : '立即预热'),
|
|
712
|
+
['connecting', 'cancelling'].includes(account?.state) ? h('button', { type: 'button', style: ACTION, disabled: account?.state === 'cancelling', onClick: () => accountAction('cancel') }, account?.state === 'cancelling' ? '正在取消…' : '取消连接') : null))),
|
|
703
713
|
|
|
704
|
-
h('
|
|
705
|
-
h('
|
|
706
|
-
h(
|
|
707
|
-
h('
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, 'ACP 启动'),
|
|
711
|
-
h(SettingsFields, { ...fieldProps, fields: fieldsFor('launch') })) : null,
|
|
712
|
-
!effectiveProxyMode ? h('section', { 'aria-label': 'ACP 连接池设置', style: { display: 'grid', gap: '16px' } },
|
|
713
|
-
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, '连接池'),
|
|
714
|
-
h(SettingsFields, { ...fieldProps, fields: fieldsFor('pool') })) : null,
|
|
715
|
-
!effectiveProxyMode ? h('section', { 'aria-label': 'ACP 超时设置', style: { display: 'grid', gap: '16px' } },
|
|
716
|
-
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, '请求与工具超时'),
|
|
717
|
-
h(SettingsFields, { ...fieldProps, fields: fieldsFor('timeouts') })) : null,
|
|
718
|
-
!effectiveProxyMode ? h('section', { 'aria-label': '会话与进程设置', style: { display: 'grid', gap: '16px' } },
|
|
719
|
-
h('h4', { style: { margin: 0, fontSize: '12px', fontWeight: 650 } }, '会话与进程'),
|
|
720
|
-
h(SettingsFields, { ...fieldProps, fields: fieldsFor('advanced') })) : null)),
|
|
714
|
+
h('div', { id: 'agy-settings-page-models', role: 'tabpanel', style: pageStyle('models') },
|
|
715
|
+
h('section', { style: PANEL, 'aria-label': '模型列表' },
|
|
716
|
+
h(SectionHeading, { title: '模型',
|
|
717
|
+
action: h('button', { type: 'button', title: '刷新模型目录', 'aria-label': '刷新模型目录', 'aria-busy': modelBusy || undefined, style: { ...ACTION, flex: '0 0 auto', whiteSpace: 'nowrap' }, disabled: modelBusy || !props.control, onClick: () => pullModels(true) }, modelBusy ? '正在刷新…' : '刷新') }),
|
|
718
|
+
h(ModelOverrideList, { rows, overrides: draft[OVERRIDES_KEY], disabled: editingDisabled, onEdit: editOverride }),
|
|
719
|
+
modelMessage ? h('p', { role: 'status', style: { fontSize: '11px', lineHeight: 1.5, opacity: .65, margin: 0, overflowWrap: 'anywhere' } }, modelMessage) : null)),
|
|
721
720
|
|
|
722
|
-
|
|
723
|
-
h(
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
721
|
+
h('div', { id: 'agy-settings-page-quota', role: 'tabpanel', style: pageStyle('quota') },
|
|
722
|
+
h(SettingsQuotaPanel, { readQuota: props.readQuota, source: effectiveProxyMode ? PROXY_PROVIDER : PROVIDER,
|
|
723
|
+
accountRevision: effectiveProxyMode ? previousProxyAccount.current?.revision : undefined,
|
|
724
|
+
cliQuotaOn: (dirtyKeys.includes('cliQuotaEnabled') ? baseline.current.cliQuotaEnabled : draft.cliQuotaEnabled) === true,
|
|
725
|
+
cliQuotaDirty: dirtyKeys.includes('cliQuotaEnabled') }),
|
|
726
|
+
h('section', { style: PANEL, 'aria-label': '额度取数方式' },
|
|
727
|
+
h(SectionHeading, { title: 'CLI 额度(可选)' }),
|
|
728
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('quota') }))),
|
|
730
729
|
|
|
731
|
-
h('
|
|
732
|
-
h('
|
|
730
|
+
h('div', { id: 'agy-settings-page-advanced', role: 'tabpanel', style: pageStyle('advanced') },
|
|
731
|
+
!effectiveProxyMode ? h('section', { style: PANEL, 'aria-label': '常用设置' },
|
|
732
|
+
h(SectionHeading, { title: '会话偏好' }),
|
|
733
|
+
h(SettingsFields, { ...fieldProps, fields: fieldsFor('general') })) : null,
|
|
734
|
+
h(SettingsGroup, { title: '网络与 CLI', fields: fieldsFor('network'), fieldProps, open: effectiveProxyMode }),
|
|
735
|
+
!effectiveProxyMode ? h(SettingsGroup, { title: 'ACP 启动', fields: fieldsFor('launch'), fieldProps }) : null,
|
|
736
|
+
!effectiveProxyMode ? h(SettingsGroup, { title: '连接池', fields: fieldsFor('pool'), fieldProps }) : null,
|
|
737
|
+
!effectiveProxyMode ? h(SettingsGroup, { title: '请求与工具超时', fields: fieldsFor('timeouts'), fieldProps }) : null,
|
|
738
|
+
!effectiveProxyMode ? h(SettingsGroup, { title: '会话与进程', fields: fieldsFor('advanced'), fieldProps }) : null,
|
|
739
|
+
!effectiveProxyMode ? h('details', { style: PANEL, 'aria-label': '连接诊断' },
|
|
740
|
+
h('summary', { style: SUMMARY },
|
|
741
|
+
h('span', { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
742
|
+
h(StatusDot, { color: connection.color }),
|
|
743
|
+
h('span', { style: { fontSize: '13px', fontWeight: 650 } }, '连接诊断')),
|
|
744
|
+
h('small', { style: { fontSize: '11px', opacity: .6 } }, '状态、并发与耗时')),
|
|
745
|
+
h('div', { style: { marginTop: '16px', paddingTop: '14px', borderTop: '1px solid rgba(128,128,128,.14)' } },
|
|
746
|
+
account?.runtime ? RuntimeDetails({ runtime: account.runtime }) : h('p', { style: { margin: 0, fontSize: '12px', opacity: .6 } }, '连接后显示运行详情。'))) : null))
|
|
733
747
|
}
|
|
734
748
|
|
|
735
749
|
/**
|
|
@@ -741,7 +755,7 @@ window.__ModuleLoader__.load({
|
|
|
741
755
|
|
|
742
756
|
const TRACK = 'rgba(128,128,128,.18)'
|
|
743
757
|
const CARD = { padding: '12px', borderRadius: '12px', border: '1px solid rgba(128,128,128,.18)', background: 'rgba(128,128,128,.05)' }
|
|
744
|
-
const PANEL = { padding: '
|
|
758
|
+
const PANEL = { padding: '14px', borderRadius: '12px', border: '1px solid rgba(128,128,128,.16)', background: 'rgba(128,128,128,.025)' }
|
|
745
759
|
const SUMMARY = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '12px', cursor: 'pointer', listStyle: 'none' }
|
|
746
760
|
const ACTION = { padding: '7px 12px', minHeight: '34px', borderRadius: '8px', border: '1px solid rgba(128,128,128,.22)', background: 'rgba(128,128,128,.06)', color: 'inherit', font: 'inherit', fontSize: '12px', cursor: 'pointer' }
|
|
747
761
|
const PRIMARY = { ...ACTION, background: '#177c55', border: '1px solid #177c55', color: '#fff', fontWeight: 600 }
|
|
@@ -797,17 +811,19 @@ window.__ModuleLoader__.load({
|
|
|
797
811
|
style: { display: 'block', width: compact ? '34px' : '100%', height: compact ? compactHeight + 'px' : '4px', borderRadius: '99px', overflow: 'hidden', background: TRACK },
|
|
798
812
|
}, h('span', { style: { display: 'block', height: '100%', width: unknown ? '100%' : pct + '%', opacity: unknown ? .35 : 1, borderRadius: '99px', background: tone(bucket, stale) } }))
|
|
799
813
|
}
|
|
800
|
-
function useQuota(readQuota, enabled = true, source) {
|
|
814
|
+
function useQuota(readQuota, enabled = true, source, model) {
|
|
801
815
|
const [data, setData] = React.useState(null)
|
|
802
816
|
const [refreshing, setRefreshing] = React.useState(false)
|
|
803
817
|
const version = React.useRef(0)
|
|
804
818
|
const currentSource = React.useRef(source)
|
|
819
|
+
const currentModel = React.useRef(model)
|
|
820
|
+
currentModel.current = model
|
|
805
821
|
const update = React.useCallback((value) => setData(value), [])
|
|
806
822
|
const load = React.useCallback((force = false) => {
|
|
807
823
|
if (!enabled || !readQuota) return Promise.resolve()
|
|
808
824
|
const current = ++version.current
|
|
809
825
|
setRefreshing(true)
|
|
810
|
-
return Promise.resolve().then(() => readQuota(force, source)).then(unwrap).then((value) => {
|
|
826
|
+
return Promise.resolve().then(() => readQuota(force, source, model)).then(unwrap).then((value) => {
|
|
811
827
|
if (current === version.current) update(value)
|
|
812
828
|
}).catch((error) => {
|
|
813
829
|
if (current === version.current) {
|
|
@@ -817,7 +833,7 @@ window.__ModuleLoader__.load({
|
|
|
817
833
|
: { status: 'error', groups: [], error: { message } })
|
|
818
834
|
}
|
|
819
835
|
}).finally(() => { if (current === version.current) setRefreshing(false) })
|
|
820
|
-
}, [readQuota, enabled, update, source])
|
|
836
|
+
}, [readQuota, enabled, update, source, model])
|
|
821
837
|
React.useEffect(() => {
|
|
822
838
|
currentSource.current = source
|
|
823
839
|
setData(null)
|
|
@@ -825,6 +841,7 @@ window.__ModuleLoader__.load({
|
|
|
825
841
|
void load(false)
|
|
826
842
|
const off = readQuota.subscribe?.((value, origin) => {
|
|
827
843
|
if (origin !== undefined && origin !== source) return
|
|
844
|
+
if (value?.requestedModel !== undefined && value.requestedModel !== currentModel.current) return
|
|
828
845
|
if (value === null) {
|
|
829
846
|
++version.current; update(null); setRefreshing(false)
|
|
830
847
|
void load(true)
|
|
@@ -834,12 +851,12 @@ window.__ModuleLoader__.load({
|
|
|
834
851
|
})
|
|
835
852
|
const timer = setInterval(() => { if (typeof document === 'undefined' || document.visibilityState !== 'hidden') void load(false) }, 300_000)
|
|
836
853
|
return () => { ++version.current; clearInterval(timer); off?.() }
|
|
837
|
-
}, [load, enabled, readQuota, update, source])
|
|
854
|
+
}, [load, enabled, readQuota, update, source, model])
|
|
838
855
|
return { data: currentSource.current === source ? data : null, refreshing, load }
|
|
839
856
|
}
|
|
840
857
|
function CreditsDetails({ credits, pending }) {
|
|
841
858
|
const valid = credits?.status === 'ok' && Number.isSafeInteger(credits.remainingCredits) && credits.remainingCredits >= 0
|
|
842
|
-
const label = valid ? credits.remainingCredits.toLocaleString() : pending ? '正在读取…' : credits?.status === 'error' ? '查询失败' : '未提供'
|
|
859
|
+
const label = valid ? credits.remainingCredits.toLocaleString() : pending ? '正在读取…' : credits?.status === 'error' ? '查询失败' : credits?.status === 'unavailable' ? '未启用' : '未提供'
|
|
843
860
|
return h('section', { style: { padding: '9px 0', borderTop: '1px solid rgba(128,128,128,.16)' }, 'aria-label': 'AI Credits 余额' },
|
|
844
861
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '12px' } },
|
|
845
862
|
h('strong', { style: { fontSize: '12px' } }, 'AI Credits'),
|
|
@@ -850,7 +867,7 @@ window.__ModuleLoader__.load({
|
|
|
850
867
|
credits?.stale ? h('p', { role: 'status', style: { margin: '4px 0 0', fontSize: '10px', color: '#d99b28' } }, '余额缓存 · ' + (credits.staleReason ?? '余额刷新失败')) : null,
|
|
851
868
|
credits?.error?.message ? h('p', { role: credits.status === 'error' ? 'alert' : 'status', style: { margin: '4px 0 0', fontSize: '10px', overflowWrap: 'anywhere' } }, credits.error.message) : null)
|
|
852
869
|
}
|
|
853
|
-
function SettingsQuotaPanel({ readQuota, source, accountRevision }) {
|
|
870
|
+
function SettingsQuotaPanel({ readQuota, source, accountRevision, cliQuotaOn, cliQuotaDirty }) {
|
|
854
871
|
const [data, setData] = React.useState(null)
|
|
855
872
|
const [refreshing, setRefreshing] = React.useState(false)
|
|
856
873
|
const version = React.useRef(0)
|
|
@@ -891,9 +908,9 @@ window.__ModuleLoader__.load({
|
|
|
891
908
|
}
|
|
892
909
|
}
|
|
893
910
|
return h('section', { style: PANEL, 'aria-label': '套餐额度查询' },
|
|
894
|
-
h(SectionHeading, { title: '套餐额度', description: (source === PROXY_PROVIDER ? '代理账户' : 'ACP
|
|
911
|
+
h(SectionHeading, { title: '套餐额度', description: (source === PROXY_PROVIDER ? '代理账户' : 'ACP 账户') + (cliQuotaDirty ? ' · CLI 开关未保存' : cliQuotaOn ? ' · CLI 补充取数' : ''),
|
|
895
912
|
action: h('button', { type: 'button', style: ACTION, disabled: !readQuota || refreshing, 'aria-busy': refreshing || undefined, onClick: load }, refreshing ? '正在拉取…' : '拉取额度') }),
|
|
896
|
-
data ? QuotaDetails({ data, refreshing, source }) : h('p', { role: 'status', style: { margin: 0, fontSize: '12px', lineHeight: 1.
|
|
913
|
+
data ? QuotaDetails({ data, refreshing, source }) : h('p', { role: 'status', style: { margin: 0, fontSize: '12px', lineHeight: 1.5, opacity: .65 } }, refreshing ? '正在读取…' : '尚未查询'))
|
|
897
914
|
}
|
|
898
915
|
function QuotaDetails({ data, refreshing, load, model, source, onClose }) {
|
|
899
916
|
return h('div', { style: { display: 'grid', gap: 0 } },
|
|
@@ -917,7 +934,7 @@ window.__ModuleLoader__.load({
|
|
|
917
934
|
h('div', { title: bucket.disabled ? bucket.description : bucket.resetTime, style: { display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', fontSize: '10px', lineHeight: 1.35, opacity: .58, marginTop: '4px' } }, bucket.disabled ? bucket.description ?? '此窗口不可用' : resetCountdown(bucket.resetTime)))
|
|
918
935
|
})) : h('span', { style: { opacity: .55, fontSize: '11px' } }, data ? '未提供额度数据' : '正在读取…'))),
|
|
919
936
|
CreditsDetails({ credits: data?.credits, pending: !data }),
|
|
920
|
-
source === PROXY_PROVIDER ? h('small', { style: { opacity: .55, fontSize: '10px', lineHeight: 1.4, padding: '7px 0', borderBottom: '1px solid rgba(128,128,128,.14)' } }, '
|
|
937
|
+
source === PROXY_PROVIDER ? h('small', { style: { opacity: .55, fontSize: '10px', lineHeight: 1.4, padding: '7px 0', borderBottom: '1px solid rgba(128,128,128,.14)' } }, '套餐额度属于代理账户。AI Credits 默认不查询;打开「用 agy CLI 查询额度」后才读取,且来自官方 CLI 账户,可能不是同一账户。') : null,
|
|
921
938
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', paddingTop: '8px' } },
|
|
922
939
|
h('small', { style: { flex: 1, opacity: .5, minWidth: 0, fontSize: '10px' } }, data?.updatedAt ? '更新于 ' + new Date(data.updatedAt).toLocaleString() : '尚无成功快照'),
|
|
923
940
|
load ? h('button', { type: 'button', disabled: refreshing, onClick: () => load(true), style: { ...ACTION, minHeight: '27px', padding: '3px 9px', fontSize: '11px' } }, refreshing ? '刷新中…' : '刷新额度') : null))
|
|
@@ -941,7 +958,7 @@ window.__ModuleLoader__.load({
|
|
|
941
958
|
const isProxy = state?.current?.provider === PROXY_PROVIDER
|
|
942
959
|
const enabled = isAcp || isProxy
|
|
943
960
|
const model = state?.current?.model
|
|
944
|
-
const quota = useQuota(readQuota, enabled, state?.current?.provider)
|
|
961
|
+
const quota = useQuota(readQuota, enabled, state?.current?.provider, model)
|
|
945
962
|
const [expanded, setExpanded] = React.useState(false)
|
|
946
963
|
const [position, setPosition] = React.useState(null)
|
|
947
964
|
const root = React.useRef(null)
|
|
@@ -994,7 +1011,7 @@ window.__ModuleLoader__.load({
|
|
|
994
1011
|
}, [expanded])
|
|
995
1012
|
if (!enabled) return null
|
|
996
1013
|
const groups = quotaGroups(quota.data)
|
|
997
|
-
const family = modelGroup(model)
|
|
1014
|
+
const family = modelGroup(quota.data?.routeModel ?? model)
|
|
998
1015
|
// 官方 /usage 可能只报告 Gemini 共享额度;只有真实 Argon 分组存在时,
|
|
999
1016
|
// 才把 Argon 型号绑定到独立组,不能命中一个预建的空组而误报未知。
|
|
1000
1017
|
const group = groups.find((g) => g.key === family && (family !== 'argon' || g.buckets.length > 0))
|
|
@@ -1031,9 +1048,10 @@ window.__ModuleLoader__.load({
|
|
|
1031
1048
|
function makeQuotaReader(scope) {
|
|
1032
1049
|
const listeners = new Set()
|
|
1033
1050
|
const epochs = new Map()
|
|
1051
|
+
const modelsBySource = new Map()
|
|
1034
1052
|
let proxyAccountKey
|
|
1035
1053
|
let mode
|
|
1036
|
-
const read = async (force) => {
|
|
1054
|
+
const read = async (force, source, model) => {
|
|
1037
1055
|
const deadline = Date.now() + 20_000
|
|
1038
1056
|
for (;;) {
|
|
1039
1057
|
// **只能用 `get()`**:`scope.remote.agyQuota` 这种属性访问在未 inject 的
|
|
@@ -1045,7 +1063,11 @@ window.__ModuleLoader__.load({
|
|
|
1045
1063
|
// Host 侧会起官方 agy CLI 取数(其启动含登录刷新)。万一它挂在网络上,
|
|
1046
1064
|
// 也要让药丸显示「读取失败 + 原因」,而不是一直停在「额度…」。
|
|
1047
1065
|
return await withTimeout(
|
|
1048
|
-
service.read(
|
|
1066
|
+
service.read({
|
|
1067
|
+
...(force ? { force: true } : {}),
|
|
1068
|
+
...(source === PROVIDER ? { source: 'acp' } : source === PROXY_PROVIDER ? { source: 'proxy' } : {}),
|
|
1069
|
+
...(model ? { model } : {}),
|
|
1070
|
+
}),
|
|
1049
1071
|
60_000,
|
|
1050
1072
|
'额度读取超时(Host 侧 agy CLI 60 秒内没有返回)',
|
|
1051
1073
|
)
|
|
@@ -1056,9 +1078,11 @@ window.__ModuleLoader__.load({
|
|
|
1056
1078
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
1057
1079
|
}
|
|
1058
1080
|
}
|
|
1059
|
-
const reader = async (force, source) => {
|
|
1081
|
+
const reader = async (force, source, model) => {
|
|
1082
|
+
if (model) modelsBySource.set(source, model)
|
|
1083
|
+
const requestedModel = model ?? modelsBySource.get(source)
|
|
1060
1084
|
const epoch = epochs.get(source) ?? 0
|
|
1061
|
-
const value = unwrap(await read(force))
|
|
1085
|
+
const value = unwrap(await read(force, source, requestedModel))
|
|
1062
1086
|
if (epoch !== (epochs.get(source) ?? 0)) throw new Error('账户已变化,请重新拉取额度')
|
|
1063
1087
|
for (const listener of listeners) {
|
|
1064
1088
|
if (source === undefined) listener(value)
|
|
@@ -1158,6 +1182,8 @@ window.__ModuleLoader__.load({
|
|
|
1158
1182
|
if (value === undefined || value === null) return undefined
|
|
1159
1183
|
if (typeof value !== 'object' || Array.isArray(value)) throw new TypeError('Invalid AgYQuotaRequest')
|
|
1160
1184
|
if (value.force !== undefined && typeof value.force !== 'boolean') throw new TypeError('Invalid AgYQuotaRequest.force')
|
|
1185
|
+
if (value.source !== undefined && !['acp', 'proxy'].includes(value.source)) throw new TypeError('Invalid AgYQuotaRequest.source')
|
|
1186
|
+
if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) throw new TypeError('Invalid AgYQuotaRequest.model')
|
|
1161
1187
|
return value
|
|
1162
1188
|
}
|
|
1163
1189
|
|