@ai-wendao/ant 0.4.4 → 0.4.5
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/client.js +297 -151
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -10,6 +10,17 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
|
|
11
11
|
const ANT_API_PROTOCOL_VERSIONS = [1]
|
|
12
12
|
const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
|
|
13
|
+
const ANT_SETTINGS_TAB_STORAGE_KEY = 'ant-settings-tab'
|
|
14
|
+
const ANT_SETTINGS_TABS = [
|
|
15
|
+
['connection', '团队连接'], ['models', '团队模型'], ['bot-chat', '机器人与聊天'],
|
|
16
|
+
['capability', '能力授权'], ['local', '本机与任务'], ['updates', 'ANT 更新'],
|
|
17
|
+
]
|
|
18
|
+
const loadAntSettingsTab = () => {
|
|
19
|
+
try {
|
|
20
|
+
const saved = localStorage.getItem(ANT_SETTINGS_TAB_STORAGE_KEY)
|
|
21
|
+
return ANT_SETTINGS_TABS.some(([id]) => id === saved) ? saved : 'connection'
|
|
22
|
+
} catch { return 'connection' }
|
|
23
|
+
}
|
|
13
24
|
const fileCountFormat = new Intl.NumberFormat('zh-CN')
|
|
14
25
|
const marketSkillCategories = {
|
|
15
26
|
'ant-ppt-master': '演示', 'ant-frontend-slides': '演示', 'ant-slidev': '演示',
|
|
@@ -234,6 +245,7 @@ window.__ModuleLoader__.load({
|
|
|
234
245
|
}
|
|
235
246
|
|
|
236
247
|
function TeamServiceConfig({ remote, openInstalledPlugin }) {
|
|
248
|
+
const [activeTab, setActiveTab] = useState(loadAntSettingsTab)
|
|
237
249
|
const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
|
|
238
250
|
const [draft, setDraft] = useState(serviceUrl)
|
|
239
251
|
const [binding, setBinding] = useState({ state: 'checking', url: null })
|
|
@@ -241,6 +253,24 @@ window.__ModuleLoader__.load({
|
|
|
241
253
|
const [message, setMessage] = useState('')
|
|
242
254
|
const [pluginAlerts, setPluginAlerts] = useState([])
|
|
243
255
|
const [delegationRevision, setDelegationRevision] = useState(0)
|
|
256
|
+
const [updateSummary, setUpdateSummary] = useState(null)
|
|
257
|
+
const selectSettingsTab = (tab) => {
|
|
258
|
+
setActiveTab(tab)
|
|
259
|
+
try { localStorage.setItem(ANT_SETTINGS_TAB_STORAGE_KEY, tab) } catch {}
|
|
260
|
+
}
|
|
261
|
+
const moveSettingsTabFocus = (event, index) => {
|
|
262
|
+
const key = event.key
|
|
263
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(key)) return
|
|
264
|
+
event.preventDefault()
|
|
265
|
+
const step = key === 'ArrowLeft' ? -1 : key === 'ArrowRight' ? 1 : 0
|
|
266
|
+
const next = key === 'Home' ? 0 : key === 'End' ? ANT_SETTINGS_TABS.length - 1
|
|
267
|
+
: (index + step + ANT_SETTINGS_TABS.length) % ANT_SETTINGS_TABS.length
|
|
268
|
+
const [tab] = ANT_SETTINGS_TABS[next]
|
|
269
|
+
selectSettingsTab(tab)
|
|
270
|
+
event.currentTarget.parentElement?.querySelectorAll('[role="tab"]')?.[next]?.focus()
|
|
271
|
+
}
|
|
272
|
+
const updateNeedsAttention = ['available', 'artifact-missing', 'revoked', 'migration-required',
|
|
273
|
+
'unknown', 'failed', 'installing', 'done'].includes(updateSummary?.state)
|
|
244
274
|
useEffect(() => {
|
|
245
275
|
let disposed = false
|
|
246
276
|
const revision = bindingRevision.current
|
|
@@ -312,6 +342,11 @@ window.__ModuleLoader__.load({
|
|
|
312
342
|
}
|
|
313
343
|
}
|
|
314
344
|
const link = binding.state === 'ready' ? consoleUrl(binding.url || serviceUrl) : null
|
|
345
|
+
const bindingSummary = binding.state === 'checking' ? '正在确认这台 DSH 的团队连接…'
|
|
346
|
+
: binding.state === 'error' ? '暂时无法确认团队连接状态'
|
|
347
|
+
: binding.state === 'unverified' ? '本机有 ANT 凭证,但设备归属待核对'
|
|
348
|
+
: binding.url ? `团队服务已连接 · ${new URL(binding.url).host}` : '尚未连接团队服务'
|
|
349
|
+
const bindingReadyForSummary = binding.state === 'ready' && Boolean(binding.url)
|
|
315
350
|
const onConnectionChange = (url) => {
|
|
316
351
|
bindingRevision.current += 1
|
|
317
352
|
setBinding({ state: 'ready', url: url || null })
|
|
@@ -322,45 +357,79 @@ window.__ModuleLoader__.load({
|
|
|
322
357
|
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, url) } catch {}
|
|
323
358
|
}
|
|
324
359
|
}
|
|
325
|
-
|
|
326
|
-
h('
|
|
327
|
-
|
|
328
|
-
h('
|
|
329
|
-
|
|
330
|
-
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
|
|
331
|
-
h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: binding.url ? '1fr' : 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
|
|
360
|
+
const teamServicePanel = h('section', { style: { ...panelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
361
|
+
h('div', null,
|
|
362
|
+
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '团队服务'),
|
|
363
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
|
|
364
|
+
h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: binding.url ? '1fr' : 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
|
|
332
365
|
h('label', { htmlFor: 'ant-team-service-url', style: { gridColumn: '1 / -1', fontSize: 13, fontWeight: 550 } }, binding.url ? '当前连接的团队服务地址' : '服务地址'),
|
|
333
|
-
h('input', {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
required: true,
|
|
337
|
-
value: binding.url || draft,
|
|
338
|
-
readOnly: binding.state !== 'ready' || Boolean(binding.url),
|
|
339
|
-
placeholder: 'https://ant.example.com',
|
|
340
|
-
onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) },
|
|
341
|
-
style: inputStyle,
|
|
342
|
-
}),
|
|
366
|
+
h('input', { id: 'ant-team-service-url', type: 'url', required: true, value: binding.url || draft,
|
|
367
|
+
readOnly: binding.state !== 'ready' || Boolean(binding.url), placeholder: 'https://ant.example.com',
|
|
368
|
+
onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) }, style: inputStyle }),
|
|
343
369
|
binding.url ? null : h('button', { type: 'submit', disabled: binding.state !== 'ready', style: primaryButtonStyle }, '保存地址')),
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
370
|
+
binding.url ? h('p', { style: mutedTextStyle }, '若要切换团队,请先在下方断开当前设备,再填写新地址并验证邮箱。') : null,
|
|
371
|
+
binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
|
|
372
|
+
binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
|
|
373
|
+
binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
|
|
374
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
|
|
375
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
376
|
+
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null))
|
|
377
|
+
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
378
|
+
h('p', { style: mutedTextStyle }, '连接团队服务后,即可同步获准模型并使用团队协作。'),
|
|
379
|
+
h('div', { role: 'status', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
380
|
+
flexWrap: 'wrap', gap: 10, padding: '10px 12px', borderRadius: 8,
|
|
381
|
+
backgroundColor: bindingReadyForSummary ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)' } },
|
|
382
|
+
h('span', null, bindingSummary),
|
|
383
|
+
!bindingReadyForSummary ? h('button', { type: 'button', onClick: () => selectSettingsTab('connection'),
|
|
384
|
+
style: secondaryButtonStyle }, binding.state === 'error' ? '检查连接' : '查看连接设置') : null),
|
|
385
|
+
h('div', { style: { position: 'sticky', top: 0, zIndex: 8, display: 'grid', gap: 8, padding: '6px 0',
|
|
386
|
+
backgroundColor: 'var(--dsw-alias-bg-module-platform)' } },
|
|
387
|
+
pluginAlerts.length ? h('section', { role: 'alert', style: { ...panelStyle,
|
|
388
|
+
borderColor: 'var(--dsw-alias-label-danger, #d44)', maxHeight: '28vh', overflowY: 'auto' } },
|
|
352
389
|
h('h3', { style: { margin: 0, fontSize: 16 } }, '已安装插件存在安全撤回公告'),
|
|
353
390
|
...pluginAlerts.map((plugin) => h('p', { key: plugin.id, style: mutedTextStyle },
|
|
354
391
|
`${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
|
|
355
392
|
...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
|
|
356
393
|
onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
|
|
357
|
-
h(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
394
|
+
updateNeedsAttention ? h('section', { role: 'status', 'aria-live': 'polite', style: {
|
|
395
|
+
...panelStyle, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap',
|
|
396
|
+
gap: 12, padding: 12, borderColor: updateSummary.state === 'revoked'
|
|
397
|
+
? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-border-l2))',
|
|
398
|
+
backgroundColor: updateSummary.state === 'revoked'
|
|
399
|
+
? 'var(--dsw-alias-state-error-secondary, var(--dsw-alias-bg-module-platform))'
|
|
400
|
+
: 'var(--dsw-alias-state-warning-secondary, var(--dsw-alias-bg-module-platform))',
|
|
401
|
+
} },
|
|
402
|
+
h('div', { style: { display: 'grid', gap: 3, flex: '1 1 300px' } },
|
|
403
|
+
h('strong', null, updateSummary.state === 'available'
|
|
404
|
+
? `有新版 ANT:${updateSummary.currentVersion || '当前版本未知'} → ${updateSummary.version}`
|
|
405
|
+
: updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
|
|
406
|
+
: updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
|
|
407
|
+
h('span', { style: mutedTextStyle }, updateSummary.message || '打开 ANT 更新查看处理方式。')),
|
|
408
|
+
h('button', { type: 'button', onClick: () => selectSettingsTab('updates'), style: primaryButtonStyle },
|
|
409
|
+
updateSummary.state === 'available' ? '查看并更新' : '查看处理方式')) : null,
|
|
410
|
+
h('nav', { role: 'tablist', 'aria-label': 'ANT 插件设置', style: {
|
|
411
|
+
display: 'flex', gap: 4, overflowX: 'auto', borderBottom: '1px solid var(--dsw-alias-border-l2)',
|
|
412
|
+
} },
|
|
413
|
+
...ANT_SETTINGS_TABS.map(([tab, label], index) => h('button', { key: tab, id: `ant-settings-tab-${tab}`,
|
|
414
|
+
type: 'button', role: 'tab', tabIndex: activeTab === tab ? 0 : -1,
|
|
415
|
+
'aria-controls': 'ant-settings-content', 'aria-selected': String(activeTab === tab),
|
|
416
|
+
onKeyDown: (event) => moveSettingsTabFocus(event, index), onClick: () => selectSettingsTab(tab),
|
|
417
|
+
style: { ...secondaryButtonStyle, minHeight: 38, padding: '7px 12px', whiteSpace: 'nowrap',
|
|
418
|
+
borderRadius: '8px 8px 0 0', color: activeTab === tab ? 'var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.color,
|
|
419
|
+
borderBottom: activeTab === tab ? '2px solid var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.border,
|
|
420
|
+
backgroundColor: activeTab === tab ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor,
|
|
421
|
+
} }, tab === 'updates' && updateNeedsAttention ? `${label} · ${updateSummary.state === 'available' ? '有新版' : '需处理'}` : label)))),
|
|
422
|
+
h('div', { id: 'ant-settings-content', role: 'tabpanel', 'aria-labelledby': `ant-settings-tab-${activeTab}` },
|
|
423
|
+
teamServicePanel,
|
|
424
|
+
h(ModelSyncFooter, { remote, serviceUrl, bindingReady: binding.state === 'ready', activeTab,
|
|
425
|
+
onServiceUrlChange: setServiceUrl, onConnectionChange,
|
|
426
|
+
onUpdateStateChange: setUpdateSummary, onSelectTab: selectSettingsTab }),
|
|
427
|
+
h(DelegationCards, { remote, revision: delegationRevision,
|
|
428
|
+
activeTab, onSelectTab: selectSettingsTab,
|
|
429
|
+
openImSettings: () => openInstalledPlugin('@xmanrui/dsh-im')})))
|
|
361
430
|
}
|
|
362
431
|
|
|
363
|
-
function DelegationCards({ remote, revision, openImSettings }) {
|
|
432
|
+
function DelegationCards({ remote, revision, activeTab, onSelectTab, openImSettings }) {
|
|
364
433
|
const [bots, setBots] = useState([])
|
|
365
434
|
const [enrolled, setEnrolled] = useState([])
|
|
366
435
|
const [groups, setGroups] = useState([])
|
|
@@ -430,6 +499,7 @@ window.__ModuleLoader__.load({
|
|
|
430
499
|
} finally { setLoading(false) }
|
|
431
500
|
}
|
|
432
501
|
useEffect(() => { void load().catch((error) => setMessage(error.message)) }, [remote, revision])
|
|
502
|
+
useEffect(() => { setMessage('') }, [activeTab])
|
|
433
503
|
const selectedBot = bots.find((bot) => bot.bot_id === botId)
|
|
434
504
|
const botConnected = Boolean(selectedBot?.available && selectedBot?.connected)
|
|
435
505
|
const selectedEnrollment = enrolled.find((bot) => bot.bot_fingerprint === selectedBot?.fingerprint)
|
|
@@ -459,12 +529,12 @@ window.__ModuleLoader__.load({
|
|
|
459
529
|
: conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
|
|
460
530
|
: conversationKind === 'group' && !groupId ? '请在“参数与授权范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
|
|
461
531
|
: senderScope === 'allowlist' && !senderIds.trim() ? '请在“参数与授权范围”中填写可请求成员的 IM ID。'
|
|
462
|
-
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '
|
|
532
|
+
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
|
|
463
533
|
: parameters.some((rule) => rule.kind === 'gitea_pr') && !selectedEnrollment?.gitea_origin
|
|
464
|
-
? '
|
|
534
|
+
? '请先在“机器人与聊天”中登记这个机器人的可信 Gitea 地址。'
|
|
465
535
|
: parameters.some((rule) => rule.kind === 'gitea_pr')
|
|
466
536
|
&& normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
|
|
467
|
-
? '请先保存机器人级可信 Gitea 地址;PR
|
|
537
|
+
? '请先保存机器人级可信 Gitea 地址;PR 参数会自动使用这个地址。'
|
|
468
538
|
: selectedRunnerNeedsPr && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
|
|
469
539
|
? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
|
|
470
540
|
: selectedRunnerNeedsPr && !parameters[0].required
|
|
@@ -488,7 +558,6 @@ window.__ModuleLoader__.load({
|
|
|
488
558
|
gitea_origin: selectedEnrollment?.gitea_origin || trustedOrigin || '', repository: '' }])
|
|
489
559
|
setRunner(codexAvailable ? 'codex' : claudeCodeAvailable ? 'claude-code' : 'dsh')
|
|
490
560
|
setCreateOpen(true)
|
|
491
|
-
window.document?.getElementById('ant-new-task')?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
492
561
|
}
|
|
493
562
|
const saveGiteaToken = async () => {
|
|
494
563
|
if (!giteaToken.trim()) return
|
|
@@ -724,7 +793,11 @@ window.__ModuleLoader__.load({
|
|
|
724
793
|
} catch (error) { setMessage(error.message) }
|
|
725
794
|
finally { setBusy(false) }
|
|
726
795
|
}
|
|
727
|
-
return h('section', { style: panelStyle,
|
|
796
|
+
return h('section', { style: { ...panelStyle,
|
|
797
|
+
display: ['bot-chat', 'capability', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
798
|
+
'aria-label': '协作机器人与开放能力' },
|
|
799
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
800
|
+
h('div', { role: 'region', 'aria-label': '机器人连接和聊天范围', style: { display: activeTab === 'bot-chat' ? 'grid' : 'none', gap: 14 } },
|
|
728
801
|
h('h3', { style: { margin: 0, fontSize: 16 } }, '协作机器人'),
|
|
729
802
|
h('p', { style: mutedTextStyle }, '同事可以像平常一样和机器人聊天。DSH 负责理解与回答;只有准备执行你开放的能力时,ANT 才核对请求人、群、参数和权限。群里的任务结果会回复原群。'),
|
|
730
803
|
h('p', { role: 'status', style: mutedTextStyle }, '每项能力都单独设定执行方和请求范围。普通对话不会自动获得读取电脑、调用工具或访问其他群的权限。'),
|
|
@@ -733,6 +806,11 @@ window.__ModuleLoader__.load({
|
|
|
733
806
|
h('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12,
|
|
734
807
|
display: 'grid', gap: 8 } },
|
|
735
808
|
h('strong', null, '机器人和聊天范围'),
|
|
809
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 } },
|
|
810
|
+
h('div', { style: { padding: 10, borderRadius: 8, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
811
|
+
h('strong', null, '私聊'), h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '默认可用,可正常聊天;执行任务仍需单独授权。')),
|
|
812
|
+
h('div', { style: { padding: 10, borderRadius: 8, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
813
|
+
h('strong', null, '群聊'), h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '可同时启用;群成员是否能执行任务由每项能力授权决定。'))),
|
|
736
814
|
h('span', { role: 'status', style: mutedTextStyle }, loading ? '正在读取机器人与任务状态…'
|
|
737
815
|
: botLoadError ? '暂时无法读取机器人状态。请刷新后再试。'
|
|
738
816
|
: connectedBots.length === 0 ? '尚未发现已连接的机器人。请先在 DSH-IM 中连接飞书机器人。'
|
|
@@ -765,6 +843,17 @@ window.__ModuleLoader__.load({
|
|
|
765
843
|
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null,
|
|
766
844
|
selectedEnrollmentBound && botConnected ? h('p', { style: mutedTextStyle },
|
|
767
845
|
`自测:私聊“${selectedBot.name}”发送“你能做什么”;发起任务后可问“刚才的任务怎么样了”。群聊请先在目标群 @机器人,再开放能力时选择该群。`) : null,
|
|
846
|
+
selectedBot ? h('div', { style: { display: 'grid', gap: 8, padding: 10,
|
|
847
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
|
|
848
|
+
h('strong', null, `可信 Gitea 地址 · ${selectedBot.name}`),
|
|
849
|
+
h('p', { style: mutedTextStyle }, '为这个机器人登记一次。PR 能力只需配置仓库和 PR 编号,不必重复填写地址。'),
|
|
850
|
+
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
851
|
+
h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
|
|
852
|
+
onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
|
|
853
|
+
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
854
|
+
h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
|
|
855
|
+
onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
|
|
856
|
+
selectedEnrollmentBound ? '保存可信地址' : botConnected ? '绑定机器人并保存地址' : '先连接机器人'))) : null,
|
|
768
857
|
selectedEnrollmentBound && botConnected ? h('div', { style: { display: 'grid', gap: 8 } },
|
|
769
858
|
h('strong', null, '允许聊天的群'),
|
|
770
859
|
h('span', { style: mutedTextStyle }, '群成员可以正常聊天;是否能让机器人办事,由每项能力单独决定。若机器人尚未发现群,可以直接填群 ID。'),
|
|
@@ -784,31 +873,42 @@ window.__ModuleLoader__.load({
|
|
|
784
873
|
selectedChatGroup?.chat_enabled && selectedGroupHasTasks ? h('span', { style: mutedTextStyle },
|
|
785
874
|
'本群还有开放能力;关闭聊天前请先停止这些能力。') : null) : null,
|
|
786
875
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
787
|
-
h('button', { type: 'button', onClick: () => { setCreateOpen(true)
|
|
788
|
-
window.document?.getElementById('ant-new-task')?.scrollIntoView({ behavior: 'smooth', block: 'start' }) },
|
|
876
|
+
h('button', { type: 'button', onClick: () => { setCreateOpen(true); onSelectTab('capability') },
|
|
789
877
|
style: primaryButtonStyle }, '开放新能力'),
|
|
790
|
-
h('button', { type: 'button', onClick: preparePrExample, style: secondaryButtonStyle }, '使用 PR 差异审查示例'),
|
|
878
|
+
h('button', { type: 'button', onClick: () => { preparePrExample(); onSelectTab('capability') }, style: secondaryButtonStyle }, '使用 PR 差异审查示例'),
|
|
791
879
|
h('button', { type: 'button', onClick: () => void load().catch((error) => setMessage(error.message)),
|
|
792
|
-
style: secondaryButtonStyle }, '刷新状态'))),
|
|
880
|
+
style: secondaryButtonStyle }, '刷新状态')))),
|
|
881
|
+
h('div', { role: 'region', 'aria-label': '已开放能力列表', style: { display: activeTab === 'capability' ? 'grid' : 'none', gap: 12 } },
|
|
793
882
|
h('h4', { style: { marginBottom: 0 } }, `我开放的能力 · ${authorizationLoadError ? '暂未读取' : activeCards.length}`),
|
|
794
883
|
...activeCards.map((card) => h('div', { key: card.id, style: { display: 'flex', gap: 12,
|
|
795
884
|
alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
|
|
796
|
-
h('span', { style: { flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } }, `${card.title} · ${bots.find((bot) => bot.fingerprint === card.bot_fingerprint)?.name || '机器人'} · ${card.conversation_kind === 'group' ? `群 ${groups.find((group) => group.group_id === card.conversation_id)?.group_code || card.conversation_id}` : '私聊'} · ${card.sender_scope === 'allowlist' ? '仅指定成员可请求' : card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'} · ${card.runner === 'codex' ? 'Codex
|
|
885
|
+
h('span', { style: { flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } }, `${card.title} · ${bots.find((bot) => bot.fingerprint === card.bot_fingerprint)?.name || '机器人'} · ${card.conversation_kind === 'group' ? `群 ${groups.find((group) => group.group_id === card.conversation_id)?.group_code || card.conversation_id}` : '私聊'} · ${card.sender_scope === 'allowlist' ? '仅指定成员可请求' : card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'} · ${card.runner === 'codex' ? 'Codex PR 快照审查试用' : card.runner === 'claude-code' ? 'Claude Code PR 完整快照审查试用' : 'DSH 文字处理'} · ${new Date(card.expires_at * 1000).toLocaleString('zh-CN')} 到期`),
|
|
797
886
|
h('button', { type: 'button', disabled: busy, onClick: () => void revokeCard(card), style: secondaryButtonStyle }, '停止'))),
|
|
798
887
|
activeCards.length === 0 ? h('p', { style: mutedTextStyle },
|
|
799
888
|
authorizationLoadError ? '暂时无法确认已开放的能力,请刷新状态后再试。'
|
|
800
|
-
: '还没有开放能力。先选机器人,再开放一项能力;PR 审查只是可选示例。') : null,
|
|
889
|
+
: '还没有开放能力。先选机器人,再开放一项能力;PR 审查只是可选示例。') : null),
|
|
890
|
+
h('div', { role: 'region', 'aria-label': '最近任务', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
801
891
|
h('h4', { style: { marginBottom: 0 } }, '最近任务'),
|
|
802
892
|
...(tasks.filter((task) => task.task_id).length ? tasks.filter((task) => task.task_id)
|
|
803
|
-
.map((task) =>
|
|
804
|
-
|
|
805
|
-
|
|
893
|
+
.map((task) => {
|
|
894
|
+
const title = task.title || (task.pr_number
|
|
895
|
+
? `${task.repository ? `${task.repository} ` : ''}PR #${task.pr_number} 差异审查` : '任务')
|
|
896
|
+
const status = task.outcome === 'canceled' ? '已取消' : task.outcome === 'failed' ? '未完成' : ({
|
|
806
897
|
pending: '排队中', running: '处理中', reply_pending: '结果待发送', reply_attempted: '回复状态待核对', done: '已回复',
|
|
807
|
-
}[task.status] || task.status)
|
|
808
|
-
|
|
809
|
-
|
|
898
|
+
}[task.status] || task.status)
|
|
899
|
+
const createdAt = task.created_at && Number.isFinite(Date.parse(task.created_at))
|
|
900
|
+
? new Date(task.created_at).toLocaleString('zh-CN') : ''
|
|
901
|
+
return h('div', { key: task.task_id, style: { display: 'flex', gap: 12, alignItems: 'center',
|
|
902
|
+
justifyContent: 'space-between', flexWrap: 'wrap', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
|
|
903
|
+
h('div', { style: { flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
904
|
+
h('strong', null, title),
|
|
905
|
+
h('div', { style: mutedTextStyle }, `${status}${createdAt ? ` · ${createdAt}` : ''} · 任务 ${task.task_id}`)),
|
|
906
|
+
['card_task', 'legacy_pr_task'].includes(task.kind) && ['pending', 'running'].includes(task.status) ? h('button', { type: 'button', disabled: busy,
|
|
907
|
+
onClick: () => void cancelTask(task), style: secondaryButtonStyle }, '取消任务') : null)
|
|
908
|
+
})
|
|
810
909
|
: [h('p', { key: 'none', style: mutedTextStyle }, loadFailures.includes('status')
|
|
811
|
-
? '任务记录暂时无法读取,请刷新状态。' : '暂无任务。')]),
|
|
910
|
+
? '任务记录暂时无法读取,请刷新状态。' : '暂无任务。')])),
|
|
911
|
+
h('div', { role: 'region', 'aria-label': '新建能力', style: { display: activeTab === 'capability' ? 'grid' : 'none' } },
|
|
812
912
|
h('details', { id: 'ant-new-task', open: createOpen,
|
|
813
913
|
onToggle: (event) => setCreateOpen(event.currentTarget.open),
|
|
814
914
|
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
@@ -835,13 +935,13 @@ window.__ModuleLoader__.load({
|
|
|
835
935
|
onChange: (event) => setRunner(event.target.value), style: inputStyle },
|
|
836
936
|
h('option', { value: 'dsh' }, 'DSH · 已提供文字'),
|
|
837
937
|
h('option', { value: 'codex',
|
|
838
|
-
disabled: !codexAvailable }, 'Codex · 单个 Gitea PR
|
|
938
|
+
disabled: !codexAvailable }, 'Codex · 单个 Gitea PR 完整快照审查试用'),
|
|
839
939
|
h('option', { value: 'claude-code', disabled: !claudeCodeAvailable },
|
|
840
940
|
'Claude Code · 单个 Gitea PR 完整快照试用'))),
|
|
841
941
|
nativePrUnavailableReason ? h('p', { role: 'status', style: mutedTextStyle },
|
|
842
942
|
`${nativePrUnavailableReason},已禁用 Codex/Claude Code 完整快照选项;可选择 DSH 差异审查。`) : null,
|
|
843
943
|
runner === 'codex' ? h('p', { role: 'status', style: mutedTextStyle },
|
|
844
|
-
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head
|
|
944
|
+
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head 的临时代码快照。不会收到 Gitea 令牌;请以这台 DSH 的实际子代理权限为准,只有强制只读隔离已验证时才视为只读。Git LFS 实际文件不会下载。') : null,
|
|
845
945
|
runner === 'claude-code' ? h('p', { role: 'status', style: mutedTextStyle },
|
|
846
946
|
'Claude Code 通过 DSH 已注册的子代理运行,可查看本次 PR 的临时代码快照。ANT 不会传 Gitea 令牌;Claude Code 沿用 DSH 提供方的本机权限,不具备 Codex 路径相同的固定只读隔离。Git LFS 实际文件不会下载。') : null,
|
|
847
947
|
selectedRunnerNeedsPr ? h('p', { role: 'status', style: mutedTextStyle },
|
|
@@ -880,21 +980,12 @@ window.__ModuleLoader__.load({
|
|
|
880
980
|
h('button', { type: 'button', disabled: parameters.length >= 12, style: secondaryButtonStyle,
|
|
881
981
|
onClick: () => setParameters((current) => [...current, { name: '', kind: 'text', required: true,
|
|
882
982
|
description: '', min: '', max: '', choices: '', gitea_origin: '', repository: '' }]) }, '添加参数'),
|
|
883
|
-
parameters.some((rule) => rule.kind === 'gitea_pr') ? h('div', { style: { display: '
|
|
884
|
-
h('
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
h('button', { type: 'button',
|
|
888
|
-
|
|
889
|
-
selectedEnrollmentBound ? '保存可信地址' : '绑定到这台 DSH 并保存地址')) : null,
|
|
890
|
-
parameters.some((rule) => rule.kind === 'gitea_pr') ? h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
891
|
-
h('input', { 'aria-label': '只读 Gitea 访问令牌', type: 'password', autoComplete: 'off', value: giteaToken,
|
|
892
|
-
onChange: (event) => setGiteaToken(event.target.value), placeholder: tokenConfigured ? '本机已保存 Gitea 令牌' : '先保存只读 Gitea 令牌',
|
|
893
|
-
style: { ...inputStyle, flex: '1 1 260px' } }),
|
|
894
|
-
h('button', { type: 'button', disabled: busy || !giteaToken.trim(),
|
|
895
|
-
onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, '保存令牌')) : null,
|
|
896
|
-
parameters.some((rule) => rule.kind === 'gitea_pr') ? h('p', { style: mutedTextStyle },
|
|
897
|
-
'令牌只保存在这台 DSH,用于读取 PR,不会提交到 ANT 服务端或交给执行方。') : null,
|
|
983
|
+
parameters.some((rule) => rule.kind === 'gitea_pr') ? h('div', { style: { display: 'grid', gap: 6 } },
|
|
984
|
+
h('p', { style: mutedTextStyle }, selectedEnrollment?.gitea_origin
|
|
985
|
+
? `将使用机器人已登记的可信 Gitea 地址:${selectedEnrollment.gitea_origin}`
|
|
986
|
+
: '请先在“机器人与聊天”中为这个机器人登记可信 Gitea 地址。'),
|
|
987
|
+
!tokenConfigured ? h('button', { type: 'button', onClick: () => onSelectTab('local'), style: secondaryButtonStyle },
|
|
988
|
+
'前往本机与任务保存 Gitea 令牌') : h('p', { style: mutedTextStyle }, '本机 Gitea 只读令牌已保存。')) : null,
|
|
898
989
|
h('label', null, '使用场景', h('select', { value: conversationKind,
|
|
899
990
|
onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
|
|
900
991
|
h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
|
|
@@ -921,7 +1012,20 @@ window.__ModuleLoader__.load({
|
|
|
921
1012
|
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天')))),
|
|
922
1013
|
cardBlockReason ? h('p', { role: 'status', style: mutedTextStyle }, cardBlockReason) : null,
|
|
923
1014
|
h('button', { disabled: busy || Boolean(cardBlockReason),
|
|
924
|
-
style: primaryButtonStyle }, '保存并开放能力'))),
|
|
1015
|
+
style: primaryButtonStyle }, '保存并开放能力')))),
|
|
1016
|
+
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1017
|
+
h('section', { style: { ...panelStyle, padding: 14 } },
|
|
1018
|
+
h('strong', null, 'Gitea 本机凭据'),
|
|
1019
|
+
h('p', { style: mutedTextStyle }, tokenConfigured
|
|
1020
|
+
? '只读访问令牌已保存在这台 DSH 的安全凭据中;不会同步到 ANT 服务端或发送给执行器。'
|
|
1021
|
+
: 'PR 审查能力需要只读访问令牌。令牌只保存到这台 DSH。'),
|
|
1022
|
+
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1023
|
+
h('input', { 'aria-label': 'Gitea 只读访问令牌', type: 'password', autoComplete: 'new-password', value: giteaToken,
|
|
1024
|
+
onChange: (event) => setGiteaToken(event.target.value),
|
|
1025
|
+
placeholder: tokenConfigured ? '已保存;输入新令牌可替换' : '粘贴 Gitea 只读令牌',
|
|
1026
|
+
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
1027
|
+
h('button', { type: 'button', disabled: busy || !giteaToken.trim(),
|
|
1028
|
+
onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))),
|
|
925
1029
|
h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
926
1030
|
h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '本机处理设置'),
|
|
927
1031
|
h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务;PR 仓库缓存上限按这台 DSH 的长期仓库缓存总量计算,不包括任务临时工作目录。'),
|
|
@@ -943,8 +1047,7 @@ window.__ModuleLoader__.load({
|
|
|
943
1047
|
onChange: (event) => setRepositoryCacheLimitGib(event.target.value), style: inputStyle }),
|
|
944
1048
|
h('span', { style: mutedTextStyle }, '默认 10 GiB,范围 1–100 GiB,按长期仓库文件逻辑大小计算(不含临时工作区和文件系统分配开销)。日常不会淘汰仍启用能力的缓存;停用或到期能力会在刷新时回收。单次拉取若实际越界,为恢复预算会清理这项能力的仓库缓存(旧 PR 可重新下载)。')),
|
|
945
1049
|
h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置')),
|
|
946
|
-
h('p', { style: mutedTextStyle },
|
|
947
|
-
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null)
|
|
1050
|
+
h('p', { style: mutedTextStyle }, `本机检测到的执行方:${executors.map((item) => `${item.title}${item.available ? '(可用)' : `(${item.reason || '未启用或未找到'})`}`).join('、') || '暂未读取到'}。只有能力配置下拉框中列出的执行方能接收委托。`))))
|
|
948
1051
|
}
|
|
949
1052
|
|
|
950
1053
|
function SkillCenterPage({ remote, onBack, onAll, onSkills, onPlugins, openBundle,
|
|
@@ -1437,10 +1540,12 @@ window.__ModuleLoader__.load({
|
|
|
1437
1540
|
? h('p', { style: mutedTextStyle }, noMatches) : null))
|
|
1438
1541
|
}
|
|
1439
1542
|
|
|
1440
|
-
function ModelSyncFooter({ remote, serviceUrl, bindingReady, onServiceUrlChange,
|
|
1543
|
+
function ModelSyncFooter({ remote, serviceUrl, bindingReady, activeTab, onServiceUrlChange,
|
|
1544
|
+
onConnectionChange, onUpdateStateChange, onSelectTab }) {
|
|
1441
1545
|
const [email, setEmail] = useState('')
|
|
1442
1546
|
const [code, setCode] = useState('')
|
|
1443
1547
|
const [message, setMessage] = useState('')
|
|
1548
|
+
const [modelMessage, setModelMessage] = useState('')
|
|
1444
1549
|
const [busy, setBusy] = useState(false)
|
|
1445
1550
|
const [codeRequested, setCodeRequested] = useState(false)
|
|
1446
1551
|
const [credentialConfigured, setCredentialConfigured] = useState(null)
|
|
@@ -1449,9 +1554,15 @@ window.__ModuleLoader__.load({
|
|
|
1449
1554
|
const [allowLocalCleanup, setAllowLocalCleanup] = useState(false)
|
|
1450
1555
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
1451
1556
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
1557
|
+
const [authorizedModels, setAuthorizedModels] = useState([])
|
|
1558
|
+
const [lastModelSync, setLastModelSync] = useState(null)
|
|
1452
1559
|
const [serviceCompatibility, setServiceCompatibility] = useState({ state: 'idle', message: '' })
|
|
1453
1560
|
const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
|
|
1454
1561
|
|
|
1562
|
+
useEffect(() => {
|
|
1563
|
+
onUpdateStateChange?.({ ...bundleUpdate, version: bundleRelease?.version || '' })
|
|
1564
|
+
}, [bundleUpdate, bundleRelease, onUpdateStateChange])
|
|
1565
|
+
|
|
1455
1566
|
const connectionErrorMessage = (result, status) => {
|
|
1456
1567
|
const message = result?.message || `请求失败(${status})`
|
|
1457
1568
|
if (!['LEGACY_DEVICE_REVOKE_REQUIRED', 'ANT_CONNECTION_CLEANUP_REQUIRED'].includes(result?.code)) return message
|
|
@@ -1556,7 +1667,7 @@ window.__ModuleLoader__.load({
|
|
|
1556
1667
|
return
|
|
1557
1668
|
}
|
|
1558
1669
|
setBusy(true)
|
|
1559
|
-
|
|
1670
|
+
setModelMessage('正在从 ANT 刷新获准模型…')
|
|
1560
1671
|
try {
|
|
1561
1672
|
await requireCompatibleService(serviceUrl, true)
|
|
1562
1673
|
const response = await fetch('/api/ant-teams/models', { method: 'POST' })
|
|
@@ -1566,12 +1677,14 @@ window.__ModuleLoader__.load({
|
|
|
1566
1677
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
1567
1678
|
}
|
|
1568
1679
|
const count = await applyAuthorizedModels(result.models, result.service_url)
|
|
1680
|
+
setAuthorizedModels(result.models)
|
|
1681
|
+
setLastModelSync(new Date())
|
|
1569
1682
|
setCredentialConfigured(true)
|
|
1570
1683
|
setNeedsReconnect(false)
|
|
1571
1684
|
setReauthenticate(false)
|
|
1572
|
-
|
|
1685
|
+
setModelMessage(`模型目录已刷新,共 ${count} 个获准模型。`)
|
|
1573
1686
|
} catch (error) {
|
|
1574
|
-
|
|
1687
|
+
setModelMessage(error instanceof Error ? error.message : '刷新模型失败,请稍后重试。')
|
|
1575
1688
|
} finally {
|
|
1576
1689
|
setBusy(false)
|
|
1577
1690
|
}
|
|
@@ -1912,6 +2025,8 @@ window.__ModuleLoader__.load({
|
|
|
1912
2025
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
1913
2026
|
credentialSaved = true
|
|
1914
2027
|
await applyAuthorizedModels(result.models, normalized)
|
|
2028
|
+
setAuthorizedModels(result.models)
|
|
2029
|
+
setLastModelSync(new Date())
|
|
1915
2030
|
setCode('')
|
|
1916
2031
|
onServiceUrlChange(normalized)
|
|
1917
2032
|
setCodeRequested(false)
|
|
@@ -1940,86 +2055,117 @@ window.__ModuleLoader__.load({
|
|
|
1940
2055
|
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
1941
2056
|
const updateGuideUrl = pinnedServiceUrl
|
|
1942
2057
|
? new URL('/ant-update', pinnedServiceUrl).href : null
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
? '
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
style:
|
|
1963
|
-
|
|
1964
|
-
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color,
|
|
1965
|
-
},
|
|
1966
|
-
}, serviceCompatibility.message) : null,
|
|
1967
|
-
credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
1968
|
-
h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
1969
|
-
h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
1970
|
-
h('button', { type: 'button', disabled: busy, onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() }, style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
1971
|
-
h('section', { style: { display: 'grid', gap: 8, paddingTop: 12, borderTop: '0.5px solid var(--dsw-alias-border-l2)' } },
|
|
1972
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },
|
|
1973
|
-
h('h4', { style: { margin: 0, fontSize: 14, fontWeight: 650 } }, 'ANT 插件更新'),
|
|
1974
|
-
h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
|
|
1975
|
-
!canCheckUpdate ? h('p', { role: 'status', style: mutedTextStyle }, '连接团队后可在这里检查并安装 ANT 更新;需要先恢复插件时,可打开团队管理台的更新指引。') : null,
|
|
1976
|
-
updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, `打开 ${pinnedServiceUrl} 的更新指引 ↗`)
|
|
1977
|
-
: !canCheckUpdate ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|
|
1978
|
-
bundleUpdate.message ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
1979
|
-
? { ...mutedTextStyle, color: 'var(--dsw-alias-state-error-primary)' } : mutedTextStyle }, bundleUpdate.message) : null,
|
|
1980
|
-
canCheckUpdate && bundleRelease?.version && ['available', 'artifact-missing'].includes(bundleUpdate.state) ? h('div', { style: { display: 'grid', gap: 8 } },
|
|
1981
|
-
h('p', { style: mutedTextStyle }, `发布版本:${bundleRelease.version} · 精确包名:${directInstallSpec(bundleRelease)}`),
|
|
1982
|
-
bundleRelease.release_notes ? h('p', { style: mutedTextStyle }, bundleRelease.release_notes) : null,
|
|
1983
|
-
bundleUpdate.state === 'available'
|
|
1984
|
-
? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null) : null),
|
|
1985
|
-
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
1986
|
-
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
1987
|
-
h('input', {
|
|
1988
|
-
id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
|
|
1989
|
-
onChange: (event) => setEmail(event.target.value), style: inputStyle,
|
|
1990
|
-
}),
|
|
1991
|
-
h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
|
|
1992
|
-
credentialConfigured === true && showConnectForm ? h('button', {
|
|
1993
|
-
type: 'button', disabled: busy,
|
|
2058
|
+
const modelRows = authorizedModels.map((model) => h('div', { key: `${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
2059
|
+
style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 12, alignItems: 'center',
|
|
2060
|
+
padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2061
|
+
h('strong', null, model.display_name || model.model_id),
|
|
2062
|
+
h('span', { style: mutedTextStyle }, model.protocol === 'anthropic' ? 'Anthropic' : 'OpenAI'),
|
|
2063
|
+
h('span', { style: mutedTextStyle }, '已同步到 DSH')))
|
|
2064
|
+
const connectionPanel = h('section', { style: { ...panelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
2065
|
+
h('div', null,
|
|
2066
|
+
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, 'ANT 团队账号'),
|
|
2067
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '邮箱验证建立这台 DSH 的成员凭证;断开后会清除此设备凭证和同步的模型设置。')),
|
|
2068
|
+
h('span', { role: 'status', style: mutedTextStyle }, credentialConfigured === true && !bindingReady
|
|
2069
|
+
? '本机有 ANT 凭证,但尚未确认它绑定的团队;请完成上方的绑定检查。'
|
|
2070
|
+
: credentialConfigured === true ? 'DSH 已安全保存此设备的 ANT 团队凭证。'
|
|
2071
|
+
: credentialConfigured === false ? '验证工作邮箱后即可连接团队服务。' : '正在检查 ANT 连接状态…'),
|
|
2072
|
+
serviceCompatibility.state !== 'idle' ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
2073
|
+
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color } },
|
|
2074
|
+
serviceCompatibility.message) : null,
|
|
2075
|
+
credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
2076
|
+
h('button', { type: 'button', disabled: busy || !bindingReady,
|
|
2077
|
+
onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
2078
|
+
h('button', { type: 'button', disabled: busy,
|
|
1994
2079
|
onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
1995
|
-
style: secondaryButtonStyle,
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2080
|
+
style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
2081
|
+
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
2082
|
+
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
2083
|
+
h('input', { id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
|
|
2084
|
+
onChange: (event) => setEmail(event.target.value), style: inputStyle }),
|
|
2085
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
|
|
2086
|
+
showConnectForm && codeRequested ? h('form', { onSubmit: connect, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
2087
|
+
h('label', { htmlFor: 'ant-team-model-code', style: { fontSize: 13, fontWeight: 550 } }, '邮箱验证码'),
|
|
2088
|
+
h('input', { id: 'ant-team-model-code', type: 'text', inputMode: 'numeric', autoComplete: 'one-time-code',
|
|
2089
|
+
required: true, maxLength: 8, value: code, placeholder: '8 位验证码',
|
|
2090
|
+
onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle }),
|
|
2091
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
2092
|
+
credentialConfigured === true && showConnectForm ? h('button', { type: 'button', disabled: busy,
|
|
2093
|
+
onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
2094
|
+
style: secondaryButtonStyle }, '断开当前设备') : null,
|
|
2095
|
+
allowLocalCleanup ? h('section', { style: { ...panelStyle, padding: 14, gap: 9 } },
|
|
2096
|
+
h('p', { style: mutedTextStyle }, savedDeviceId()
|
|
2097
|
+
? `已保存的设备编号:${savedDeviceId()}。请交给原团队管理员核对。`
|
|
2098
|
+
: '本机无法确认旧设备编号。请让原团队管理员按成员查看设备清单,勿用新编号猜测。'),
|
|
2099
|
+
h('p', { style: mutedTextStyle }, '仅清理本机不会确认服务端令牌已撤销;请先取得管理员确认。'),
|
|
2100
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
2101
|
+
h('button', { type: 'button', disabled: busy, onClick: () => void clearOnlyLocal(), style: secondaryButtonStyle }, '仅清理本机'),
|
|
2102
|
+
h('button', { type: 'button', disabled: busy || !savedDeviceId(), onClick: async () => {
|
|
2103
|
+
try {
|
|
2104
|
+
const deviceId = savedDeviceId()
|
|
2105
|
+
if (!deviceId) throw new Error('device id unavailable')
|
|
2106
|
+
if (typeof window.navigator?.clipboard?.writeText !== 'function') throw new Error('clipboard unavailable')
|
|
2107
|
+
await window.navigator.clipboard.writeText(deviceId)
|
|
2108
|
+
setMessage('设备编号已复制,请交给原团队管理员。')
|
|
2109
|
+
} catch { setMessage(savedDeviceId() ? `请复制设备编号:${savedDeviceId()}` : '本机无法确认旧设备编号,请联系原团队管理员核查。') }
|
|
2110
|
+
}, style: secondaryButtonStyle }, '复制设备编号'))) : null,
|
|
2111
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null)
|
|
2112
|
+
|
|
2113
|
+
const modelPanel = h('section', { style: { ...panelStyle, display: activeTab === 'models' ? 'grid' : 'none' } },
|
|
2114
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
2115
|
+
h('div', null,
|
|
2116
|
+
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '团队模型'),
|
|
2117
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '每个获准模型单独列出;刷新后同步到 DSH 模型选择器。')),
|
|
2118
|
+
h('span', { style: { flexShrink: 0, padding: '4px 9px', borderRadius: 999,
|
|
2119
|
+
color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
2120
|
+
backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
|
|
2121
|
+
fontSize: 12,
|
|
2122
|
+
} }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
|
|
2123
|
+
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2124
|
+
modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
|
|
2125
|
+
h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
|
|
2126
|
+
: credentialConfigured === true ? '当前没有已获准模型,请刷新或联系团队管理员。'
|
|
2127
|
+
: '连接并验证工作邮箱后,这里会列出逐个获准模型。'),
|
|
2128
|
+
credentialConfigured !== true ? h('button', { type: 'button', onClick: () => onSelectTab('connection'),
|
|
2129
|
+
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
2130
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
2131
|
+
h('button', { type: 'button', disabled: busy || !bindingReady || credentialConfigured !== true,
|
|
2132
|
+
onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2133
|
+
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString('zh-CN')}`) : null),
|
|
2134
|
+
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
2135
|
+
|
|
2136
|
+
const updatePanel = h('section', { style: { ...panelStyle, display: activeTab === 'updates' ? 'grid' : 'none' } },
|
|
2137
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
2138
|
+
h('div', null,
|
|
2139
|
+
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, 'ANT 插件更新'),
|
|
2140
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '团队连接就绪后自动检查;有新版时顶部会持续提醒。')),
|
|
2141
|
+
h('span', { style: { ...mutedTextStyle, color: bundleUpdate.state === 'revoked'
|
|
2142
|
+
? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
2143
|
+
bundleUpdate.state === 'available' ? '有新版' : bundleUpdate.state === 'done' ? '需重启 DSH'
|
|
2144
|
+
: bundleUpdate.state === 'current' ? '已是最新' : bundleUpdate.state === 'revoked' ? '安装包已撤回' : '更新状态')),
|
|
2145
|
+
bundleUpdate.currentVersion ? h('p', { style: mutedTextStyle }, `当前安装版本:${bundleUpdate.currentVersion}`) : null,
|
|
2146
|
+
bundleRelease?.version ? h('p', { style: mutedTextStyle }, `最新发布版本:${bundleRelease.version} · 精确包名:${directInstallSpec(bundleRelease)}`) : null,
|
|
2147
|
+
serviceCompatibility.state !== 'idle' ? h('p', { role: 'status', style: {
|
|
2148
|
+
...mutedTextStyle,
|
|
2149
|
+
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color,
|
|
2150
|
+
} }, `当前已安装版本与团队服务端协议:${serviceCompatibility.message}`) : null,
|
|
2151
|
+
bundleUpdate.message ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
2152
|
+
? { ...mutedTextStyle, color: 'var(--dsw-alias-state-error-primary)' } : mutedTextStyle }, bundleUpdate.message) : null,
|
|
2153
|
+
!canCheckUpdate && updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer',
|
|
2154
|
+
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, `打开 ${pinnedServiceUrl} 的更新指引 ↗`) : null,
|
|
2155
|
+
!canCheckUpdate && !updateGuideUrl ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|
|
2156
|
+
!canCheckUpdate ? h('button', { type: 'button', onClick: () => onSelectTab('connection'), style: secondaryButtonStyle },
|
|
2157
|
+
'前往团队连接') : null,
|
|
2158
|
+
canCheckUpdate && bundleRelease?.version && ['available', 'artifact-missing'].includes(bundleUpdate.state)
|
|
2159
|
+
? h('div', { style: { display: 'grid', gap: 8 } },
|
|
2160
|
+
bundleRelease.release_notes ? h('div', { style: { ...panelStyle, padding: 12 } },
|
|
2161
|
+
h('strong', null, '更新说明'), h('p', { style: mutedTextStyle }, bundleRelease.release_notes)) : null,
|
|
2162
|
+
bundleUpdate.state === 'available'
|
|
2163
|
+
? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null) : null,
|
|
2164
|
+
h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking',
|
|
2165
|
+
onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '重新检查更新'))
|
|
2166
|
+
|
|
2167
|
+
return h('div', { style: { display: 'grid', gap: 16, width: '100%' } },
|
|
2168
|
+
connectionPanel, modelPanel, updatePanel)
|
|
2023
2169
|
|
|
2024
2170
|
}
|
|
2025
2171
|
|