@ai-wendao/ant 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/client.js +544 -199
- package/delegation.js +29 -0
- package/index.js +10 -4
- 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', '关于'],
|
|
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': '演示',
|
|
@@ -102,6 +113,7 @@ window.__ModuleLoader__.load({
|
|
|
102
113
|
backgroundColor: 'var(--dsw-alias-bg-module-platform)', boxSizing: 'border-box',
|
|
103
114
|
}
|
|
104
115
|
const mutedTextStyle = { margin: 0, color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6 }
|
|
116
|
+
const sectionTitleStyle = { margin: 0, fontSize: 16, fontWeight: 650, lineHeight: 1.4 }
|
|
105
117
|
const inputStyle = {
|
|
106
118
|
width: '100%', boxSizing: 'border-box', padding: '10px 12px',
|
|
107
119
|
color: 'var(--dsw-alias-label-primary)', backgroundColor: 'var(--dsw-specific-input-major)',
|
|
@@ -234,6 +246,7 @@ window.__ModuleLoader__.load({
|
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
function TeamServiceConfig({ remote, openInstalledPlugin }) {
|
|
249
|
+
const [activeTab, setActiveTab] = useState(loadAntSettingsTab)
|
|
237
250
|
const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
|
|
238
251
|
const [draft, setDraft] = useState(serviceUrl)
|
|
239
252
|
const [binding, setBinding] = useState({ state: 'checking', url: null })
|
|
@@ -241,6 +254,24 @@ window.__ModuleLoader__.load({
|
|
|
241
254
|
const [message, setMessage] = useState('')
|
|
242
255
|
const [pluginAlerts, setPluginAlerts] = useState([])
|
|
243
256
|
const [delegationRevision, setDelegationRevision] = useState(0)
|
|
257
|
+
const [updateSummary, setUpdateSummary] = useState(null)
|
|
258
|
+
const selectSettingsTab = (tab) => {
|
|
259
|
+
setActiveTab(tab)
|
|
260
|
+
try { localStorage.setItem(ANT_SETTINGS_TAB_STORAGE_KEY, tab) } catch {}
|
|
261
|
+
}
|
|
262
|
+
const moveSettingsTabFocus = (event, index) => {
|
|
263
|
+
const key = event.key
|
|
264
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(key)) return
|
|
265
|
+
event.preventDefault()
|
|
266
|
+
const step = key === 'ArrowLeft' ? -1 : key === 'ArrowRight' ? 1 : 0
|
|
267
|
+
const next = key === 'Home' ? 0 : key === 'End' ? ANT_SETTINGS_TABS.length - 1
|
|
268
|
+
: (index + step + ANT_SETTINGS_TABS.length) % ANT_SETTINGS_TABS.length
|
|
269
|
+
const [tab] = ANT_SETTINGS_TABS[next]
|
|
270
|
+
selectSettingsTab(tab)
|
|
271
|
+
event.currentTarget.parentElement?.querySelectorAll('[role="tab"]')?.[next]?.focus()
|
|
272
|
+
}
|
|
273
|
+
const updateNeedsAttention = ['available', 'artifact-missing', 'revoked', 'migration-required',
|
|
274
|
+
'unknown', 'failed', 'installing', 'done'].includes(updateSummary?.state)
|
|
244
275
|
useEffect(() => {
|
|
245
276
|
let disposed = false
|
|
246
277
|
const revision = bindingRevision.current
|
|
@@ -312,6 +343,11 @@ window.__ModuleLoader__.load({
|
|
|
312
343
|
}
|
|
313
344
|
}
|
|
314
345
|
const link = binding.state === 'ready' ? consoleUrl(binding.url || serviceUrl) : null
|
|
346
|
+
const bindingSummary = binding.state === 'checking' ? '正在确认这台 DSH 的团队连接…'
|
|
347
|
+
: binding.state === 'error' ? '暂时无法确认团队连接状态'
|
|
348
|
+
: binding.state === 'unverified' ? '本机有 ANT 凭证,但设备归属待核对'
|
|
349
|
+
: binding.url ? `团队服务已连接 · ${new URL(binding.url).host}` : '尚未连接团队服务'
|
|
350
|
+
const bindingReadyForSummary = binding.state === 'ready' && Boolean(binding.url)
|
|
315
351
|
const onConnectionChange = (url) => {
|
|
316
352
|
bindingRevision.current += 1
|
|
317
353
|
setBinding({ state: 'ready', url: url || null })
|
|
@@ -322,45 +358,81 @@ window.__ModuleLoader__.load({
|
|
|
322
358
|
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, url) } catch {}
|
|
323
359
|
}
|
|
324
360
|
}
|
|
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 } },
|
|
361
|
+
const teamServicePanel = h('section', { style: { ...panelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
362
|
+
h('div', null,
|
|
363
|
+
h('h3', { style: sectionTitleStyle }, '团队服务'),
|
|
364
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
|
|
365
|
+
h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: binding.url ? '1fr' : 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
|
|
332
366
|
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
|
-
}),
|
|
367
|
+
h('input', { id: 'ant-team-service-url', type: 'url', required: true, value: binding.url || draft,
|
|
368
|
+
readOnly: binding.state !== 'ready' || Boolean(binding.url), placeholder: 'https://ant.example.com',
|
|
369
|
+
onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) }, style: inputStyle }),
|
|
343
370
|
binding.url ? null : h('button', { type: 'submit', disabled: binding.state !== 'ready', style: primaryButtonStyle }, '保存地址')),
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
371
|
+
binding.url ? h('p', { style: mutedTextStyle }, '若要切换团队,请先在下方断开当前设备,再填写新地址并验证邮箱。') : null,
|
|
372
|
+
binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
|
|
373
|
+
binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
|
|
374
|
+
binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
|
|
375
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
|
|
376
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
377
|
+
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null))
|
|
378
|
+
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
379
|
+
h('div', { style: { display: 'grid', gap: 4 } },
|
|
380
|
+
h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650, lineHeight: 1.35 } }, '协作机器人'),
|
|
381
|
+
h('p', { style: mutedTextStyle }, '同事可以像平常一样聊天;需要使用你电脑的能力时,ANT 才核验授权。')),
|
|
382
|
+
h('div', { role: 'status', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
383
|
+
flexWrap: 'wrap', gap: 10, padding: '10px 12px', borderRadius: 8,
|
|
384
|
+
backgroundColor: bindingReadyForSummary ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)' } },
|
|
385
|
+
h('span', null, bindingSummary),
|
|
386
|
+
!bindingReadyForSummary ? h('button', { type: 'button', onClick: () => selectSettingsTab('connection'),
|
|
387
|
+
style: secondaryButtonStyle }, binding.state === 'error' ? '检查连接' : '查看连接设置') : null),
|
|
388
|
+
h('div', { style: { position: 'sticky', top: 0, zIndex: 8, display: 'grid', gap: 8, padding: '6px 0',
|
|
389
|
+
backgroundColor: 'var(--dsw-alias-bg-module-platform)' } },
|
|
390
|
+
pluginAlerts.length ? h('section', { role: 'alert', style: { ...panelStyle,
|
|
391
|
+
borderColor: 'var(--dsw-alias-label-danger, #d44)', maxHeight: '28vh', overflowY: 'auto' } },
|
|
392
|
+
h('h3', { style: sectionTitleStyle }, '已安装插件存在安全撤回公告'),
|
|
353
393
|
...pluginAlerts.map((plugin) => h('p', { key: plugin.id, style: mutedTextStyle },
|
|
354
394
|
`${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
|
|
355
395
|
...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
|
|
356
396
|
onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
|
|
357
|
-
h(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
397
|
+
updateNeedsAttention ? h('section', { role: 'status', 'aria-live': 'polite', style: {
|
|
398
|
+
...panelStyle, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap',
|
|
399
|
+
gap: 12, padding: 12, borderColor: updateSummary.state === 'revoked'
|
|
400
|
+
? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-border-l2))',
|
|
401
|
+
backgroundColor: updateSummary.state === 'revoked'
|
|
402
|
+
? 'var(--dsw-alias-state-error-secondary, var(--dsw-alias-bg-module-platform))'
|
|
403
|
+
: 'var(--dsw-alias-state-warning-secondary, var(--dsw-alias-bg-module-platform))',
|
|
404
|
+
} },
|
|
405
|
+
h('div', { style: { display: 'grid', gap: 3, flex: '1 1 300px' } },
|
|
406
|
+
h('strong', null, updateSummary.state === 'available'
|
|
407
|
+
? `有新版 ANT:${updateSummary.currentVersion || '当前版本未知'} → ${updateSummary.version}`
|
|
408
|
+
: updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
|
|
409
|
+
: updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
|
|
410
|
+
h('span', { style: mutedTextStyle }, updateSummary.message || '打开“关于”查看处理方式。')),
|
|
411
|
+
h('button', { type: 'button', onClick: () => selectSettingsTab('updates'), style: primaryButtonStyle },
|
|
412
|
+
updateSummary.state === 'available' ? '查看并更新' : '查看处理方式')) : null,
|
|
413
|
+
h('nav', { role: 'tablist', 'aria-label': 'ANT 插件设置', style: {
|
|
414
|
+
display: 'flex', gap: 4, overflowX: 'auto', borderBottom: '1px solid var(--dsw-alias-border-l2)',
|
|
415
|
+
} },
|
|
416
|
+
...ANT_SETTINGS_TABS.map(([tab, label], index) => h('button', { key: tab, id: `ant-settings-tab-${tab}`,
|
|
417
|
+
type: 'button', role: 'tab', tabIndex: activeTab === tab ? 0 : -1,
|
|
418
|
+
'aria-controls': 'ant-settings-content', 'aria-selected': String(activeTab === tab),
|
|
419
|
+
onKeyDown: (event) => moveSettingsTabFocus(event, index), onClick: () => selectSettingsTab(tab),
|
|
420
|
+
style: { ...secondaryButtonStyle, minHeight: 38, padding: '7px 12px', whiteSpace: 'nowrap',
|
|
421
|
+
borderRadius: '8px 8px 0 0', color: activeTab === tab ? 'var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.color,
|
|
422
|
+
borderBottom: activeTab === tab ? '2px solid var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.border,
|
|
423
|
+
backgroundColor: activeTab === tab ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor,
|
|
424
|
+
} }, tab === 'updates' && updateNeedsAttention ? `${label} · ${updateSummary.state === 'available' ? '有新版' : '需处理'}` : label)))),
|
|
425
|
+
h('div', { id: 'ant-settings-content', role: 'tabpanel', 'aria-labelledby': `ant-settings-tab-${activeTab}` },
|
|
426
|
+
teamServicePanel,
|
|
427
|
+
h(ModelSyncFooter, { remote, serviceUrl, bindingReady: binding.state === 'ready', activeTab,
|
|
428
|
+
onServiceUrlChange: setServiceUrl, onConnectionChange,
|
|
429
|
+
onUpdateStateChange: setUpdateSummary, onSelectTab: selectSettingsTab }),
|
|
430
|
+
h(DelegationCards, { remote, revision: delegationRevision,
|
|
431
|
+
activeTab, onSelectTab: selectSettingsTab,
|
|
432
|
+
openImSettings: () => openInstalledPlugin('@xmanrui/dsh-im')})))
|
|
361
433
|
}
|
|
362
434
|
|
|
363
|
-
function DelegationCards({ remote, revision, openImSettings }) {
|
|
435
|
+
function DelegationCards({ remote, revision, activeTab, onSelectTab, openImSettings }) {
|
|
364
436
|
const [bots, setBots] = useState([])
|
|
365
437
|
const [enrolled, setEnrolled] = useState([])
|
|
366
438
|
const [groups, setGroups] = useState([])
|
|
@@ -393,6 +465,7 @@ window.__ModuleLoader__.load({
|
|
|
393
465
|
const [loadFailures, setLoadFailures] = useState([])
|
|
394
466
|
const [createOpen, setCreateOpen] = useState(false)
|
|
395
467
|
const [message, setMessage] = useState('')
|
|
468
|
+
const [draftNotice, setDraftNotice] = useState('')
|
|
396
469
|
const load = async () => {
|
|
397
470
|
setLoading(true)
|
|
398
471
|
setMessage('')
|
|
@@ -430,12 +503,27 @@ window.__ModuleLoader__.load({
|
|
|
430
503
|
} finally { setLoading(false) }
|
|
431
504
|
}
|
|
432
505
|
useEffect(() => { void load().catch((error) => setMessage(error.message)) }, [remote, revision])
|
|
506
|
+
useEffect(() => { setMessage('') }, [activeTab])
|
|
433
507
|
const selectedBot = bots.find((bot) => bot.bot_id === botId)
|
|
434
508
|
const botConnected = Boolean(selectedBot?.available && selectedBot?.connected)
|
|
435
509
|
const selectedEnrollment = enrolled.find((bot) => bot.bot_fingerprint === selectedBot?.fingerprint)
|
|
436
510
|
const selectedEnrollmentBound = selectedEnrollment?.device_bound_to_current === true
|
|
511
|
+
const dmChatSettingAvailable = typeof selectedEnrollment?.dm_chat_enabled === 'boolean'
|
|
512
|
+
const dmChatEnabled = selectedEnrollment?.dm_chat_enabled !== false
|
|
513
|
+
const dmChatStatus = !selectedBot ? '先选择机器人'
|
|
514
|
+
: loadFailures.includes('enrolled-bots') ? '暂时无法读取'
|
|
515
|
+
: !selectedEnrollmentBound ? '先绑定本机'
|
|
516
|
+
: !dmChatSettingAvailable ? '需升级服务端' : dmChatEnabled ? '已开启' : '已关闭'
|
|
517
|
+
const dmChatDescription = !selectedBot ? '选择机器人后查看或修改私聊设置。'
|
|
518
|
+
: loadFailures.includes('enrolled-bots') ? '暂时无法读取私聊设置,请刷新状态后再试。'
|
|
519
|
+
: !selectedEnrollmentBound ? '请先把机器人绑定到这台 DSH,再修改私聊设置。'
|
|
520
|
+
: !dmChatSettingAvailable ? '当前团队服务端尚未提供私聊开关,请先升级服务端。'
|
|
521
|
+
: dmChatEnabled
|
|
522
|
+
? '允许此机器人接收私聊;任务仍按能力授权执行。'
|
|
523
|
+
: '新私聊消息不会交给 DSH;所有者身份绑定仍可用,已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
|
|
437
524
|
const availableGroups = groups.filter((group) => group.bot_fingerprint === selectedBot?.fingerprint)
|
|
438
525
|
const selectedChatGroup = availableGroups.find((group) => group.group_id === chatGroupId)
|
|
526
|
+
const groupChatLoadFailed = loadFailures.includes('groups')
|
|
439
527
|
const activeCards = cards.filter((card) => !card.revoked_at && card.expires_at * 1000 > Date.now())
|
|
440
528
|
const selectedGroupHasTasks = Boolean(selectedBot && chatGroupId && (
|
|
441
529
|
activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
@@ -454,17 +542,21 @@ window.__ModuleLoader__.load({
|
|
|
454
542
|
? `仅指定成员:${senderIds.trim() || '尚未填写成员 ID'}`
|
|
455
543
|
: conversationKind === 'group' ? `群 ${groupId || '尚未选择'} 的所有成员` : '任何能私聊此机器人的人'
|
|
456
544
|
const resultRouteSummary = conversationKind === 'group' ? '回复到原群(群内可见)' : '回复到发起任务的私聊'
|
|
545
|
+
const executorSummary = runner === 'codex' ? '本机 Codex · PR 快照'
|
|
546
|
+
: runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH · 当前授权模型'
|
|
547
|
+
const resourceSummary = selectedRunnerNeedsPr ? '指定仓库的已核验 PR 快照'
|
|
548
|
+
: '当前会话中 DSH 实际可见的内容'
|
|
457
549
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
458
550
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
459
551
|
: conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
|
|
460
552
|
: conversationKind === 'group' && !groupId ? '请在“参数与授权范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
|
|
461
553
|
: senderScope === 'allowlist' && !senderIds.trim() ? '请在“参数与授权范围”中填写可请求成员的 IM ID。'
|
|
462
|
-
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '
|
|
554
|
+
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
|
|
463
555
|
: parameters.some((rule) => rule.kind === 'gitea_pr') && !selectedEnrollment?.gitea_origin
|
|
464
|
-
? '
|
|
556
|
+
? '请先在“机器人与聊天”中登记这个机器人的可信 Gitea 地址。'
|
|
465
557
|
: parameters.some((rule) => rule.kind === 'gitea_pr')
|
|
466
558
|
&& normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
|
|
467
|
-
? '请先保存机器人级可信 Gitea 地址;PR
|
|
559
|
+
? '请先保存机器人级可信 Gitea 地址;PR 参数会自动使用这个地址。'
|
|
468
560
|
: selectedRunnerNeedsPr && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
|
|
469
561
|
? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
|
|
470
562
|
: selectedRunnerNeedsPr && !parameters[0].required
|
|
@@ -488,7 +580,6 @@ window.__ModuleLoader__.load({
|
|
|
488
580
|
gitea_origin: selectedEnrollment?.gitea_origin || trustedOrigin || '', repository: '' }])
|
|
489
581
|
setRunner(codexAvailable ? 'codex' : claudeCodeAvailable ? 'claude-code' : 'dsh')
|
|
490
582
|
setCreateOpen(true)
|
|
491
|
-
window.document?.getElementById('ant-new-task')?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
492
583
|
}
|
|
493
584
|
const saveGiteaToken = async () => {
|
|
494
585
|
if (!giteaToken.trim()) return
|
|
@@ -520,7 +611,7 @@ window.__ModuleLoader__.load({
|
|
|
520
611
|
if (!response.ok) throw new Error(result.message || '协作机器人启用失败。')
|
|
521
612
|
await load()
|
|
522
613
|
if (origin) setTrustedOrigin(origin)
|
|
523
|
-
setMessage('
|
|
614
|
+
setMessage('协作机器人已启用。你可以分别设置私聊和群聊,再开放需要的能力。')
|
|
524
615
|
} catch (error) { setMessage(error.message) }
|
|
525
616
|
finally { setBusy(false) }
|
|
526
617
|
}
|
|
@@ -557,9 +648,8 @@ window.__ModuleLoader__.load({
|
|
|
557
648
|
} catch (error) { setMessage(error.message) }
|
|
558
649
|
finally { setBusy(false) }
|
|
559
650
|
}
|
|
560
|
-
const saveGroupChat = async () => {
|
|
651
|
+
const saveGroupChat = async (enabled) => {
|
|
561
652
|
if (!selectedBot || !selectedEnrollmentBound || !chatGroupId.trim()) return
|
|
562
|
-
const enabled = !selectedChatGroup?.chat_enabled
|
|
563
653
|
if (!enabled && selectedGroupHasTasks) {
|
|
564
654
|
setMessage('这个群还有开放能力。请先停止这些能力,再关闭群聊。')
|
|
565
655
|
return
|
|
@@ -585,6 +675,27 @@ window.__ModuleLoader__.load({
|
|
|
585
675
|
} catch (error) { setMessage(error.message) }
|
|
586
676
|
finally { setBusy(false) }
|
|
587
677
|
}
|
|
678
|
+
const saveDmChat = async (enabled) => {
|
|
679
|
+
if (!selectedBot || !selectedEnrollmentBound || !dmChatSettingAvailable) return
|
|
680
|
+
if (!window.confirm(enabled
|
|
681
|
+
? '开启后,此机器人会在私聊中正常对话;私聊任务仍逐项按能力授权核验。'
|
|
682
|
+
: '关闭后,新私聊消息不会进入 DSH,包括普通聊天、任务请求和状态查询;所有者身份绑定命令仍可用。关闭前已提交的任务不会被撤销,仍按原私聊回复。已开放的私聊能力不会删除;重新开启后,未过期的能力会恢复触发。群聊设置不受影响。')) return
|
|
683
|
+
setBusy(true)
|
|
684
|
+
try {
|
|
685
|
+
const response = await fetch('/api/ant-teams/delegation/dm-chat', {
|
|
686
|
+
method: 'PUT', headers: { 'content-type': 'application/json' },
|
|
687
|
+
body: JSON.stringify({ bot_id: selectedBot.bot_id,
|
|
688
|
+
bot_fingerprint: selectedBot.fingerprint, enabled }),
|
|
689
|
+
})
|
|
690
|
+
if (!response.ok) {
|
|
691
|
+
const result = await readLocalJsonResponse(response)
|
|
692
|
+
throw new Error(result.message || '私聊设置保存失败。')
|
|
693
|
+
}
|
|
694
|
+
await load()
|
|
695
|
+
setMessage(enabled ? '已开启此机器人的私聊。' : '已关闭此机器人的私聊;群聊设置保持不变。')
|
|
696
|
+
} catch (error) { setMessage(error.message) }
|
|
697
|
+
finally { setBusy(false) }
|
|
698
|
+
}
|
|
588
699
|
const saveLocalProcessingSettings = async (event) => {
|
|
589
700
|
event.preventDefault()
|
|
590
701
|
setBusy(true)
|
|
@@ -604,8 +715,51 @@ window.__ModuleLoader__.load({
|
|
|
604
715
|
} catch (error) { setMessage(error.message) }
|
|
605
716
|
finally { setBusy(false) }
|
|
606
717
|
}
|
|
718
|
+
const setupStepper = h('ol', { 'aria-label': '设置步骤', style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 150px), 1fr))',
|
|
719
|
+
gap: 8, padding: 0, margin: 0, listStyle: 'none' } },
|
|
720
|
+
...[
|
|
721
|
+
{ label: '绑定机器人', detail: '连接到这台 DSH' },
|
|
722
|
+
{ label: '设置聊天范围', detail: '私聊和群聊分别控制' },
|
|
723
|
+
{ label: '开放能力', detail: selectedTaskCount ? `${selectedTaskCount} 项已开放` : '按需要逐项开放' },
|
|
724
|
+
{ label: '复核授权', detail: createOpen ? '正在配置授权范围' : selectedTaskCount ? '可随时查看与停止' : '请求范围由所有者设定' },
|
|
725
|
+
].map((step, index) => {
|
|
726
|
+
const currentStep = !botConnected || !selectedEnrollmentBound ? 0 : createOpen ? 3 : selectedTaskCount ? 4 : 2
|
|
727
|
+
const state = index < currentStep ? '完成' : index === currentStep ? '当前' : '待完成'
|
|
728
|
+
return h('li', { key: step.label, style: { display: 'grid', gap: 3, padding: '8px 10px',
|
|
729
|
+
borderTop: `2px solid ${state === '完成' ? 'var(--dsw-alias-state-success-primary)' : state === '当前' ? 'var(--dsw-alias-state-business-primary)' : 'var(--dsw-alias-border-l2)'}`,
|
|
730
|
+
minWidth: 0 } },
|
|
731
|
+
h('strong', { style: { fontSize: 13, fontWeight: 600 } }, step.label),
|
|
732
|
+
h('span', { style: mutedTextStyle }, step.detail))
|
|
733
|
+
}))
|
|
734
|
+
const chatScopePanel = h('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12,
|
|
735
|
+
display: 'grid', gap: 8 } },
|
|
736
|
+
h('strong', null, '机器人和聊天范围'),
|
|
737
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 } },
|
|
738
|
+
h('div', { style: { display: 'grid', gap: 5, padding: 10, borderRadius: 8,
|
|
739
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
740
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
741
|
+
h('label', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
742
|
+
h('input', { type: 'checkbox', checked: dmChatEnabled,
|
|
743
|
+
disabled: busy || !selectedBot || !selectedEnrollmentBound
|
|
744
|
+
|| enrollmentLoadError || !dmChatSettingAvailable,
|
|
745
|
+
onChange: (event) => void saveDmChat(event.target.checked),
|
|
746
|
+
'aria-label': '允许机器人私聊' }), h('strong', null, '私聊')),
|
|
747
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'nowrap' } }, dmChatStatus)),
|
|
748
|
+
h('p', { style: mutedTextStyle }, dmChatDescription)),
|
|
749
|
+
h('div', { style: { display: 'grid', gap: 5, padding: 10, borderRadius: 8,
|
|
750
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
751
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
752
|
+
h('label', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
753
|
+
h('input', { type: 'checkbox', checked: Boolean(selectedChatGroup?.chat_enabled),
|
|
754
|
+
disabled: busy || groupChatLoadFailed || !selectedEnrollmentBound || !botConnected || !chatGroupId.trim()
|
|
755
|
+
|| (Boolean(selectedChatGroup?.chat_enabled) && selectedGroupHasTasks),
|
|
756
|
+
onChange: (event) => void saveGroupChat(event.target.checked),
|
|
757
|
+
'aria-label': '允许所选群进行普通聊天' }), h('strong', null, '群聊')),
|
|
758
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'nowrap' } }, `${availableGroups.length} 个已发现`)),
|
|
759
|
+
h('p', { style: mutedTextStyle }, '选择一个群后可单独启用;不影响私聊设置,群任务仍需单独授权。'))))
|
|
607
760
|
const createCard = async (event) => {
|
|
608
761
|
event.preventDefault()
|
|
762
|
+
setDraftNotice('')
|
|
609
763
|
if (!selectedBot || !botConnected || !selectedEnrollmentBound) { setMessage('请先在这台 DSH 绑定选中的协作机器人。'); return }
|
|
610
764
|
if (!title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
611
765
|
setMessage('请填写能力名称、适用请求说明和处理要求。'); return
|
|
@@ -712,6 +866,30 @@ window.__ModuleLoader__.load({
|
|
|
712
866
|
} catch (error) { setMessage(error.message) }
|
|
713
867
|
finally { setBusy(false) }
|
|
714
868
|
}
|
|
869
|
+
const copyCardToDraft = (card) => {
|
|
870
|
+
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
871
|
+
const cardEnrollment = enrolled.find((bot) => bot.bot_fingerprint === card.bot_fingerprint)
|
|
872
|
+
const titleChars = Array.from(`复制:${card.title || '未命名能力'}`)
|
|
873
|
+
while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
|
|
874
|
+
setBotId(card.bot_id || cardBot?.bot_id || '')
|
|
875
|
+
setTrustedOrigin(cardEnrollment?.gitea_origin || '')
|
|
876
|
+
setTitle(titleChars.join(''))
|
|
877
|
+
setIntent(card.intent || '')
|
|
878
|
+
setExamples(Array.isArray(card.examples) ? card.examples.join('\n') : '')
|
|
879
|
+
setTaskPrompt(card.task_prompt || '')
|
|
880
|
+
setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
|
|
881
|
+
setParameters(Array.isArray(card.parameters) ? card.parameters.map((rule) => ({
|
|
882
|
+
...rule, min: String(rule.min ?? ''), max: String(rule.max ?? ''), choices: Array.isArray(rule.choices) ? rule.choices.join(',') : String(rule.choices ?? ''),
|
|
883
|
+
})) : [])
|
|
884
|
+
setConversationKind(card.conversation_kind === 'group' ? 'group' : 'dm')
|
|
885
|
+
setGroupId(card.conversation_id || '')
|
|
886
|
+
setSenderScope(card.sender_scope === 'allowlist' ? 'allowlist' : 'any')
|
|
887
|
+
setSenderIds(Array.isArray(card.sender_ids) ? card.sender_ids.join('\n') : '')
|
|
888
|
+
setDays(7)
|
|
889
|
+
setDraftNotice('已复制到新草稿。复制不保留原有效期;开放前请重新核对群、请求人和执行方,默认有效期为 7 天。')
|
|
890
|
+
setCreateOpen(true)
|
|
891
|
+
onSelectTab('capability')
|
|
892
|
+
}
|
|
715
893
|
const cancelTask = async (task) => {
|
|
716
894
|
if (!window.confirm(`取消任务 ${task.task_id}?已经发生的读取不能撤回。`)) return
|
|
717
895
|
setBusy(true)
|
|
@@ -724,15 +902,16 @@ window.__ModuleLoader__.load({
|
|
|
724
902
|
} catch (error) { setMessage(error.message) }
|
|
725
903
|
finally { setBusy(false) }
|
|
726
904
|
}
|
|
727
|
-
return h('section', { style: panelStyle,
|
|
728
|
-
|
|
729
|
-
|
|
905
|
+
return h('section', { style: { ...panelStyle,
|
|
906
|
+
display: ['bot-chat', 'capability', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
907
|
+
'aria-label': '协作机器人与开放能力' },
|
|
908
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
909
|
+
h('div', { role: 'region', 'aria-label': '机器人连接和聊天范围', style: { display: activeTab === 'bot-chat' ? 'grid' : 'none', gap: 14 } },
|
|
910
|
+
h('h3', { style: sectionTitleStyle }, '协作机器人'),
|
|
911
|
+
h('p', { style: mutedTextStyle }, '在所有者开启的私聊或群聊中,同事可以像平常一样和机器人聊天。DSH 负责理解与回答;只有准备执行你开放的能力时,ANT 才核对请求人、群、参数和权限。群里的任务结果会回复原群。'),
|
|
730
912
|
h('p', { role: 'status', style: mutedTextStyle }, '每项能力都单独设定执行方和请求范围。普通对话不会自动获得读取电脑、调用工具或访问其他群的权限。'),
|
|
731
913
|
loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
|
|
732
914
|
`部分协作信息暂时无法读取:${loadFailures.map((path) => loadNames[path]).join('、')}。已读取的信息仍显示,请稍后刷新状态。`) : null,
|
|
733
|
-
h('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12,
|
|
734
|
-
display: 'grid', gap: 8 } },
|
|
735
|
-
h('strong', null, '机器人和聊天范围'),
|
|
736
915
|
h('span', { role: 'status', style: mutedTextStyle }, loading ? '正在读取机器人与任务状态…'
|
|
737
916
|
: botLoadError ? '暂时无法读取机器人状态。请刷新后再试。'
|
|
738
917
|
: connectedBots.length === 0 ? '尚未发现已连接的机器人。请先在 DSH-IM 中连接飞书机器人。'
|
|
@@ -742,12 +921,13 @@ window.__ModuleLoader__.load({
|
|
|
742
921
|
: '机器人已连接;协作启用状态暂未读取,请刷新状态。'
|
|
743
922
|
: selectedBot && !selectedEnrollmentBound ? selectedEnrollment
|
|
744
923
|
? `“${selectedBot.name}”已连接;原协作配置尚未绑定到这台 DSH,请在本机重新启用。若原设备令牌曾被撤销,旧任务权限已关闭,绑定后需重新开放。`
|
|
745
|
-
: `“${selectedBot.name}
|
|
746
|
-
: enrolledCount === 0 ? '
|
|
747
|
-
: !selectedBot ? '
|
|
748
|
-
: authorizationLoadError ? `“${selectedBot.name}
|
|
749
|
-
:
|
|
750
|
-
: `“${selectedBot.name}
|
|
924
|
+
: `“${selectedBot.name}”已连接;请先启用本机协作。`
|
|
925
|
+
: enrolledCount === 0 ? '机器人已连接;请选择并启用协作。'
|
|
926
|
+
: !selectedBot ? '已有协作机器人;请选择一台,查看已开放的能力和聊天范围。'
|
|
927
|
+
: authorizationLoadError ? `“${selectedBot.name}”已启用协作;已开放任务暂未读取,请刷新状态。`
|
|
928
|
+
: !dmChatEnabled ? `“${selectedBot.name}”已启用协作;私聊已关闭,群聊仍按各自设置工作。`
|
|
929
|
+
: selectedTaskCount === 0 ? `“${selectedBot.name}”已启用协作;下一步开放一项能力。你也可以先私聊自测普通聊天。`
|
|
930
|
+
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount} 项能力已开放。可以私聊自测。`),
|
|
751
931
|
bots.length ? h('label', null, '我的机器人', h('select', { value: botId,
|
|
752
932
|
onChange: (event) => { setBotId(event.target.value); setGroupId(''); setChatGroupId(''); setBindingCode('');
|
|
753
933
|
setTrustedOrigin(enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
|
|
@@ -763,60 +943,147 @@ window.__ModuleLoader__.load({
|
|
|
763
943
|
selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
764
944
|
onClick: () => void enrollBot(), style: secondaryButtonStyle },
|
|
765
945
|
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null,
|
|
946
|
+
selectedBot ? h('div', { style: { display: 'grid', gap: 8, padding: 10,
|
|
947
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
|
|
948
|
+
h('strong', null, `可信 Gitea 地址 · ${selectedBot.name}`),
|
|
949
|
+
h('p', { style: mutedTextStyle }, '为这个机器人登记一次。PR 能力只需配置仓库和 PR 编号,不必重复填写地址。'),
|
|
950
|
+
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
951
|
+
h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
|
|
952
|
+
onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
|
|
953
|
+
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
954
|
+
h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
|
|
955
|
+
onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
|
|
956
|
+
selectedEnrollmentBound ? '保存可信地址' : botConnected ? '绑定机器人并保存地址' : '先连接机器人'))) : null,
|
|
957
|
+
setupStepper,
|
|
958
|
+
chatScopePanel,
|
|
766
959
|
selectedEnrollmentBound && botConnected ? h('p', { style: mutedTextStyle },
|
|
767
|
-
|
|
960
|
+
dmChatEnabled
|
|
961
|
+
? `自测:私聊“${selectedBot.name}”发送“你能做什么”;发起任务后可问“刚才的任务怎么样了”。群聊请先在目标群 @机器人,再开放能力时选择该群。`
|
|
962
|
+
: '私聊当前已关闭;如要恢复,请在上方开启。群聊请先在目标群 @机器人,再开放能力时选择该群。') : null,
|
|
768
963
|
selectedEnrollmentBound && botConnected ? h('div', { style: { display: 'grid', gap: 8 } },
|
|
769
|
-
h('strong', null, '
|
|
770
|
-
h('span', { style: mutedTextStyle },
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
964
|
+
h('strong', null, '群聊白名单'),
|
|
965
|
+
h('span', { role: 'status', style: mutedTextStyle }, groupChatLoadFailed
|
|
966
|
+
? '群聊列表与当前权限暂时无法读取,请刷新后再更改;仍可填写群 ID。'
|
|
967
|
+
: availableGroups.length
|
|
968
|
+
? '从机器人已发现的群中选择;仍可手动填写群 ID。允许普通聊天不等于允许群成员执行任务。'
|
|
969
|
+
: '先在目标群 @机器人并刷新列表;若平台暂未返回群列表,也可以手动填写群 ID。'),
|
|
970
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 8, alignItems: 'end' } },
|
|
971
|
+
h('label', { style: { display: 'grid', gap: 5 } },
|
|
972
|
+
h('span', { style: mutedTextStyle }, '选择已记录的群'),
|
|
973
|
+
h('select', { 'aria-label': '已记录的群', value: availableGroups.some((group) => group.group_id === chatGroupId) ? chatGroupId : '',
|
|
974
|
+
onChange: (event) => setChatGroupId(event.target.value), style: inputStyle },
|
|
975
|
+
h('option', { value: '' }, '选择已发现的群聊'),
|
|
976
|
+
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
977
|
+
`${group.group_code} · ${group.chat_enabled ? '已允许普通聊天' : '未允许普通聊天'}`)))),
|
|
978
|
+
h('button', { type: 'button', disabled: busy || !selectedEnrollmentBound,
|
|
979
|
+
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')),
|
|
980
|
+
h('label', { style: { display: 'grid', gap: 5 } },
|
|
981
|
+
h('span', { style: mutedTextStyle }, '群 ID(平台未返回群名时可手动填写)'),
|
|
982
|
+
h('input', { 'aria-label': '群 ID', value: chatGroupId, maxLength: 256,
|
|
983
|
+
onChange: (event) => setChatGroupId(event.target.value.trim()),
|
|
984
|
+
placeholder: '输入群 ID', style: inputStyle })),
|
|
784
985
|
selectedChatGroup?.chat_enabled && selectedGroupHasTasks ? h('span', { style: mutedTextStyle },
|
|
785
|
-
'
|
|
986
|
+
'本群还有开放能力;关闭普通聊天前请先停止这些能力。') : null,
|
|
987
|
+
h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', paddingTop: 4,
|
|
988
|
+
borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
989
|
+
h('span', { style: mutedTextStyle }, '群内所有者身份'),
|
|
990
|
+
h('span', { style: { ...mutedTextStyle, color: selectedEnrollment.owner_bound
|
|
991
|
+
? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
992
|
+
selectedEnrollment.owner_bound ? '已绑定' : '尚未绑定'),
|
|
993
|
+
h('button', { type: 'button', disabled: busy || !chatGroupId.trim(),
|
|
994
|
+
onClick: () => void issueBindingCode(), style: secondaryButtonStyle },
|
|
995
|
+
selectedEnrollment.owner_bound ? '重新获取绑定码' : '获取绑定码')),
|
|
996
|
+
h('span', { style: mutedTextStyle }, '仅当允许群成员发起受限能力时需要;验证后由机器人所有者在群内确认。'),
|
|
997
|
+
bindingCode ? h('p', { role: 'status', style: mutedTextStyle },
|
|
998
|
+
`请你本人私聊机器人发送“绑定身份 ${bindingCode}”,再回到群里 @机器人完成确认。`) : null) : null,
|
|
786
999
|
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' }) },
|
|
1000
|
+
h('button', { type: 'button', onClick: () => { setCreateOpen(true); onSelectTab('capability') },
|
|
789
1001
|
style: primaryButtonStyle }, '开放新能力'),
|
|
790
|
-
h('button', { type: 'button', onClick: preparePrExample, style: secondaryButtonStyle }, '使用 PR 差异审查示例'),
|
|
1002
|
+
h('button', { type: 'button', onClick: () => { preparePrExample(); onSelectTab('capability') }, style: secondaryButtonStyle }, '使用 PR 差异审查示例'),
|
|
791
1003
|
h('button', { type: 'button', onClick: () => void load().catch((error) => setMessage(error.message)),
|
|
792
1004
|
style: secondaryButtonStyle }, '刷新状态'))),
|
|
793
|
-
h('
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1005
|
+
h('div', { role: 'region', 'aria-label': '已开放能力列表', style: { display: activeTab === 'capability' ? 'grid' : 'none', gap: 12 } },
|
|
1006
|
+
h('h4', { style: sectionTitleStyle }, `我开放的能力 · ${authorizationLoadError ? '暂未读取' : activeCards.length}`),
|
|
1007
|
+
h('p', { style: mutedTextStyle }, '当前一项能力只能绑定一个范围:私聊,或某一个群。若要给其他群或私聊开放相同任务,目前需分别建立能力卡;它们的请求人和有效期独立。'),
|
|
1008
|
+
...activeCards.map((card) => {
|
|
1009
|
+
const expiresAt = Number(card.expires_at) * 1000
|
|
1010
|
+
const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
|
|
1011
|
+
const expiry = remainingDays <= 7 ? `${remainingDays} 天后到期` : `至 ${new Date(expiresAt).toLocaleDateString('zh-CN')}`
|
|
1012
|
+
const conversation = card.conversation_kind === 'group'
|
|
1013
|
+
? `群 ${groups.find((group) => group.group_id === card.conversation_id)?.group_code || card.conversation_id}` : '私聊'
|
|
1014
|
+
const requester = card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1015
|
+
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'
|
|
1016
|
+
const runnerName = card.runner === 'codex' ? '本机 Codex · PR 快照'
|
|
1017
|
+
: card.runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH 文字处理'
|
|
1018
|
+
const parametersText = (card.parameters || []).map((rule) => {
|
|
1019
|
+
const constraint = rule.kind === 'gitea_pr' && rule.repository ? `仓库 ${rule.repository}`
|
|
1020
|
+
: rule.kind === 'choice' ? `选项 ${(rule.choices || []).join('、')}`
|
|
1021
|
+
: ['integer', 'gitea_pr'].includes(rule.kind)
|
|
1022
|
+
? `范围 ${rule.min ?? 1}–${rule.max ?? '无上限'}` : ''
|
|
1023
|
+
return `${rule.name}:${rule.description || rule.kind}${rule.required ? '(必填)' : '(可选)'}${constraint ? ` · ${constraint}` : ''}`
|
|
1024
|
+
}).join(';')
|
|
1025
|
+
const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
|
|
1026
|
+
const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
|
|
1027
|
+
const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
|
|
1028
|
+
: 'var(--dsw-alias-label-secondary)'
|
|
1029
|
+
return h('div', { key: card.id, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
1030
|
+
alignItems: 'start', gap: 12, borderTop: '1px solid var(--dsw-alias-border-l2)', padding: '10px 0' } },
|
|
1031
|
+
h('div', { style: { display: 'grid', gap: 4, minWidth: 0 } },
|
|
1032
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1033
|
+
h('strong', { style: { overflowWrap: 'anywhere' } }, card.title || '未命名能力'),
|
|
1034
|
+
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
|
|
1035
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
|
|
1036
|
+
h('span', { style: mutedTextStyle }, `${runnerName} · ${conversation} · ${requester} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'}`),
|
|
1037
|
+
h('details', null,
|
|
1038
|
+
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看配置'),
|
|
1039
|
+
h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
|
|
1040
|
+
card.intent ? h('p', { style: mutedTextStyle }, `适用请求:${card.intent}`) : null,
|
|
1041
|
+
parametersText ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, `请求参数:${parametersText}`)
|
|
1042
|
+
: h('p', { style: mutedTextStyle }, '请求参数:无'),
|
|
1043
|
+
requesterIds ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, `允许请求的 IM 用户:${requesterIds}`) : null,
|
|
1044
|
+
examplesText ? h('p', { style: mutedTextStyle }, `请求示例:${examplesText}`) : null,
|
|
1045
|
+
card.task_prompt ? h('p', { style: mutedTextStyle }, `处理要求:${card.task_prompt}`) : null))),
|
|
1046
|
+
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end' } },
|
|
1047
|
+
h('button', { type: 'button', disabled: busy, onClick: () => copyCardToDraft(card), style: secondaryButtonStyle }, '复制'),
|
|
1048
|
+
h('button', { type: 'button', disabled: busy, onClick: () => void revokeCard(card), style: secondaryButtonStyle }, '停止')))
|
|
1049
|
+
}),
|
|
798
1050
|
activeCards.length === 0 ? h('p', { style: mutedTextStyle },
|
|
799
1051
|
authorizationLoadError ? '暂时无法确认已开放的能力,请刷新状态后再试。'
|
|
800
|
-
: '还没有开放能力。先选机器人,再开放一项能力;PR 审查只是可选示例。') : null,
|
|
801
|
-
h('
|
|
1052
|
+
: '还没有开放能力。先选机器人,再开放一项能力;PR 审查只是可选示例。') : null),
|
|
1053
|
+
h('div', { role: 'region', 'aria-label': '最近任务', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1054
|
+
h('h4', { style: sectionTitleStyle }, '最近任务'),
|
|
802
1055
|
...(tasks.filter((task) => task.task_id).length ? tasks.filter((task) => task.task_id)
|
|
803
|
-
.map((task) =>
|
|
804
|
-
|
|
805
|
-
|
|
1056
|
+
.map((task) => {
|
|
1057
|
+
const title = task.title || (task.pr_number
|
|
1058
|
+
? `${task.repository ? `${task.repository} ` : ''}PR #${task.pr_number} 差异审查` : '任务')
|
|
1059
|
+
const status = task.outcome === 'canceled' ? '已取消' : task.outcome === 'failed' ? '未完成' : ({
|
|
806
1060
|
pending: '排队中', running: '处理中', reply_pending: '结果待发送', reply_attempted: '回复状态待核对', done: '已回复',
|
|
807
|
-
}[task.status] || task.status)
|
|
808
|
-
|
|
809
|
-
|
|
1061
|
+
}[task.status] || task.status)
|
|
1062
|
+
const createdAt = task.created_at && Number.isFinite(Date.parse(task.created_at))
|
|
1063
|
+
? new Date(task.created_at).toLocaleString('zh-CN') : ''
|
|
1064
|
+
return h('div', { key: task.task_id, style: { display: 'flex', gap: 12, alignItems: 'center',
|
|
1065
|
+
justifyContent: 'space-between', flexWrap: 'wrap', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
|
|
1066
|
+
h('div', { style: { flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
1067
|
+
h('strong', null, title),
|
|
1068
|
+
h('div', { style: mutedTextStyle }, `${status}${createdAt ? ` · ${createdAt}` : ''} · 任务 ${task.task_id}`)),
|
|
1069
|
+
['card_task', 'legacy_pr_task'].includes(task.kind) && ['pending', 'running'].includes(task.status) ? h('button', { type: 'button', disabled: busy,
|
|
1070
|
+
onClick: () => void cancelTask(task), style: secondaryButtonStyle }, '取消任务') : null)
|
|
1071
|
+
})
|
|
810
1072
|
: [h('p', { key: 'none', style: mutedTextStyle }, loadFailures.includes('status')
|
|
811
|
-
? '任务记录暂时无法读取,请刷新状态。' : '暂无任务。')]),
|
|
1073
|
+
? '任务记录暂时无法读取,请刷新状态。' : '暂无任务。')])),
|
|
1074
|
+
h('div', { role: 'region', 'aria-label': '新建能力', style: { display: activeTab === 'capability' ? 'grid' : 'none' } },
|
|
812
1075
|
h('details', { id: 'ant-new-task', open: createOpen,
|
|
813
1076
|
onToggle: (event) => setCreateOpen(event.currentTarget.open),
|
|
814
1077
|
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
815
1078
|
h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '开放一项能力'),
|
|
816
|
-
h('
|
|
1079
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))',
|
|
1080
|
+
gap: 16, alignItems: 'start', marginTop: 12 } },
|
|
1081
|
+
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
817
1082
|
h('p', { style: mutedTextStyle }, selectedBot
|
|
818
1083
|
? `当前机器人:${selectedBot.name}${selectedEnrollmentBound ? ' · 已绑定此设备' : ' · 请先绑定到此设备'}`
|
|
819
1084
|
: '请先在上方选择机器人并启用协作。'),
|
|
1085
|
+
draftNotice ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1086
|
+
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, draftNotice) : null,
|
|
820
1087
|
selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
|
|
821
1088
|
'机器人及其任务能力只在这台 DSH 设备接收和执行;同一账号的其他设备不会继承本机授权。') : null,
|
|
822
1089
|
selectedEnrollment && !selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
|
|
@@ -835,19 +1102,17 @@ window.__ModuleLoader__.load({
|
|
|
835
1102
|
onChange: (event) => setRunner(event.target.value), style: inputStyle },
|
|
836
1103
|
h('option', { value: 'dsh' }, 'DSH · 已提供文字'),
|
|
837
1104
|
h('option', { value: 'codex',
|
|
838
|
-
disabled: !codexAvailable }, 'Codex · 单个 Gitea PR
|
|
1105
|
+
disabled: !codexAvailable }, 'Codex · 单个 Gitea PR 完整快照审查试用'),
|
|
839
1106
|
h('option', { value: 'claude-code', disabled: !claudeCodeAvailable },
|
|
840
1107
|
'Claude Code · 单个 Gitea PR 完整快照试用'))),
|
|
841
1108
|
nativePrUnavailableReason ? h('p', { role: 'status', style: mutedTextStyle },
|
|
842
1109
|
`${nativePrUnavailableReason},已禁用 Codex/Claude Code 完整快照选项;可选择 DSH 差异审查。`) : null,
|
|
843
1110
|
runner === 'codex' ? h('p', { role: 'status', style: mutedTextStyle },
|
|
844
|
-
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head
|
|
1111
|
+
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head 的临时代码快照。不会收到 Gitea 令牌;请以这台 DSH 的实际子代理权限为准,只有强制只读隔离已验证时才视为只读。Git LFS 实际文件不会下载。') : null,
|
|
845
1112
|
runner === 'claude-code' ? h('p', { role: 'status', style: mutedTextStyle },
|
|
846
1113
|
'Claude Code 通过 DSH 已注册的子代理运行,可查看本次 PR 的临时代码快照。ANT 不会传 Gitea 令牌;Claude Code 沿用 DSH 提供方的本机权限,不具备 Codex 路径相同的固定只读隔离。Git LFS 实际文件不会下载。') : null,
|
|
847
1114
|
selectedRunnerNeedsPr ? h('p', { role: 'status', style: mutedTextStyle },
|
|
848
1115
|
'本机 PR 执行方需要 Gitea granular token 的只读权限 read:repository 和 read:user;read:user 仅用于这台 DSH 获取 Git HTTPS 登录名。') : null,
|
|
849
|
-
h('p', { role: 'status', style: mutedTextStyle },
|
|
850
|
-
`开放后:${requesterSummary}可请求 · 有效期 ${days} 天 · ${resultRouteSummary}`),
|
|
851
1116
|
h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 10 } },
|
|
852
1117
|
h('summary', { style: { cursor: 'pointer' } }, '参数与授权范围'),
|
|
853
1118
|
h('div', null, h('strong', null, '需要从请求中确认的参数'),
|
|
@@ -863,7 +1128,7 @@ window.__ModuleLoader__.load({
|
|
|
863
1128
|
onChange: (event) => updateParameter(index, { kind: event.target.value }), style: { ...inputStyle, flex: '1 1 150px' } },
|
|
864
1129
|
h('option', { value: 'text' }, '文本'), h('option', { value: 'integer' }, '整数'),
|
|
865
1130
|
h('option', { value: 'choice' }, '选项'), h('option', { value: 'gitea_pr' }, 'Gitea PR 编号')),
|
|
866
|
-
h('label', null, h('input', { type: 'checkbox', checked: rule.required,
|
|
1131
|
+
h('label', null, h('input', { type: 'checkbox', checked: rule.required, 'aria-label': '参数必填',
|
|
867
1132
|
onChange: (event) => updateParameter(index, { required: event.target.checked }) }), ' 必填'),
|
|
868
1133
|
['integer', 'gitea_pr'].includes(rule.kind) ? h('input', { 'aria-label': '最小值', type: 'number', value: rule.min,
|
|
869
1134
|
onChange: (event) => updateParameter(index, { min: event.target.value }), placeholder: '最小值', style: { ...inputStyle, width: 120 } }) : null,
|
|
@@ -880,21 +1145,12 @@ window.__ModuleLoader__.load({
|
|
|
880
1145
|
h('button', { type: 'button', disabled: parameters.length >= 12, style: secondaryButtonStyle,
|
|
881
1146
|
onClick: () => setParameters((current) => [...current, { name: '', kind: 'text', required: true,
|
|
882
1147
|
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,
|
|
1148
|
+
parameters.some((rule) => rule.kind === 'gitea_pr') ? h('div', { style: { display: 'grid', gap: 6 } },
|
|
1149
|
+
h('p', { style: mutedTextStyle }, selectedEnrollment?.gitea_origin
|
|
1150
|
+
? `将使用机器人已登记的可信 Gitea 地址:${selectedEnrollment.gitea_origin}`
|
|
1151
|
+
: '请先在“机器人与聊天”中为这个机器人登记可信 Gitea 地址。'),
|
|
1152
|
+
!tokenConfigured ? h('button', { type: 'button', onClick: () => onSelectTab('local'), style: secondaryButtonStyle },
|
|
1153
|
+
'前往本机与任务保存 Gitea 令牌') : h('p', { style: mutedTextStyle }, '本机 Gitea 只读令牌已保存。')) : null,
|
|
898
1154
|
h('label', null, '使用场景', h('select', { value: conversationKind,
|
|
899
1155
|
onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
|
|
900
1156
|
h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
|
|
@@ -905,12 +1161,6 @@ window.__ModuleLoader__.load({
|
|
|
905
1161
|
`识别码 ${group.group_code} · ${group.group_id}`)))) : null,
|
|
906
1162
|
conversationKind === 'group' ? h('button', { type: 'button', disabled: busy,
|
|
907
1163
|
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表') : null,
|
|
908
|
-
conversationKind === 'group' && selectedEnrollmentBound ? h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
909
|
-
h('span', { style: mutedTextStyle }, selectedEnrollment.owner_bound ? '已绑定你的飞书身份,可在群内批准任务' : '群内批准前需绑定你的飞书身份'),
|
|
910
|
-
h('button', { type: 'button', disabled: busy, onClick: () => void issueBindingCode(), style: secondaryButtonStyle },
|
|
911
|
-
selectedEnrollment.owner_bound ? '重新获取绑定码' : '获取身份绑定码')) : null,
|
|
912
|
-
conversationKind === 'group' && bindingCode ? h('p', { role: 'status', style: mutedTextStyle },
|
|
913
|
-
`请你本人私聊机器人发送“绑定身份 ${bindingCode}”。完成后在群里 @机器人发送“在本群启用任务 任务名称”,并按提示确认;结果会公开回复该群。`) : null,
|
|
914
1164
|
h('label', null, '谁可以请求', h('select', { value: senderScope,
|
|
915
1165
|
onChange: (event) => setSenderScope(event.target.value), style: inputStyle },
|
|
916
1166
|
h('option', { value: 'any' }, conversationKind === 'group' ? '此群任何成员' : '任何能私聊此机器人者'),
|
|
@@ -919,9 +1169,42 @@ window.__ModuleLoader__.load({
|
|
|
919
1169
|
onChange: (event) => setSenderIds(event.target.value), placeholder: '多个 ID 用逗号分隔', style: inputStyle }) : null,
|
|
920
1170
|
h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
921
1171
|
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天')))),
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1172
|
+
),
|
|
1173
|
+
h('aside', { role: 'region', 'aria-label': '开放前复核', style: { ...panelStyle, padding: 14, gap: 10,
|
|
1174
|
+
position: 'sticky', top: 12, alignSelf: 'start' } },
|
|
1175
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1176
|
+
h('h4', { style: sectionTitleStyle }, '开放前复核'),
|
|
1177
|
+
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1178
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '草稿 · 未开放')),
|
|
1179
|
+
h('p', { style: mutedTextStyle }, '保存后,机器人只会在以下范围内执行这项能力。'),
|
|
1180
|
+
h('dl', { style: { display: 'grid', gap: 8, margin: 0 } },
|
|
1181
|
+
...[
|
|
1182
|
+
['触发位置', conversationKind === 'group' ? `群 ${groupId || '尚未选择'}` : '私聊'],
|
|
1183
|
+
['可请求人', requesterSummary],
|
|
1184
|
+
['执行方', executorSummary],
|
|
1185
|
+
['读取资料', resourceSummary],
|
|
1186
|
+
['有效期', `${days} 天`],
|
|
1187
|
+
['回复位置', resultRouteSummary],
|
|
1188
|
+
].map(([label, value]) => h('div', { key: label, style: { display: 'grid', gridTemplateColumns: '84px minmax(0, 1fr)', gap: 8 } },
|
|
1189
|
+
h('dt', { style: mutedTextStyle }, label),
|
|
1190
|
+
h('dd', { style: { margin: 0, minWidth: 0, overflowWrap: 'anywhere' } }, value)))),
|
|
1191
|
+
cardBlockReason ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1192
|
+
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, cardBlockReason) : null,
|
|
1193
|
+
h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
|
|
1194
|
+
style: { ...primaryButtonStyle, width: '100%' } }, '确认并开放能力'))))),
|
|
1195
|
+
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1196
|
+
h('section', { style: { ...panelStyle, padding: 14 } },
|
|
1197
|
+
h('strong', null, 'Gitea 本机凭据'),
|
|
1198
|
+
h('p', { style: mutedTextStyle }, tokenConfigured
|
|
1199
|
+
? '只读访问令牌已保存在这台 DSH 的安全凭据中;不会同步到 ANT 服务端或发送给执行器。'
|
|
1200
|
+
: 'PR 审查能力需要只读访问令牌。令牌只保存到这台 DSH。'),
|
|
1201
|
+
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1202
|
+
h('input', { 'aria-label': 'Gitea 只读访问令牌', type: 'password', autoComplete: 'new-password', value: giteaToken,
|
|
1203
|
+
onChange: (event) => setGiteaToken(event.target.value),
|
|
1204
|
+
placeholder: tokenConfigured ? '已保存;输入新令牌可替换' : '粘贴 Gitea 只读令牌',
|
|
1205
|
+
style: { ...inputStyle, flex: '1 1 280px' } }),
|
|
1206
|
+
h('button', { type: 'button', disabled: busy || !giteaToken.trim(),
|
|
1207
|
+
onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))),
|
|
925
1208
|
h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
926
1209
|
h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '本机处理设置'),
|
|
927
1210
|
h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务;PR 仓库缓存上限按这台 DSH 的长期仓库缓存总量计算,不包括任务临时工作目录。'),
|
|
@@ -943,8 +1226,7 @@ window.__ModuleLoader__.load({
|
|
|
943
1226
|
onChange: (event) => setRepositoryCacheLimitGib(event.target.value), style: inputStyle }),
|
|
944
1227
|
h('span', { style: mutedTextStyle }, '默认 10 GiB,范围 1–100 GiB,按长期仓库文件逻辑大小计算(不含临时工作区和文件系统分配开销)。日常不会淘汰仍启用能力的缓存;停用或到期能力会在刷新时回收。单次拉取若实际越界,为恢复预算会清理这项能力的仓库缓存(旧 PR 可重新下载)。')),
|
|
945
1228
|
h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置')),
|
|
946
|
-
h('p', { style: mutedTextStyle },
|
|
947
|
-
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null)
|
|
1229
|
+
h('p', { style: mutedTextStyle }, `本机检测到的执行方:${executors.map((item) => `${item.title}${item.available ? '(可用)' : `(${item.reason || '未启用或未找到'})`}`).join('、') || '暂未读取到'}。只有能力配置下拉框中列出的执行方能接收委托。`))))
|
|
948
1230
|
}
|
|
949
1231
|
|
|
950
1232
|
function SkillCenterPage({ remote, onBack, onAll, onSkills, onPlugins, openBundle,
|
|
@@ -1437,10 +1719,12 @@ window.__ModuleLoader__.load({
|
|
|
1437
1719
|
? h('p', { style: mutedTextStyle }, noMatches) : null))
|
|
1438
1720
|
}
|
|
1439
1721
|
|
|
1440
|
-
function ModelSyncFooter({ remote, serviceUrl, bindingReady, onServiceUrlChange,
|
|
1722
|
+
function ModelSyncFooter({ remote, serviceUrl, bindingReady, activeTab, onServiceUrlChange,
|
|
1723
|
+
onConnectionChange, onUpdateStateChange, onSelectTab }) {
|
|
1441
1724
|
const [email, setEmail] = useState('')
|
|
1442
1725
|
const [code, setCode] = useState('')
|
|
1443
1726
|
const [message, setMessage] = useState('')
|
|
1727
|
+
const [modelMessage, setModelMessage] = useState('')
|
|
1444
1728
|
const [busy, setBusy] = useState(false)
|
|
1445
1729
|
const [codeRequested, setCodeRequested] = useState(false)
|
|
1446
1730
|
const [credentialConfigured, setCredentialConfigured] = useState(null)
|
|
@@ -1449,9 +1733,15 @@ window.__ModuleLoader__.load({
|
|
|
1449
1733
|
const [allowLocalCleanup, setAllowLocalCleanup] = useState(false)
|
|
1450
1734
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
1451
1735
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
1736
|
+
const [authorizedModels, setAuthorizedModels] = useState([])
|
|
1737
|
+
const [lastModelSync, setLastModelSync] = useState(null)
|
|
1452
1738
|
const [serviceCompatibility, setServiceCompatibility] = useState({ state: 'idle', message: '' })
|
|
1453
1739
|
const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
|
|
1454
1740
|
|
|
1741
|
+
useEffect(() => {
|
|
1742
|
+
onUpdateStateChange?.({ ...bundleUpdate, version: bundleRelease?.version || '' })
|
|
1743
|
+
}, [bundleUpdate, bundleRelease, onUpdateStateChange])
|
|
1744
|
+
|
|
1455
1745
|
const connectionErrorMessage = (result, status) => {
|
|
1456
1746
|
const message = result?.message || `请求失败(${status})`
|
|
1457
1747
|
if (!['LEGACY_DEVICE_REVOKE_REQUIRED', 'ANT_CONNECTION_CLEANUP_REQUIRED'].includes(result?.code)) return message
|
|
@@ -1556,7 +1846,7 @@ window.__ModuleLoader__.load({
|
|
|
1556
1846
|
return
|
|
1557
1847
|
}
|
|
1558
1848
|
setBusy(true)
|
|
1559
|
-
|
|
1849
|
+
setModelMessage('正在从 ANT 刷新获准模型…')
|
|
1560
1850
|
try {
|
|
1561
1851
|
await requireCompatibleService(serviceUrl, true)
|
|
1562
1852
|
const response = await fetch('/api/ant-teams/models', { method: 'POST' })
|
|
@@ -1566,12 +1856,14 @@ window.__ModuleLoader__.load({
|
|
|
1566
1856
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
1567
1857
|
}
|
|
1568
1858
|
const count = await applyAuthorizedModels(result.models, result.service_url)
|
|
1859
|
+
setAuthorizedModels(result.models)
|
|
1860
|
+
setLastModelSync(new Date())
|
|
1569
1861
|
setCredentialConfigured(true)
|
|
1570
1862
|
setNeedsReconnect(false)
|
|
1571
1863
|
setReauthenticate(false)
|
|
1572
|
-
|
|
1864
|
+
setModelMessage(`模型目录已刷新,共 ${count} 个获准模型。`)
|
|
1573
1865
|
} catch (error) {
|
|
1574
|
-
|
|
1866
|
+
setModelMessage(error instanceof Error ? error.message : '刷新模型失败,请稍后重试。')
|
|
1575
1867
|
} finally {
|
|
1576
1868
|
setBusy(false)
|
|
1577
1869
|
}
|
|
@@ -1912,6 +2204,8 @@ window.__ModuleLoader__.load({
|
|
|
1912
2204
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
1913
2205
|
credentialSaved = true
|
|
1914
2206
|
await applyAuthorizedModels(result.models, normalized)
|
|
2207
|
+
setAuthorizedModels(result.models)
|
|
2208
|
+
setLastModelSync(new Date())
|
|
1915
2209
|
setCode('')
|
|
1916
2210
|
onServiceUrlChange(normalized)
|
|
1917
2211
|
setCodeRequested(false)
|
|
@@ -1940,86 +2234,137 @@ window.__ModuleLoader__.load({
|
|
|
1940
2234
|
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
1941
2235
|
const updateGuideUrl = pinnedServiceUrl
|
|
1942
2236
|
? 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
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
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,
|
|
2237
|
+
const modelRows = authorizedModels.map((model) => h('div', { key: `${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
2238
|
+
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
|
|
2239
|
+
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2240
|
+
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, model.display_name || model.model_id),
|
|
2241
|
+
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
2242
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
2243
|
+
`${model.protocol === 'anthropic' ? 'Anthropic' : 'OpenAI'} 协议`),
|
|
2244
|
+
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, '已同步到 DSH')))
|
|
2245
|
+
const connectionPanel = h('section', { style: { ...panelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
2246
|
+
h('div', null,
|
|
2247
|
+
h('h3', { style: sectionTitleStyle }, 'ANT 团队账号'),
|
|
2248
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '邮箱验证建立这台 DSH 的成员凭证;断开后会清除此设备凭证和同步的模型设置。')),
|
|
2249
|
+
h('span', { role: 'status', style: mutedTextStyle }, credentialConfigured === true && !bindingReady
|
|
2250
|
+
? '本机有 ANT 凭证,但尚未确认它绑定的团队;请完成上方的绑定检查。'
|
|
2251
|
+
: credentialConfigured === true ? 'DSH 已安全保存此设备的 ANT 团队凭证。'
|
|
2252
|
+
: credentialConfigured === false ? '验证工作邮箱后即可连接团队服务。' : '正在检查 ANT 连接状态…'),
|
|
2253
|
+
h('div', { role: 'note', style: { display: 'grid', gap: 3, padding: '9px 11px', borderRadius: 8,
|
|
2254
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
2255
|
+
h('strong', { style: { fontSize: 13, fontWeight: 600 } }, '配置保存范围'),
|
|
2256
|
+
h('p', { style: mutedTextStyle }, '团队模型与能力授权由 ANT 管理;成员凭证、Gitea 令牌、本机缓存和任务记录保存在这台 DSH。')),
|
|
2257
|
+
serviceCompatibility.state !== 'idle' ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
2258
|
+
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color } },
|
|
2259
|
+
serviceCompatibility.message) : null,
|
|
2260
|
+
credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
2261
|
+
h('button', { type: 'button', disabled: busy || !bindingReady,
|
|
2262
|
+
onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
2263
|
+
h('button', { type: 'button', disabled: busy,
|
|
1994
2264
|
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
|
-
|
|
2265
|
+
style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
2266
|
+
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
2267
|
+
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
2268
|
+
h('input', { id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
|
|
2269
|
+
onChange: (event) => setEmail(event.target.value), style: inputStyle }),
|
|
2270
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
|
|
2271
|
+
showConnectForm && codeRequested ? h('form', { onSubmit: connect, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
2272
|
+
h('label', { htmlFor: 'ant-team-model-code', style: { fontSize: 13, fontWeight: 550 } }, '邮箱验证码'),
|
|
2273
|
+
h('input', { id: 'ant-team-model-code', type: 'text', inputMode: 'numeric', autoComplete: 'one-time-code',
|
|
2274
|
+
required: true, maxLength: 8, value: code, placeholder: '8 位验证码',
|
|
2275
|
+
onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle }),
|
|
2276
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
2277
|
+
credentialConfigured === true && showConnectForm ? h('button', { type: 'button', disabled: busy,
|
|
2278
|
+
onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
2279
|
+
style: secondaryButtonStyle }, '断开当前设备') : null,
|
|
2280
|
+
allowLocalCleanup ? h('section', { style: { ...panelStyle, padding: 14, gap: 9 } },
|
|
2281
|
+
h('p', { style: mutedTextStyle }, savedDeviceId()
|
|
2282
|
+
? `已保存的设备编号:${savedDeviceId()}。请交给原团队管理员核对。`
|
|
2283
|
+
: '本机无法确认旧设备编号。请让原团队管理员按成员查看设备清单,勿用新编号猜测。'),
|
|
2284
|
+
h('p', { style: mutedTextStyle }, '仅清理本机不会确认服务端令牌已撤销;请先取得管理员确认。'),
|
|
2285
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
2286
|
+
h('button', { type: 'button', disabled: busy, onClick: () => void clearOnlyLocal(), style: secondaryButtonStyle }, '仅清理本机'),
|
|
2287
|
+
h('button', { type: 'button', disabled: busy || !savedDeviceId(), onClick: async () => {
|
|
2288
|
+
try {
|
|
2289
|
+
const deviceId = savedDeviceId()
|
|
2290
|
+
if (!deviceId) throw new Error('device id unavailable')
|
|
2291
|
+
if (typeof window.navigator?.clipboard?.writeText !== 'function') throw new Error('clipboard unavailable')
|
|
2292
|
+
await window.navigator.clipboard.writeText(deviceId)
|
|
2293
|
+
setMessage('设备编号已复制,请交给原团队管理员。')
|
|
2294
|
+
} catch { setMessage(savedDeviceId() ? `请复制设备编号:${savedDeviceId()}` : '本机无法确认旧设备编号,请联系原团队管理员核查。') }
|
|
2295
|
+
}, style: secondaryButtonStyle }, '复制设备编号'))) : null,
|
|
2296
|
+
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null)
|
|
2297
|
+
|
|
2298
|
+
const modelPanel = h('section', { style: { ...panelStyle, display: activeTab === 'models' ? 'grid' : 'none' } },
|
|
2299
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
2300
|
+
h('div', null,
|
|
2301
|
+
h('h3', { style: sectionTitleStyle }, '团队模型'),
|
|
2302
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '每个获准模型单独列出;刷新后同步到 DSH 模型选择器。')),
|
|
2303
|
+
h('span', { style: { flexShrink: 0, padding: '4px 9px', borderRadius: 999,
|
|
2304
|
+
color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
2305
|
+
backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
|
|
2306
|
+
fontSize: 12,
|
|
2307
|
+
} }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
|
|
2308
|
+
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2309
|
+
modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
|
|
2310
|
+
h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
|
|
2311
|
+
: credentialConfigured === true ? '当前没有已获准模型,请刷新或联系团队管理员。'
|
|
2312
|
+
: '连接并验证工作邮箱后,这里会列出逐个获准模型。'),
|
|
2313
|
+
credentialConfigured !== true ? h('button', { type: 'button', onClick: () => onSelectTab('connection'),
|
|
2314
|
+
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
2315
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
2316
|
+
h('button', { type: 'button', disabled: busy || !bindingReady || credentialConfigured !== true,
|
|
2317
|
+
onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2318
|
+
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString('zh-CN')}`) : null),
|
|
2319
|
+
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
2320
|
+
|
|
2321
|
+
const updateStatusLabels = {
|
|
2322
|
+
available: '有新版', 'artifact-missing': '等待发布制品', revoked: '安装包已撤回',
|
|
2323
|
+
'migration-required': '需要迁移', unknown: '状态待确认', failed: '检查失败',
|
|
2324
|
+
installing: '正在安装', done: '需重启 DSH', current: '已是最新', checking: '正在检查', none: '尚未发布',
|
|
2325
|
+
}
|
|
2326
|
+
const updateStatusNeedsDetails = ['artifact-missing', 'revoked', 'migration-required', 'unknown', 'failed', 'installing', 'done']
|
|
2327
|
+
const updateVersionSummary = bundleUpdate.currentVersion && bundleRelease?.version
|
|
2328
|
+
? bundleUpdate.currentVersion === bundleRelease.version
|
|
2329
|
+
? `当前版本 ${bundleUpdate.currentVersion} · 已是最新`
|
|
2330
|
+
: `当前版本 ${bundleUpdate.currentVersion} · 最新版本 ${bundleRelease.version}`
|
|
2331
|
+
: bundleUpdate.currentVersion ? `当前版本 ${bundleUpdate.currentVersion}`
|
|
2332
|
+
: bundleRelease?.version ? `最新版本 ${bundleRelease.version}` : ''
|
|
2333
|
+
const showReleaseNotes = canCheckUpdate && bundleRelease?.version && bundleRelease.release_notes
|
|
2334
|
+
&& ['current', 'available', 'artifact-missing'].includes(bundleUpdate.state)
|
|
2335
|
+
const updatePanel = h('section', { style: { ...panelStyle, display: activeTab === 'updates' ? 'grid' : 'none' } },
|
|
2336
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
2337
|
+
h('div', null,
|
|
2338
|
+
h('h3', { style: sectionTitleStyle }, '关于 ANT'),
|
|
2339
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '查看版本、更新说明和兼容状态。')),
|
|
2340
|
+
h('span', { style: { ...mutedTextStyle, flexShrink: 0,
|
|
2341
|
+
color: bundleUpdate.state === 'revoked' || serviceCompatibility.state === 'incompatible'
|
|
2342
|
+
? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color } },
|
|
2343
|
+
updateStatusLabels[bundleUpdate.state] || '等待检查')),
|
|
2344
|
+
updateVersionSummary ? h('p', { style: { ...mutedTextStyle, color: 'var(--dsw-alias-label-primary)', fontWeight: 550 } }, updateVersionSummary) : null,
|
|
2345
|
+
showReleaseNotes ? h('section', { style: { display: 'grid', gap: 6, padding: 12, borderRadius: 8,
|
|
2346
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
2347
|
+
h('strong', { style: { fontSize: 14, lineHeight: 1.4 } }, '本版更新'),
|
|
2348
|
+
h('p', { style: mutedTextStyle }, bundleRelease.release_notes)) : null,
|
|
2349
|
+
['incompatible', 'unknown'].includes(serviceCompatibility.state) ? h('p', { role: 'status', style: {
|
|
2350
|
+
...mutedTextStyle,
|
|
2351
|
+
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)'
|
|
2352
|
+
: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))',
|
|
2353
|
+
} }, `服务端兼容性:${serviceCompatibility.message}`) : null,
|
|
2354
|
+
bundleUpdate.message && updateStatusNeedsDetails.includes(bundleUpdate.state) ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
2355
|
+
? { ...mutedTextStyle, color: 'var(--dsw-alias-state-error-primary)' } : mutedTextStyle }, bundleUpdate.message) : null,
|
|
2356
|
+
!canCheckUpdate && updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer',
|
|
2357
|
+
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开更新指引 ↗') : null,
|
|
2358
|
+
!canCheckUpdate && !updateGuideUrl ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|
|
2359
|
+
!canCheckUpdate ? h('button', { type: 'button', onClick: () => onSelectTab('connection'), style: secondaryButtonStyle },
|
|
2360
|
+
'前往团队连接') : null,
|
|
2361
|
+
canCheckUpdate && bundleUpdate.state === 'available'
|
|
2362
|
+
? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null,
|
|
2363
|
+
h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking',
|
|
2364
|
+
onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '正在检查…' : '检查更新'))
|
|
2365
|
+
|
|
2366
|
+
return h('div', { style: { display: 'grid', gap: 16, width: '100%' } },
|
|
2367
|
+
connectionPanel, modelPanel, updatePanel)
|
|
2023
2368
|
|
|
2024
2369
|
}
|
|
2025
2370
|
|