dsh-ant 0.5.12 → 0.6.1
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 +11 -11
- package/client.js +645 -476
- package/conversation.js +179 -57
- package/delegation.js +559 -46
- package/index.js +33 -5
- package/locale/de-de.json +6 -0
- package/locale/es-es.json +6 -0
- package/locale/ja-jp.json +6 -0
- package/locale/mail.json +37 -0
- package/locale/messages.json +20871 -0
- package/locale/pt-br.json +6 -0
- package/package.json +4 -3
package/client.js
CHANGED
|
@@ -4,7 +4,64 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} }
|
|
5
5
|
var exports = module.exports
|
|
6
6
|
const { createElement, useEffect, useRef, useState } = require('react')
|
|
7
|
-
const
|
|
7
|
+
const ANT_LOCALE_MESSAGES_PATH = '/api/ant-teams/locale-messages'
|
|
8
|
+
const ANT_LOCALE_IDS = ['es-ES', 'de-DE', 'ja-JP', 'pt-BR']
|
|
9
|
+
const LOCALIZED_PROPS = new Set(['aria-label', 'placeholder', 'title', 'alt'])
|
|
10
|
+
const RAW_LOCALE_TEXT = Symbol('ant.raw-locale-text')
|
|
11
|
+
let localeRuntime = null
|
|
12
|
+
let localeMetadata = {}
|
|
13
|
+
let localeTranslate = (key) => key
|
|
14
|
+
let localeMessagePatterns = []
|
|
15
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')
|
|
16
|
+
const compileLocalePattern = (source) => {
|
|
17
|
+
const names = []
|
|
18
|
+
const pattern = source.split(/(\{[a-zA-Z][a-zA-Z0-9_]*\})/gu).map((part) => {
|
|
19
|
+
const placeholder = /^\{([a-zA-Z][a-zA-Z0-9_]*)\}$/u.exec(part)
|
|
20
|
+
if (!placeholder) return escapeRegExp(part)
|
|
21
|
+
names.push(placeholder[1])
|
|
22
|
+
return '([\\s\\S]*?)'
|
|
23
|
+
}).join('')
|
|
24
|
+
return { source, names, regex: new RegExp(`^${pattern}$`, 'u') }
|
|
25
|
+
}
|
|
26
|
+
const translateLocaleText = (value, params = {}) => {
|
|
27
|
+
if (typeof value !== 'string') return value
|
|
28
|
+
const translated = localeTranslate(value, params)
|
|
29
|
+
if (translated !== value) return translated
|
|
30
|
+
for (const entry of localeMessagePatterns) {
|
|
31
|
+
const match = entry.regex.exec(value)
|
|
32
|
+
if (!match) continue
|
|
33
|
+
const params = Object.fromEntries(entry.names.map((name, index) => [name, match[index + 1]]))
|
|
34
|
+
return localeTranslate(entry.source, params)
|
|
35
|
+
}
|
|
36
|
+
return value
|
|
37
|
+
}
|
|
38
|
+
const rawLocaleText = (value) => ({ [RAW_LOCALE_TEXT]: true, value })
|
|
39
|
+
const formatLocale = () => localeMetadata[localeRuntime?.getLocale?.().active]?.formatLocale || 'en-US'
|
|
40
|
+
const formatLocaleDirection = () => localeMetadata[localeRuntime?.getLocale?.().active]?.direction || 'ltr'
|
|
41
|
+
const formatLocaleNumber = (value) => new Intl.NumberFormat(formatLocale()).format(value)
|
|
42
|
+
const formatLocaleDate = (value, options) => new Date(value).toLocaleString(formatLocale(), options)
|
|
43
|
+
const localizedChild = (value) => {
|
|
44
|
+
if (Array.isArray(value)) return value.map(localizedChild)
|
|
45
|
+
if (value?.[RAW_LOCALE_TEXT]) return value.value
|
|
46
|
+
return typeof value === 'string' ? translateLocaleText(value) : value
|
|
47
|
+
}
|
|
48
|
+
const h = (type, props, ...children) => {
|
|
49
|
+
const localizedProps = props && typeof props === 'object' ? { ...props } : props
|
|
50
|
+
if (localizedProps) {
|
|
51
|
+
for (const key of LOCALIZED_PROPS) {
|
|
52
|
+
if (typeof localizedProps[key] === 'string') localizedProps[key] = translateLocaleText(localizedProps[key])
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return createElement(type, localizedProps, ...children.map(localizedChild))
|
|
56
|
+
}
|
|
57
|
+
const confirmLocalized = (message) => window.confirm(translateLocaleText(message))
|
|
58
|
+
const promptLocalized = (message, ...args) => window.prompt(translateLocaleText(message), ...args)
|
|
59
|
+
const useLocaleRevision = () => {
|
|
60
|
+
const [, refresh] = useState(() => localeRuntime?.getLocale?.().revision || 0)
|
|
61
|
+
useEffect(() => localeRuntime?.subscribe?.(() => {
|
|
62
|
+
refresh(localeRuntime.getLocale().revision)
|
|
63
|
+
}), [])
|
|
64
|
+
}
|
|
8
65
|
const compactOpenaiProviderName = (name) => {
|
|
9
66
|
const value = typeof name === 'string' ? name.trim() : ''
|
|
10
67
|
if (!value) return value
|
|
@@ -36,7 +93,6 @@ window.__ModuleLoader__.load({
|
|
|
36
93
|
return ANT_SETTINGS_TABS.some(([id]) => id === saved) ? saved : 'connection'
|
|
37
94
|
} catch { return 'connection' }
|
|
38
95
|
}
|
|
39
|
-
const fileCountFormat = new Intl.NumberFormat('zh-CN')
|
|
40
96
|
const extensionCategories = [
|
|
41
97
|
['office', '办公文档'], ['development', '编程开发'], ['design', '设计与多媒体'],
|
|
42
98
|
['data', '数据分析'], ['collaboration', '协作沟通'], ['content', '写作与营销'],
|
|
@@ -116,9 +172,11 @@ window.__ModuleLoader__.load({
|
|
|
116
172
|
const item = curatedSkillIntroductions[skill.dsh_name]
|
|
117
173
|
return item && item.ref === skill.source_ref ? item.description : null
|
|
118
174
|
}
|
|
175
|
+
const genericSkillIntroduction = '这项技能尚未提供中文介绍。请查看英文原文,或联系企业管理员补充。'
|
|
119
176
|
const skillIntroduction = (skill) => hasChineseIntroduction(skill.description)
|
|
120
|
-
? skill.description : curatedSkillIntroduction(skill)
|
|
121
|
-
|
|
177
|
+
? skill.description : curatedSkillIntroduction(skill) || genericSkillIntroduction
|
|
178
|
+
const skillIntroductionDisplay = (skill, description) => description === genericSkillIntroduction
|
|
179
|
+
|| description === curatedSkillIntroduction(skill) ? description : rawLocaleText(description)
|
|
122
180
|
const skillEnglishSource = (skill) => {
|
|
123
181
|
let url
|
|
124
182
|
try { url = new URL(skill.source_url) } catch { return null }
|
|
@@ -142,6 +200,11 @@ window.__ModuleLoader__.load({
|
|
|
142
200
|
backgroundColor: 'var(--dsw-alias-state-business-tertiary)',
|
|
143
201
|
borderLeft: '3px solid var(--dsw-alias-state-business-primary)',
|
|
144
202
|
}
|
|
203
|
+
const warningNoticeStyle = {
|
|
204
|
+
display: 'grid', gap: 4, padding: '9px 11px', borderRadius: 8,
|
|
205
|
+
backgroundColor: 'var(--dsw-alias-state-warning-tertiary, var(--dsw-alias-bg-layer-2))',
|
|
206
|
+
borderLeft: '3px solid var(--dsw-alias-state-warning-primary, var(--dsw-alias-border-l2))',
|
|
207
|
+
}
|
|
145
208
|
const mutedTextStyle = { margin: 0, color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6 }
|
|
146
209
|
const feedbackNoticeStyle = { ...mutedTextStyle, position: 'sticky', top: 0, zIndex: 8,
|
|
147
210
|
padding: '9px 12px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
@@ -233,7 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
233
296
|
.replace(/(https?:\/\/)[^/\s?#@]+:[^@\s/]+@/giu, '$1[REDACTED]@')
|
|
234
297
|
.slice(0, 180)
|
|
235
298
|
}
|
|
236
|
-
const inject = ['slots', 'layout', 'pluginNavigation', 'remote', 'remote.credentials', 'remote.settings', 'remote.pluginManager']
|
|
299
|
+
const inject = ['slots', 'layout', 'pluginNavigation', 'remote', 'remote.credentials', 'remote.settings', 'remote.pluginManager', 'locale']
|
|
237
300
|
|
|
238
301
|
async function readLocalJsonResponse(response) {
|
|
239
302
|
if (response.status === 204) return null
|
|
@@ -336,6 +399,7 @@ window.__ModuleLoader__.load({
|
|
|
336
399
|
}
|
|
337
400
|
|
|
338
401
|
function TeamServiceConfig({ remote, openInstalledPlugin }) {
|
|
402
|
+
useLocaleRevision()
|
|
339
403
|
const [activeTab, setActiveTab] = useState(loadAntSettingsTab)
|
|
340
404
|
const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
|
|
341
405
|
const [draft, setDraft] = useState(serviceUrl)
|
|
@@ -362,7 +426,11 @@ window.__ModuleLoader__.load({
|
|
|
362
426
|
}
|
|
363
427
|
const updateNeedsAttention = ['available', 'artifact-missing', 'revoked', 'migration-required',
|
|
364
428
|
'unknown', 'failed', 'installing', 'done'].includes(updateSummary?.state)
|
|
365
|
-
||
|
|
429
|
+
|| updateSummary?.dshCompatibility?.state === 'incompatible'
|
|
430
|
+
const aboutNeedsAttention = updateNeedsAttention
|
|
431
|
+
|| ['unknown', 'untested'].includes(updateSummary?.dshCompatibility?.state)
|
|
432
|
+
const aboutAttentionIsCritical = updateSummary?.state === 'revoked'
|
|
433
|
+
|| updateSummary?.dshCompatibility?.state === 'incompatible'
|
|
366
434
|
useEffect(() => {
|
|
367
435
|
let disposed = false
|
|
368
436
|
const revision = bindingRevision.current
|
|
@@ -465,7 +533,7 @@ window.__ModuleLoader__.load({
|
|
|
465
533
|
binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
|
|
466
534
|
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
467
535
|
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)
|
|
468
|
-
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
536
|
+
return h('section', { dir: formatLocaleDirection(), style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
469
537
|
h('div', { style: { display: 'grid', gap: 4 } },
|
|
470
538
|
h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650, lineHeight: 1.35 } }, '团队与协作'),
|
|
471
539
|
h('p', { style: mutedTextStyle }, '管理团队连接、机器人聊天入口和本机任务授权。日常聊天保持自然;执行能力按你开放的范围核验。')),
|
|
@@ -498,14 +566,11 @@ window.__ModuleLoader__.load({
|
|
|
498
566
|
h('div', { style: { display: 'grid', gap: 3, flex: '1 1 300px' } },
|
|
499
567
|
h('strong', null, updateSummary.dshCompatibility?.state === 'incompatible'
|
|
500
568
|
? `当前 DSH ${updateSummary.dshCompatibility.runtime_version || ''} 与 ANT 不兼容`
|
|
501
|
-
: updateSummary.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
: updateSummary.state === '
|
|
505
|
-
|
|
506
|
-
: updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
|
|
507
|
-
: updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
|
|
508
|
-
h('span', { style: mutedTextStyle }, ['incompatible', 'unknown', 'untested'].includes(updateSummary.dshCompatibility?.state)
|
|
569
|
+
: updateSummary.state === 'available'
|
|
570
|
+
? `有新版 ANT:${updateSummary.currentVersion || '当前版本未知'} → ${updateSummary.version}`
|
|
571
|
+
: updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
|
|
572
|
+
: updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
|
|
573
|
+
h('span', { style: mutedTextStyle }, updateSummary.dshCompatibility?.state === 'incompatible'
|
|
509
574
|
? updateSummary.dshCompatibility.message
|
|
510
575
|
: updateSummary.state === 'available'
|
|
511
576
|
? '查看“关于”中的更新说明与兼容要求;安装后需重启 DSH。'
|
|
@@ -518,17 +583,24 @@ window.__ModuleLoader__.load({
|
|
|
518
583
|
h('nav', { role: 'tablist', 'aria-label': 'ANT 插件设置', style: {
|
|
519
584
|
display: 'flex', gap: 4, overflowX: 'auto', borderBottom: '1px solid var(--dsw-alias-border-l2)',
|
|
520
585
|
} },
|
|
521
|
-
...ANT_SETTINGS_TABS.map(([tab, label], index) =>
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
586
|
+
...ANT_SETTINGS_TABS.map(([tab, label], index) => {
|
|
587
|
+
const showBadge = tab === 'updates' && aboutNeedsAttention
|
|
588
|
+
return h('button', { key: tab, id: `ant-settings-tab-${tab}`,
|
|
589
|
+
type: 'button', role: 'tab', tabIndex: activeTab === tab ? 0 : -1,
|
|
590
|
+
'aria-controls': 'ant-settings-content', 'aria-selected': String(activeTab === tab),
|
|
591
|
+
onKeyDown: (event) => moveSettingsTabFocus(event, index), onClick: () => selectSettingsTab(tab),
|
|
592
|
+
style: { ...secondaryButtonStyle, minHeight: 38, padding: '7px 12px', whiteSpace: 'nowrap',
|
|
593
|
+
borderRadius: '8px 8px 0 0', color: activeTab === tab ? 'var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.color,
|
|
594
|
+
border: '0', borderBottom: activeTab === tab ? '2px solid var(--dsw-alias-state-business-primary)' : '2px solid transparent',
|
|
595
|
+
backgroundColor: 'transparent',
|
|
596
|
+
} },
|
|
597
|
+
label,
|
|
598
|
+
showBadge ? h('span', { role: 'img', 'aria-label': '需处理', style: {
|
|
599
|
+
display: 'inline-block', width: 8, height: 8, marginInlineStart: 5, borderRadius: '50%',
|
|
600
|
+
backgroundColor: aboutAttentionIsCritical ? 'var(--dsw-alias-state-error-primary)'
|
|
601
|
+
: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))',
|
|
602
|
+
} }) : null)
|
|
603
|
+
}))),
|
|
532
604
|
h('div', { id: 'ant-settings-content', role: 'tabpanel', 'aria-labelledby': `ant-settings-tab-${activeTab}`,
|
|
533
605
|
style: { display: 'grid', gap: activeTab === 'connection' ? 12 : 0 } },
|
|
534
606
|
teamServicePanel,
|
|
@@ -550,15 +622,15 @@ window.__ModuleLoader__.load({
|
|
|
550
622
|
const [defaults, setDefaults] = useState(null)
|
|
551
623
|
const [localConcurrency, setLocalConcurrency] = useState(null)
|
|
552
624
|
const [repositoryCacheLimitGib, setRepositoryCacheLimitGib] = useState(10)
|
|
625
|
+
const [ownerChallengeCode, setOwnerChallengeCode] = useState('')
|
|
553
626
|
const [botId, setBotId] = useState('')
|
|
554
627
|
const [conversationKind, setConversationKind] = useState('dm')
|
|
555
628
|
const [groupId, setGroupId] = useState('')
|
|
556
629
|
const [ruleDescription, setRuleDescription] = useState('')
|
|
630
|
+
const [triggerPhrases, setTriggerPhrases] = useState('')
|
|
557
631
|
const recognizedBaselineRef = useRef(null)
|
|
558
632
|
const recognizedDescriptionRef = useRef(null)
|
|
559
|
-
const autoAnalyzeDescriptionRef = useRef(null)
|
|
560
633
|
const recognitionRequestRef = useRef(0)
|
|
561
|
-
const recognitionTimerRef = useRef(null)
|
|
562
634
|
const [recognitionBusy, setRecognitionBusy] = useState(false)
|
|
563
635
|
const [recognitionMessage, setRecognitionMessage] = useState('')
|
|
564
636
|
const [missingRequired, setMissingRequired] = useState([])
|
|
@@ -570,7 +642,6 @@ window.__ModuleLoader__.load({
|
|
|
570
642
|
const [runner, setRunner] = useState('dsh')
|
|
571
643
|
const [reviewers, setReviewers] = useState(['dsh'])
|
|
572
644
|
const [parameters, setParameters] = useState([])
|
|
573
|
-
const [parallelAgentsOpen, setParallelAgentsOpen] = useState(false)
|
|
574
645
|
const [giteaToken, setGiteaToken] = useState('')
|
|
575
646
|
const [tokenConfigured, setTokenConfigured] = useState(false)
|
|
576
647
|
const [senderScope, setSenderScope] = useState('any')
|
|
@@ -584,6 +655,7 @@ window.__ModuleLoader__.load({
|
|
|
584
655
|
const [loadFailures, setLoadFailures] = useState([])
|
|
585
656
|
const [createOpen, setCreateOpen] = useState(false)
|
|
586
657
|
const [message, setMessage] = useState('')
|
|
658
|
+
const [submissionError, setSubmissionError] = useState('')
|
|
587
659
|
const [draftNotice, setDraftNotice] = useState('')
|
|
588
660
|
const load = async () => {
|
|
589
661
|
setLoading(true)
|
|
@@ -630,6 +702,7 @@ window.__ModuleLoader__.load({
|
|
|
630
702
|
const taskPrOrigins = [...new Set(parameters.filter((item) => item.kind === 'gitea_pr')
|
|
631
703
|
.map((item) => normalizeServiceUrl(item.gitea_origin)).filter(Boolean))]
|
|
632
704
|
const selectedEnrollmentBound = selectedEnrollment?.device_bound_to_current === true
|
|
705
|
+
const ownerApprovalBound = selectedEnrollment?.owner_bound === true
|
|
633
706
|
const dmChatSettingAvailable = typeof selectedEnrollment?.dm_chat_enabled === 'boolean'
|
|
634
707
|
const dmChatEnabled = selectedEnrollment?.dm_chat_enabled !== false
|
|
635
708
|
const dmChatStatus = !selectedBot ? '先选择机器人'
|
|
@@ -641,7 +714,7 @@ window.__ModuleLoader__.load({
|
|
|
641
714
|
: !selectedEnrollmentBound ? '请先把机器人绑定到这台 DSH,再修改私聊设置。'
|
|
642
715
|
: !dmChatSettingAvailable ? '当前团队服务端尚未提供私聊开关,请先升级服务端。'
|
|
643
716
|
: dmChatEnabled
|
|
644
|
-
|
|
717
|
+
? '允许此机器人接收私聊。匹配任务卡的请求按预授权执行;没有匹配卡片的明确任务可转给已绑定主人逐次审批。'
|
|
645
718
|
: '新私聊消息不会交给 DSH;已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
|
|
646
719
|
const availableGroups = groups.filter((group) => group.bot_fingerprint === selectedBot?.fingerprint)
|
|
647
720
|
const groupLabel = (group) => {
|
|
@@ -671,9 +744,8 @@ window.__ModuleLoader__.load({
|
|
|
671
744
|
: !selectedBot ? '已有协作机器人;请选择一台,查看已开放的能力和聊天范围。'
|
|
672
745
|
: authorizationLoadError ? `“${selectedBot.name}”已启用协作;已开放任务暂未读取,请刷新状态。`
|
|
673
746
|
: !dmChatEnabled ? `“${selectedBot.name}”已启用协作;私聊已关闭,群聊仍按各自设置工作。`
|
|
674
|
-
: selectedTaskCount === 0 ? `“${selectedBot.name}
|
|
675
|
-
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount}
|
|
676
|
-
const nativeReviewerSelected = reviewers.some((item) => ['codex', 'claude-code'].includes(item))
|
|
747
|
+
: selectedTaskCount === 0 ? `“${selectedBot.name}”已启用协作;绑定审批私聊后,未配置的明确任务会先问你。常用任务可建卡直接放行。`
|
|
748
|
+
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount} 项预授权能力。其他明确任务可交给主人逐次审批。`
|
|
677
749
|
const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
|
|
678
750
|
const hasSelectedPrRule = activeCards.some((card) => card.bot_fingerprint === selectedBot?.fingerprint
|
|
679
751
|
&& Array.isArray(card.parameters) && card.parameters.some((rule) => rule.kind === 'gitea_pr'))
|
|
@@ -683,23 +755,34 @@ window.__ModuleLoader__.load({
|
|
|
683
755
|
const codexAvailable = executors.some((item) => item.id === 'codex' && item.available)
|
|
684
756
|
const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
|
|
685
757
|
const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
|
|
686
|
-
const
|
|
758
|
+
const executorUnavailableReason = executors.find((item) => item.reason)?.reason || null
|
|
687
759
|
const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
|
|
688
760
|
const cardReviewerSummary = (card) => {
|
|
689
761
|
const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
|
|
690
|
-
const unavailable = selected.filter((runner) => runner
|
|
691
|
-
if (card.runner
|
|
762
|
+
const unavailable = selected.filter((runner) => !executors.some((item) => item.id === runner && item.available))
|
|
763
|
+
if (unavailable.includes(card.runner)) return translateLocaleText('{executor} 默认执行方暂不可用',
|
|
764
|
+
{ executor: reviewerLabel(card.runner) })
|
|
765
|
+
const additional = selected.filter((runner) => runner !== card.runner)
|
|
766
|
+
const selectedSummary = additional.length
|
|
767
|
+
? translateLocaleText('默认 {executor};交叉审查:{reviewers}',
|
|
768
|
+
{ executor: reviewerLabel(card.runner), reviewers: additional.map(reviewerLabel).join('、') })
|
|
769
|
+
: translateLocaleText('{executor} 默认执行', { executor: reviewerLabel(card.runner) })
|
|
692
770
|
return unavailable.length
|
|
693
|
-
?
|
|
694
|
-
:
|
|
771
|
+
? `${selectedSummary};${unavailable.map(reviewerLabel).join('、')} 暂不可用`
|
|
772
|
+
: selectedSummary
|
|
695
773
|
}
|
|
696
774
|
const requesterSummary = senderScope === 'allowlist'
|
|
697
|
-
?
|
|
698
|
-
|
|
699
|
-
|
|
775
|
+
? translateLocaleText('仅指定成员:{member_ids}',
|
|
776
|
+
{ member_ids: senderIds.trim() || translateLocaleText('尚未填写成员 ID') })
|
|
777
|
+
: translateLocaleText(conversationKind === 'group' ? '群内所有成员' : '任何能私聊此机器人的人')
|
|
778
|
+
const resultRouteSummary = translateLocaleText(conversationKind === 'group'
|
|
779
|
+
? '回复到原群(群内可见)' : '回复到发起任务的私聊')
|
|
700
780
|
const ruleSummaryCurrent = recognizedDescriptionRef.current === ruleDescription.trim()
|
|
701
|
-
const ruleSummaryPending =
|
|
702
|
-
const
|
|
781
|
+
const ruleSummaryPending = recognitionBusy
|
|
782
|
+
const ruleSummaryDirty = Boolean(ruleDescription.trim() && !ruleSummaryCurrent)
|
|
783
|
+
const ruleTriggerSummary = triggerPhrases.split('\n').map((item) => item.trim()).filter(Boolean)
|
|
784
|
+
const ruleSummaryReady = Boolean(ruleSummaryCurrent
|
|
785
|
+
&& (title || intent || ruleTriggerSummary.length || parameters.length || taskPrompt))
|
|
703
786
|
const missingHarnessInputs = [...new Set([
|
|
704
787
|
...missingRequired,
|
|
705
788
|
...(parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
@@ -732,39 +815,35 @@ window.__ModuleLoader__.load({
|
|
|
732
815
|
? `请求时还需提供:${parameters.map(parameterSummary).join(';')}`
|
|
733
816
|
: '无需额外参数,按请求中的内容处理'
|
|
734
817
|
const ruleSummaryNarrative = ruleSummaryReady
|
|
735
|
-
? `${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
|
|
818
|
+
? `${ruleTriggerSummary.length ? `消息以“${ruleTriggerSummary.join('”或“')}”开头时,` : ''}${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
|
|
736
819
|
: '识别后会用一段摘要呈现系统理解的任务。'
|
|
737
|
-
const executorSummary = runner === 'codex' ? 'DSH Codex 子 Agent'
|
|
820
|
+
const executorSummary = translateLocaleText(runner === 'codex' ? 'DSH Codex 子 Agent'
|
|
738
821
|
: runner === 'claude-code' ? 'DSH Claude Code 子 Agent' : 'DSH · 当前授权模型'
|
|
739
|
-
|
|
822
|
+
)
|
|
740
823
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
741
824
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
742
825
|
: loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 3
|
|
743
|
-
? '
|
|
826
|
+
? '团队服务端尚未支持任务触发规则与运行时授权核验;请先升级 ANT 服务端。'
|
|
744
827
|
: !ruleDescription.trim() ? '请先用自然语言描述要做的任务。'
|
|
745
828
|
: recognizedDescriptionRef.current !== ruleDescription.trim()
|
|
746
|
-
? 'AI
|
|
829
|
+
? recognitionBusy ? 'AI 正在检查当前描述;分析完成后再开放。' : '描述已修改,请点击“立即分析”更新任务理解后再开放。'
|
|
747
830
|
: !title.trim() || !intent.trim() || !taskPrompt.trim() ? '等待 AI 生成任务理解和处理要求。'
|
|
831
|
+
: !ruleTriggerSummary.length ? '任务没有明确触发开头;请在描述中写清楚请求消息以什么短语开头,再重新分析。'
|
|
748
832
|
: missingHarnessInputs.length ? `任务范围还有必需信息未明确:${missingHarnessInputs.join('、')}。请补充描述。`
|
|
749
833
|
: conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
|
|
750
834
|
: conversationKind === 'group' && !groupId ? '请在“请求范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
|
|
751
835
|
: senderScope === 'allowlist' && !senderIds.trim() ? '请填写可请求成员的 IM ID。'
|
|
752
|
-
|
|
753
|
-
?
|
|
836
|
+
: reviewers.some((item) => !executors.some((executor) => executor.id === item && executor.available))
|
|
837
|
+
? `这台 DSH 尚未启用所选执行器。${executorUnavailableReason || '请先启用对应 DSH 子 Agent 提供方。'}`
|
|
754
838
|
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
|
|
755
839
|
: parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
756
840
|
&& (typeof rule.gitea_origin !== 'string' || !normalizeServiceUrl(rule.gitea_origin) || !rule.repository))
|
|
757
841
|
? 'PR 来源或仓库尚未从自然语言描述中明确识别,请补充完整的 PR 来源链接或仓库。'
|
|
758
842
|
: parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
759
843
|
&& selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)
|
|
760
|
-
?
|
|
844
|
+
? `${translateLocaleText('任务描述中的 Gitea 来源必须与此机器人的连接器主机一致({origin});为保护令牌,ANT 不会向其他主机发送凭据。', { origin: selectedGiteaOrigin })} ${translateLocaleText('请在描述中改用该主机下的 PR 链接并重新分析。')}`
|
|
761
845
|
: taskPrOrigins.length > 1
|
|
762
846
|
? '同一任务中的 PR 资源必须来自同一个 Gitea 主机。'
|
|
763
|
-
: nativeReviewerSelected && (parameters.filter((rule) => rule.kind === 'gitea_pr').length !== 1
|
|
764
|
-
|| parameters.some((rule) => !['gitea_pr', 'git_sha'].includes(rule.kind)))
|
|
765
|
-
? 'Codex 和 Claude Code 当前只支持一个 Gitea PR 资源连接器;任务具体做审查、总结或解释仍由自然语言描述决定。'
|
|
766
|
-
: nativeReviewerSelected && !parameters.find((rule) => rule.kind === 'gitea_pr')?.required
|
|
767
|
-
? '使用本机 Provider 时,Gitea PR 资源必须是必填项。'
|
|
768
847
|
: reviewers.includes('dsh') && !dshAvailable
|
|
769
848
|
? '这台 DSH 尚未准备好任务执行器。'
|
|
770
849
|
: ''
|
|
@@ -778,23 +857,22 @@ window.__ModuleLoader__.load({
|
|
|
778
857
|
if (!next.includes(runner)) setRunner(next[0])
|
|
779
858
|
}
|
|
780
859
|
const selectRunner = (id) => {
|
|
860
|
+
if (!reviewers.includes(id)) return
|
|
781
861
|
setRunner(id)
|
|
782
|
-
setReviewers((current) => current.length > 1
|
|
783
|
-
? [...new Set([...current, id])]
|
|
784
|
-
: [id])
|
|
785
862
|
}
|
|
786
863
|
const startNewCard = (context = {}) => {
|
|
787
864
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
865
|
+
setSubmissionError('')
|
|
788
866
|
setDraftNotice('')
|
|
789
867
|
setRecognitionMessage('')
|
|
790
868
|
recognizedBaselineRef.current = null
|
|
791
869
|
recognizedDescriptionRef.current = null
|
|
792
|
-
autoAnalyzeDescriptionRef.current = null
|
|
793
870
|
recognitionRequestRef.current += 1
|
|
794
871
|
setRuleDescription('')
|
|
872
|
+
setTriggerPhrases('')
|
|
795
873
|
setMissingRequired([]); setSuggestions([]); setRuleExamples([])
|
|
796
874
|
setTitle(''); setIntent(''); setTaskPrompt(''); setRunner('dsh')
|
|
797
|
-
setReviewers(['dsh'])
|
|
875
|
+
setReviewers(['dsh'])
|
|
798
876
|
setParameters([]); setConversationKind(context.conversationKind || 'dm')
|
|
799
877
|
setGroupId(context.groupId || '')
|
|
800
878
|
setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
|
|
@@ -817,29 +895,32 @@ window.__ModuleLoader__.load({
|
|
|
817
895
|
else if (headIndex < 0 && rules.length < 12) rules.push(headRule)
|
|
818
896
|
}
|
|
819
897
|
return { title: title || '', intent: card.intent || '', task_prompt: card.task_prompt || '',
|
|
898
|
+
trigger_phrases: Array.isArray(card.trigger_phrases) ? card.trigger_phrases.map((item) => item.trim()).filter(Boolean) : [],
|
|
820
899
|
parameters: rules, examples: Array.isArray(card.examples) ? card.examples : [],
|
|
821
900
|
missing_required: [], suggestions: [] }
|
|
822
901
|
}
|
|
823
902
|
const editCard = (card, copy = false) => {
|
|
903
|
+
setSubmissionError('')
|
|
824
904
|
setBotId(card.bot_id)
|
|
825
905
|
setRecognitionMessage('')
|
|
826
|
-
autoAnalyzeDescriptionRef.current = null
|
|
827
906
|
recognitionRequestRef.current += 1
|
|
828
907
|
setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
|
|
829
908
|
const taskDraft = taskRuleDraftFromCard(card, copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
|
|
830
909
|
recognizedBaselineRef.current = taskDraft
|
|
831
910
|
const existingDescription = card.rule_description || ''
|
|
832
911
|
recognizedDescriptionRef.current = existingDescription.trim() || null
|
|
833
|
-
setDraftNotice(
|
|
834
|
-
|
|
912
|
+
setDraftNotice(!taskDraft.trigger_phrases.length
|
|
913
|
+
? '这项旧任务没有明确触发短语;新版不会按它启动任务。请重新分析描述并检查生成的触发开头。'
|
|
914
|
+
: existingDescription.trim() ? ''
|
|
915
|
+
: '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
|
|
835
916
|
setRuleDescription(existingDescription)
|
|
917
|
+
setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
|
|
836
918
|
setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
|
|
837
919
|
setTitle(taskDraft.title)
|
|
838
920
|
if (copy) setDays(7)
|
|
839
921
|
setIntent(taskDraft.intent)
|
|
840
922
|
setTaskPrompt(taskDraft.task_prompt); setRunner(card.runner)
|
|
841
923
|
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
|
|
842
|
-
setParallelAgentsOpen(false)
|
|
843
924
|
setParameters(taskDraft.parameters)
|
|
844
925
|
setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
|
|
845
926
|
setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
|
|
@@ -848,6 +929,7 @@ window.__ModuleLoader__.load({
|
|
|
848
929
|
const applyRuleDraft = (draft) => {
|
|
849
930
|
setTitle(draft.title)
|
|
850
931
|
setIntent(draft.intent)
|
|
932
|
+
setTriggerPhrases(draft.trigger_phrases.join('\n'))
|
|
851
933
|
setTaskPrompt(draft.task_prompt)
|
|
852
934
|
setParameters(draft.parameters)
|
|
853
935
|
setMissingRequired(draft.missing_required || [])
|
|
@@ -860,7 +942,6 @@ window.__ModuleLoader__.load({
|
|
|
860
942
|
setRecognitionMessage('先描述这项任务何时触发、要做什么以及涉及哪些资料。')
|
|
861
943
|
return
|
|
862
944
|
}
|
|
863
|
-
if (recognitionTimerRef.current) clearTimeout(recognitionTimerRef.current)
|
|
864
945
|
const requestNumber = ++recognitionRequestRef.current
|
|
865
946
|
setRecognitionBusy(true); setMessage(''); setRecognitionMessage('AI 正在分析必需范围、可选条件和执行方式…')
|
|
866
947
|
try {
|
|
@@ -873,6 +954,7 @@ window.__ModuleLoader__.load({
|
|
|
873
954
|
const candidate = {
|
|
874
955
|
title: typeof result.title === 'string' ? result.title : '',
|
|
875
956
|
intent: typeof result.intent === 'string' ? result.intent : '',
|
|
957
|
+
trigger_phrases: Array.isArray(result.trigger_phrases) ? result.trigger_phrases : [],
|
|
876
958
|
task_prompt: typeof result.task_prompt === 'string' ? result.task_prompt : '',
|
|
877
959
|
missing_required: Array.isArray(result.missing_required) ? result.missing_required : [],
|
|
878
960
|
suggestions: Array.isArray(result.suggestions) ? result.suggestions : [],
|
|
@@ -905,8 +987,8 @@ window.__ModuleLoader__.load({
|
|
|
905
987
|
}
|
|
906
988
|
}
|
|
907
989
|
if (requestNumber !== recognitionRequestRef.current || requestedDescription !== ruleDescription.trim()) return
|
|
908
|
-
if (!candidate.title || !candidate.intent || !candidate.task_prompt) {
|
|
909
|
-
throw new Error('AI
|
|
990
|
+
if (!candidate.title || !candidate.intent || !candidate.task_prompt || !candidate.trigger_phrases.length) {
|
|
991
|
+
throw new Error('AI 返回的任务理解或触发开头不完整。请补充描述后重试。')
|
|
910
992
|
}
|
|
911
993
|
const previous = recognizedBaselineRef.current
|
|
912
994
|
applyRuleDraft(candidate)
|
|
@@ -921,14 +1003,6 @@ window.__ModuleLoader__.load({
|
|
|
921
1003
|
if (requestNumber === recognitionRequestRef.current) setRecognitionBusy(false)
|
|
922
1004
|
}
|
|
923
1005
|
}
|
|
924
|
-
useEffect(() => {
|
|
925
|
-
if (autoAnalyzeDescriptionRef.current !== ruleDescription || !ruleDescription.trim()) return undefined
|
|
926
|
-
const description = ruleDescription
|
|
927
|
-
recognitionTimerRef.current = setTimeout(() => {
|
|
928
|
-
if (autoAnalyzeDescriptionRef.current === description) void identifyRule(description)
|
|
929
|
-
}, 900)
|
|
930
|
-
return () => clearTimeout(recognitionTimerRef.current)
|
|
931
|
-
}, [ruleDescription])
|
|
932
1006
|
const saveGiteaToken = async () => {
|
|
933
1007
|
if (!giteaToken.trim()) return
|
|
934
1008
|
setBusy(true)
|
|
@@ -953,7 +1027,22 @@ window.__ModuleLoader__.load({
|
|
|
953
1027
|
const result = await readLocalJsonResponse(response)
|
|
954
1028
|
if (!response.ok) throw new Error(result.message || '协作机器人启用失败。')
|
|
955
1029
|
await load()
|
|
956
|
-
setMessage('
|
|
1030
|
+
setMessage('协作机器人已启用。任务卡可预授权固定任务;绑定审批私聊后,没有匹配卡片的明确任务会逐次询问主人。')
|
|
1031
|
+
} catch (error) { setMessage(error.message) }
|
|
1032
|
+
finally { setBusy(false) }
|
|
1033
|
+
}
|
|
1034
|
+
const requestOwnerApprovalBinding = async () => {
|
|
1035
|
+
if (!selectedBot || !selectedEnrollmentBound) return
|
|
1036
|
+
setBusy(true); setOwnerChallengeCode('')
|
|
1037
|
+
try {
|
|
1038
|
+
const response = await fetch('/api/ant-teams/delegation/owner-challenge', {
|
|
1039
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
1040
|
+
body: JSON.stringify({ bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint }),
|
|
1041
|
+
})
|
|
1042
|
+
const result = await readLocalJsonResponse(response)
|
|
1043
|
+
if (!response.ok || typeof result.code !== 'string') throw new Error(result.message || '审批私聊绑定码获取失败。')
|
|
1044
|
+
setOwnerChallengeCode(result.code)
|
|
1045
|
+
setMessage(`请在 5 分钟内私聊此机器人,发送“绑定审批身份 ${result.code}”。绑定成功后,它才能把未配置任务的审批请求发给你。`)
|
|
957
1046
|
} catch (error) { setMessage(error.message) }
|
|
958
1047
|
finally { setBusy(false) }
|
|
959
1048
|
}
|
|
@@ -961,7 +1050,7 @@ window.__ModuleLoader__.load({
|
|
|
961
1050
|
if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
|
|
962
1051
|
const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
963
1052
|
&& card.conversation_kind === 'group' && card.conversation_id === targetGroupId)
|
|
964
|
-
if (!
|
|
1053
|
+
if (!confirmLocalized(enabled
|
|
965
1054
|
? targetHasTasks
|
|
966
1055
|
? '这个群当前因任务授权可以聊天。要在任务到期后仍允许普通聊天吗?聊天会使用团队模型额度。'
|
|
967
1056
|
: '允许这个群与机器人自然聊天?群成员仍不能执行未开放的能力,聊天会使用团队模型额度。'
|
|
@@ -984,7 +1073,7 @@ window.__ModuleLoader__.load({
|
|
|
984
1073
|
}
|
|
985
1074
|
const saveDmChat = async (enabled) => {
|
|
986
1075
|
if (!selectedBot || !selectedEnrollmentBound || !dmChatSettingAvailable) return
|
|
987
|
-
if (!
|
|
1076
|
+
if (!confirmLocalized(enabled
|
|
988
1077
|
? '开启后,此机器人会在私聊中正常对话;私聊任务仍逐项按已开放规则核验。'
|
|
989
1078
|
: '关闭后,新私聊消息不会进入 DSH,包括普通聊天、任务请求和状态查询。关闭前已提交的任务不会被撤销,仍按原私聊回复。已开放的私聊能力不会删除;重新开启后,未过期的能力会恢复触发。群聊设置不受影响。')) return
|
|
990
1079
|
setBusy(true)
|
|
@@ -1087,20 +1176,27 @@ window.__ModuleLoader__.load({
|
|
|
1087
1176
|
const createCard = async (event) => {
|
|
1088
1177
|
event.preventDefault()
|
|
1089
1178
|
setDraftNotice('')
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1179
|
+
setSubmissionError('')
|
|
1180
|
+
const reject = (reason) => {
|
|
1181
|
+
const localizedReason = translateLocaleText(reason)
|
|
1182
|
+
setSubmissionError(localizedReason); setMessage(localizedReason)
|
|
1093
1183
|
}
|
|
1184
|
+
if (!selectedBot || !botConnected || !selectedEnrollmentBound) { reject('请先在这台 DSH 绑定选中的协作机器人。'); return }
|
|
1094
1185
|
if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
|
|
1095
|
-
|
|
1186
|
+
reject('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
|
|
1096
1187
|
}
|
|
1097
1188
|
if (!ruleDescription.trim() || !title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
1098
|
-
|
|
1189
|
+
reject('请填写任务描述并点击“立即分析”,检查摘要后再开放。'); return
|
|
1099
1190
|
}
|
|
1100
1191
|
const bytes = (value) => new TextEncoder().encode(value).length
|
|
1101
1192
|
if (bytes(ruleDescription.trim()) > 4000 || bytes(title.trim()) > 100
|
|
1102
1193
|
|| bytes(intent.trim()) > 1000 || bytes(taskPrompt.trim()) > 4000) {
|
|
1103
|
-
|
|
1194
|
+
reject('任务描述或识别摘要过长,请缩短描述并重新识别。'); return
|
|
1195
|
+
}
|
|
1196
|
+
const triggerLines = triggerPhrases.split('\n').map((item) => item.normalize('NFKC').trim()).filter(Boolean)
|
|
1197
|
+
if (!triggerLines.length || triggerLines.length > 8 || triggerLines.some((item) => bytes(item) > 120)
|
|
1198
|
+
|| new Set(triggerLines.map((item) => item.toLowerCase())).size !== triggerLines.length) {
|
|
1199
|
+
reject('任务没有明确、唯一的触发开头;请在描述中写清楚消息以什么短语开头,再重新分析。'); return
|
|
1104
1200
|
}
|
|
1105
1201
|
const normalized = parameters.map((rule) => ({ name: rule.name.trim(), kind: rule.kind,
|
|
1106
1202
|
description: rule.description.trim() || null,
|
|
@@ -1112,21 +1208,21 @@ window.__ModuleLoader__.load({
|
|
|
1112
1208
|
? (typeof rule.gitea_origin === 'string' ? normalizeServiceUrl(rule.gitea_origin) : null) : null,
|
|
1113
1209
|
repository: rule.kind === 'gitea_pr' ? rule.repository.trim().toLowerCase() : null }))
|
|
1114
1210
|
if (normalized.some((rule) => !/^[A-Za-z][A-Za-z0-9_]{0,31}$/u.test(rule.name))) {
|
|
1115
|
-
|
|
1211
|
+
reject('没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
|
|
1116
1212
|
}
|
|
1117
1213
|
if (new Set(normalized.map((rule) => rule.name)).size !== normalized.length) {
|
|
1118
|
-
|
|
1214
|
+
reject('请求信息识别有重复项。请修改任务描述并重新识别。'); return
|
|
1119
1215
|
}
|
|
1120
1216
|
if (normalized.some((rule) => rule.name === 'head_sha' && rule.kind !== 'git_sha')) {
|
|
1121
|
-
|
|
1217
|
+
reject('head_sha 是 PR 提交版本的保留字段;请在自然语言描述中使用 PR Head SHA。'); return
|
|
1122
1218
|
}
|
|
1123
1219
|
if (normalized.some((rule) => rule.description && bytes(rule.description) > 100)) {
|
|
1124
|
-
|
|
1220
|
+
reject('请求信息说明过长。请缩短任务描述并重新识别。'); return
|
|
1125
1221
|
}
|
|
1126
1222
|
if (normalized.some((rule) => rule.kind === 'choice' && (!rule.choices?.length || rule.choices.length > 32
|
|
1127
1223
|
|| rule.choices.some((choice) => bytes(choice) > 100)
|
|
1128
1224
|
|| new Set(rule.choices).size !== rule.choices.length))) {
|
|
1129
|
-
|
|
1225
|
+
reject('描述中的可选范围不清楚或重复。请修改描述并重新识别。'); return
|
|
1130
1226
|
}
|
|
1131
1227
|
if (normalized.some((rule) => (rule.min != null && !Number.isInteger(rule.min))
|
|
1132
1228
|
|| (rule.max != null && !Number.isInteger(rule.max))
|
|
@@ -1134,19 +1230,19 @@ window.__ModuleLoader__.load({
|
|
|
1134
1230
|
|| (rule.kind === 'text' && rule.max != null && (rule.max < 1 || rule.max > 4096))
|
|
1135
1231
|
|| (rule.kind === 'gitea_pr' && ((rule.min != null && rule.min < 1)
|
|
1136
1232
|
|| (rule.max != null && rule.max > 1_000_000_000))))) {
|
|
1137
|
-
|
|
1233
|
+
reject('描述中的请求范围无效。请明确范围或提供完整 PR 链接,再重新识别。'); return
|
|
1138
1234
|
}
|
|
1139
1235
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && (!rule.gitea_origin || !rule.repository))) {
|
|
1140
|
-
|
|
1236
|
+
reject('没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
|
|
1141
1237
|
}
|
|
1142
1238
|
if (new Set(normalized.filter((rule) => rule.kind === 'gitea_pr')
|
|
1143
1239
|
.map((rule) => normalizeServiceUrl(rule.gitea_origin)).filter(Boolean)).size > 1) {
|
|
1144
|
-
|
|
1240
|
+
reject('同一任务中的 PR 资源必须来自同一个 Gitea 主机。')
|
|
1145
1241
|
return
|
|
1146
1242
|
}
|
|
1147
1243
|
if (normalized.some((rule) => rule.kind === 'gitea_pr'
|
|
1148
1244
|
&& selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)) {
|
|
1149
|
-
|
|
1245
|
+
reject(`${translateLocaleText('任务描述中的 Gitea 来源必须与此机器人的连接器主机一致({origin});为保护令牌,ANT 不会向其他主机发送凭据。', { origin: selectedGiteaOrigin })} ${translateLocaleText('请在描述中改用该主机下的 PR 链接并重新分析。')}`)
|
|
1150
1246
|
return
|
|
1151
1247
|
}
|
|
1152
1248
|
const validRepository = (value) => {
|
|
@@ -1155,38 +1251,41 @@ window.__ModuleLoader__.load({
|
|
|
1155
1251
|
&& /^[A-Za-z0-9_.-]{1,100}$/u.test(part))
|
|
1156
1252
|
}
|
|
1157
1253
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && !validRepository(rule.repository))) {
|
|
1158
|
-
|
|
1254
|
+
reject('仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
|
|
1159
1255
|
}
|
|
1160
1256
|
if (!reviewers.length || !reviewers.includes(runner)) {
|
|
1161
|
-
|
|
1257
|
+
reject('请至少选择一位任务执行 Agent,并设置普通请求的默认执行方。'); return
|
|
1162
1258
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const resultDestination = conversationKind === 'group' ? '原群(群内可见)' : '发起任务的私聊'
|
|
1259
|
+
const scope = conversationKind === 'group'
|
|
1260
|
+
? translateLocaleText('群 {group_id}', { group_id: groupId })
|
|
1261
|
+
: translateLocaleText('私聊')
|
|
1262
|
+
const resultDestination = translateLocaleText(conversationKind === 'group'
|
|
1263
|
+
? '原群(群内可见)' : '发起任务的私聊')
|
|
1264
|
+
const triggerSummary = translateLocaleText('只有请求以“{triggers}”开头时才会触发此任务',
|
|
1265
|
+
{ triggers: triggerLines.join('”或“') })
|
|
1171
1266
|
const executionDetails = {
|
|
1172
|
-
dsh: 'DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。',
|
|
1173
|
-
codex: 'Codex
|
|
1174
|
-
'claude-code': 'Claude Code
|
|
1267
|
+
dsh: translateLocaleText('DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。'),
|
|
1268
|
+
codex: translateLocaleText('Codex 使用这台 DSH 的 Provider Profile 权限;可访问范围由 Codex/DSH 本机配置决定。任务描述不会限制操作系统权限。'),
|
|
1269
|
+
'claude-code': translateLocaleText('Claude Code 使用这台 DSH 的 Provider Profile 权限;可访问范围由 Claude Code/DSH 本机配置决定。任务描述不会限制操作系统权限。'),
|
|
1175
1270
|
}
|
|
1176
1271
|
const executionInfo = reviewers.length > 1
|
|
1177
|
-
?
|
|
1272
|
+
? translateLocaleText('普通请求默认由 {runner} 执行;请求人明确要求多 Agent 分别处理时,会并行启动 {reviewers},分别返回结果。{details}',
|
|
1273
|
+
{ runner: reviewerLabel(runner), reviewers: reviewers.map(reviewerLabel).join('、'),
|
|
1274
|
+
details: reviewers.map((item) => executionDetails[item]).join(' ') })
|
|
1178
1275
|
: executionDetails[reviewers[0]]
|
|
1179
1276
|
const actionDescription = editingCardId
|
|
1180
|
-
?
|
|
1181
|
-
|
|
1182
|
-
|
|
1277
|
+
? translateLocaleText('更新“{title}”后,{requesterSummary}可在{scope}{triggerSummary}。旧待确认请求将失效,已提交任务继续使用提交时的配置。结果{resultDestination}。到期时间保持为 {expiry}。',
|
|
1278
|
+
{ title, requesterSummary, scope, triggerSummary, resultDestination, expiry: formatLocaleDate(editingExpiresAt * 1000) })
|
|
1279
|
+
: translateLocaleText('开放“{title}”后,{requesterSummary}可在{scope}{triggerSummary}有效期 {days} 天;结果{resultDestination}。',
|
|
1280
|
+
{ title, requesterSummary, scope, triggerSummary, resultDestination, days: formatLocaleNumber(days) })
|
|
1281
|
+
if (!confirmLocalized(`${actionDescription}${executionInfo}${translateLocaleText('请确认。')}`)) return
|
|
1183
1282
|
setBusy(true)
|
|
1184
1283
|
try {
|
|
1185
1284
|
const response = await fetch('/api/ant-teams/delegation/cards', {
|
|
1186
1285
|
method: editingCardId ? 'PUT' : 'POST', headers: { 'content-type': 'application/json' },
|
|
1187
1286
|
body: JSON.stringify({ ...(editingCardId ? { id: editingCardId } : {}),
|
|
1188
1287
|
bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
|
|
1189
|
-
title, rule_description: ruleDescription.trim(), trigger_phrases:
|
|
1288
|
+
title, rule_description: ruleDescription.trim(), trigger_phrases: triggerLines,
|
|
1190
1289
|
intent, examples: ruleExamples,
|
|
1191
1290
|
parameters: normalized, task_prompt: taskPrompt, runner, reviewers, workspace: null,
|
|
1192
1291
|
sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
|
|
@@ -1196,7 +1295,7 @@ window.__ModuleLoader__.load({
|
|
|
1196
1295
|
})
|
|
1197
1296
|
const result = await readLocalJsonResponse(response)
|
|
1198
1297
|
if (!response.ok && result.code === 'IM_TASK_RULE_REQUIRED') {
|
|
1199
|
-
throw new Error('当前 ANT
|
|
1298
|
+
throw new Error('当前 ANT 服务端不支持任务触发规则,请升级服务端后再开放任务。')
|
|
1200
1299
|
}
|
|
1201
1300
|
if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
|
|
1202
1301
|
throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
|
|
@@ -1219,22 +1318,21 @@ window.__ModuleLoader__.load({
|
|
|
1219
1318
|
}
|
|
1220
1319
|
recognizedBaselineRef.current = null
|
|
1221
1320
|
recognizedDescriptionRef.current = null
|
|
1222
|
-
autoAnalyzeDescriptionRef.current = null
|
|
1223
1321
|
recognitionRequestRef.current += 1
|
|
1224
1322
|
setRuleDescription(''); setMissingRequired([]); setSuggestions([]); setRuleExamples([])
|
|
1225
1323
|
setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
|
|
1226
|
-
setParallelAgentsOpen(false)
|
|
1227
1324
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1228
1325
|
await load()
|
|
1229
1326
|
setCreateOpen(false)
|
|
1327
|
+
setSubmissionError('')
|
|
1230
1328
|
setMessage(editingCardId
|
|
1231
1329
|
? '能力配置已更新。新请求将使用新配置,已提交任务保留原配置。'
|
|
1232
1330
|
: '任务已开放。机器人会按已确认的描述判断请求;缺少必要信息时会向请求人追问。')
|
|
1233
|
-
} catch (error) {
|
|
1331
|
+
} catch (error) { reject(error.message || translateLocaleText('能力保存失败。')) }
|
|
1234
1332
|
finally { setBusy(false) }
|
|
1235
1333
|
}
|
|
1236
1334
|
const revokeCard = async (card) => {
|
|
1237
|
-
if (!
|
|
1335
|
+
if (!confirmLocalized(`停止开放“${card.title}”?正在处理的任务无法撤销已经发生的读取。`)) return
|
|
1238
1336
|
setBusy(true)
|
|
1239
1337
|
try {
|
|
1240
1338
|
const response = await fetch('/api/ant-teams/delegation/cards', {
|
|
@@ -1247,21 +1345,25 @@ window.__ModuleLoader__.load({
|
|
|
1247
1345
|
}
|
|
1248
1346
|
const copyCardToDraft = (card) => {
|
|
1249
1347
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1250
|
-
setParallelAgentsOpen(false)
|
|
1251
1348
|
const copiedDescription = card.rule_description || ''
|
|
1252
1349
|
recognizedDescriptionRef.current = copiedDescription.trim() || null
|
|
1253
|
-
autoAnalyzeDescriptionRef.current = null
|
|
1254
1350
|
recognitionRequestRef.current += 1
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1351
|
+
const hasTriggerRule = Array.isArray(card.trigger_phrases) && card.trigger_phrases.length > 0
|
|
1352
|
+
setDraftNotice(!hasTriggerRule
|
|
1353
|
+
? '已复制旧规则。它没有明确触发短语;请重新分析并检查生成的触发开头。'
|
|
1354
|
+
: copiedDescription.trim()
|
|
1355
|
+
? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
|
|
1356
|
+
: '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
|
|
1258
1357
|
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
1259
|
-
const titleChars = Array.from(
|
|
1358
|
+
const titleChars = Array.from(translateLocaleText('复制:{title}', {
|
|
1359
|
+
title: card.title || translateLocaleText('未命名能力'),
|
|
1360
|
+
}))
|
|
1260
1361
|
while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
|
|
1261
1362
|
const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
|
|
1262
1363
|
recognizedBaselineRef.current = taskDraft
|
|
1263
1364
|
setBotId(card.bot_id || cardBot?.bot_id || '')
|
|
1264
1365
|
setRuleDescription(copiedDescription)
|
|
1366
|
+
setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
|
|
1265
1367
|
setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
|
|
1266
1368
|
setTitle(taskDraft.title)
|
|
1267
1369
|
setIntent(taskDraft.intent)
|
|
@@ -1278,7 +1380,7 @@ window.__ModuleLoader__.load({
|
|
|
1278
1380
|
onSelectTab('collaboration')
|
|
1279
1381
|
}
|
|
1280
1382
|
const cancelTask = async (task) => {
|
|
1281
|
-
if (!
|
|
1383
|
+
if (!confirmLocalized(`取消任务 ${task.task_id}?已经发生的读取不能撤回。`)) return
|
|
1282
1384
|
setBusy(true)
|
|
1283
1385
|
try {
|
|
1284
1386
|
const response = await fetch('/api/ant-teams/delegation/cancel', { method: 'POST',
|
|
@@ -1300,7 +1402,8 @@ window.__ModuleLoader__.load({
|
|
|
1300
1402
|
onChange: (event) => { setBotId(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1301
1403
|
h('option', { value: '' }, '请选择机器人'),
|
|
1302
1404
|
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1303
|
-
|
|
1405
|
+
disabled: !bot.available || !bot.connected }, rawLocaleText(bot.name),
|
|
1406
|
+
bot.available && bot.connected ? null : '(未连接)')))) : null,
|
|
1304
1407
|
connectedBots.length === 0 && !loading && !botLoadError ? h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1305
1408
|
h('button', { type: 'button', onClick: openImSettings, style: secondaryButtonStyle }, '打开 DSH-IM 设置'),
|
|
1306
1409
|
h('span', { style: mutedTextStyle }, '先在 DSH 的插件列表安装并连接飞书机器人。')) : null,
|
|
@@ -1308,7 +1411,16 @@ window.__ModuleLoader__.load({
|
|
|
1308
1411
|
style: secondaryButtonStyle }, '检查 DSH-IM 连接') : null,
|
|
1309
1412
|
selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
1310
1413
|
onClick: () => void enrollBot(), style: primaryButtonStyle },
|
|
1311
|
-
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null
|
|
1414
|
+
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null,
|
|
1415
|
+
selectedEnrollmentBound && botConnected ? h('div', { style: { display: 'grid', gap: 6 } },
|
|
1416
|
+
h('p', { style: mutedTextStyle }, ownerApprovalBound
|
|
1417
|
+
? '审批私聊已绑定;没有匹配任务卡的明确任务会发给你逐次审批。'
|
|
1418
|
+
: '绑定一次你的 IM 私聊身份后,就能审批没有匹配任务卡的明确任务。'),
|
|
1419
|
+
h('button', { type: 'button', disabled: busy,
|
|
1420
|
+
onClick: () => void requestOwnerApprovalBinding(), style: secondaryButtonStyle },
|
|
1421
|
+
ownerApprovalBound ? '重新绑定审批私聊' : '绑定审批私聊'),
|
|
1422
|
+
ownerChallengeCode ? h('p', { role: 'status', style: feedbackNoticeStyle },
|
|
1423
|
+
`请在 5 分钟内私聊此机器人发送:绑定审批身份 ${ownerChallengeCode}`) : null) : null)
|
|
1312
1424
|
const groupTaskStatusPanel = selectedEnrollmentBound && botConnected ? h('section', {
|
|
1313
1425
|
id: 'ant-group-task-status', role: 'region', 'aria-label': '各群聊天与任务状态', tabIndex: -1,
|
|
1314
1426
|
style: { ...collaborationCardStyle, gap: 10 } },
|
|
@@ -1319,7 +1431,7 @@ window.__ModuleLoader__.load({
|
|
|
1319
1431
|
loading ? '读取中…' : '刷新群列表')),
|
|
1320
1432
|
h('p', { style: mutedTextStyle }, groupChatLoadFailed
|
|
1321
1433
|
? '群列表暂不可用,请刷新状态后重试。'
|
|
1322
|
-
: availableGroups.length ? '
|
|
1434
|
+
: availableGroups.length ? '此开关只控制普通聊天。任务卡可预授权固定任务;绑定主人审批私聊后,群成员 @机器人发起的其他明确任务也可逐次审批。名称取自 DSH-IM 已保存目标。'
|
|
1323
1435
|
: '先在目标群 @机器人,再刷新状态发现群。'),
|
|
1324
1436
|
availableGroups.length ? h('div', { id: 'ant-group-card-grid', style: collaborationGridStyle },
|
|
1325
1437
|
...availableGroups.map((group) => {
|
|
@@ -1327,13 +1439,13 @@ window.__ModuleLoader__.load({
|
|
|
1327
1439
|
&& card.conversation_kind === 'group' && card.conversation_id === group.group_id).length
|
|
1328
1440
|
return h('article', { key: group.group_id, style: { ...collaborationCardStyle, padding: 10, gap: 8 } },
|
|
1329
1441
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1330
|
-
h('strong', { title: `完整识别码 ${group.group_code}` }, groupLabel(group)),
|
|
1442
|
+
h('strong', { title: `完整识别码 ${group.group_code}` }, rawLocaleText(groupLabel(group))),
|
|
1331
1443
|
h('span', { style: mutedTextStyle }, `已开放任务 ${taskCount} 项`)),
|
|
1332
1444
|
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
1333
1445
|
h('input', { type: 'checkbox', checked: group.chat_enabled,
|
|
1334
1446
|
disabled: busy || groupChatLoadFailed,
|
|
1335
1447
|
onChange: (event) => void saveGroupChat(event.target.checked, group.group_id),
|
|
1336
|
-
'aria-label': '允许群 '
|
|
1448
|
+
'aria-label': translateLocaleText('允许群 {group} 普通聊天', { group: groupLabel(group) }) }), '普通聊天'),
|
|
1337
1449
|
h('button', { type: 'button', disabled: busy, style: secondaryButtonStyle,
|
|
1338
1450
|
onClick: () => { startNewCard({ conversationKind: 'group', groupId: group.group_id }); focusAfterTabChange('collaboration', 'ant-new-task-summary') } },
|
|
1339
1451
|
'为此群开放任务'))
|
|
@@ -1343,13 +1455,15 @@ window.__ModuleLoader__.load({
|
|
|
1343
1455
|
return h('section', { style: { ...settingsPanelStyle,
|
|
1344
1456
|
display: ['collaboration', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
1345
1457
|
'aria-label': '群聊协作设置' },
|
|
1346
|
-
message
|
|
1458
|
+
message && !(activeTab === 'collaboration' && createOpen
|
|
1459
|
+
&& submissionError && message === submissionError)
|
|
1460
|
+
? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1347
1461
|
h('div', { id: 'ant-chat-entry', role: 'region', 'aria-label': '聊天入口和机器人状态', tabIndex: -1,
|
|
1348
1462
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1349
1463
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
1350
1464
|
h('div', { style: { display: 'grid', gap: 4 } },
|
|
1351
1465
|
h('h3', { style: sectionTitleStyle }, '协作总览'),
|
|
1352
|
-
h('p', { style: mutedTextStyle }, '
|
|
1466
|
+
h('p', { style: mutedTextStyle }, '选择机器人并绑定一次主人审批私聊。任务卡可预授权常用任务;其他请求由主人逐次决定。')),
|
|
1353
1467
|
h('span', { style: { ...mutedTextStyle, padding: '4px 9px', borderRadius: 999,
|
|
1354
1468
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot ? selectedBot.name : '尚未选择机器人')),
|
|
1355
1469
|
loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
|
|
@@ -1368,7 +1482,8 @@ window.__ModuleLoader__.load({
|
|
|
1368
1482
|
h('div', { id: 'ant-capability-list', role: 'region', 'aria-label': '已开放任务规则', tabIndex: -1,
|
|
1369
1483
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1370
1484
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap' } },
|
|
1371
|
-
h('h4', { style: sectionTitleStyle }, '我的任务规则 · ' +
|
|
1485
|
+
h('h4', { style: sectionTitleStyle }, '我的任务规则 · ' +
|
|
1486
|
+
(authorizationLoadError ? translateLocaleText('暂未读取') : activeCards.length)),
|
|
1372
1487
|
h('div', { role: 'toolbar', 'aria-label': '任务规则操作', style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1373
1488
|
h('button', { type: 'button', onClick: () => { startNewCard(); focusAfterTabChange('collaboration', 'ant-new-task-summary') },
|
|
1374
1489
|
style: primaryButtonStyle }, '新建任务规则'))),
|
|
@@ -1377,42 +1492,59 @@ window.__ModuleLoader__.load({
|
|
|
1377
1492
|
...activeCards.map((card) => {
|
|
1378
1493
|
const expiresAt = Number(card.expires_at) * 1000
|
|
1379
1494
|
const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
|
|
1380
|
-
const expiry = remainingDays <= 7
|
|
1495
|
+
const expiry = remainingDays <= 7
|
|
1496
|
+
? translateLocaleText('{days} 天后到期', { days: formatLocaleNumber(remainingDays) })
|
|
1497
|
+
: translateLocaleText('至 {expiry_date}', { expiry_date: formatLocaleDate(expiresAt, { dateStyle: 'short' }) })
|
|
1381
1498
|
const conversation = card.conversation_kind === 'group'
|
|
1382
|
-
?
|
|
1383
|
-
&& group.group_id === card.conversation_id)) || card.conversation_id}`
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1499
|
+
? `${translateLocaleText('群')} ${groupLabel(groups.find((group) => group.bot_fingerprint === card.bot_fingerprint
|
|
1500
|
+
&& group.group_id === card.conversation_id)) || card.conversation_id}`
|
|
1501
|
+
: translateLocaleText('私聊')
|
|
1502
|
+
const requester = translateLocaleText(card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1503
|
+
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求')
|
|
1504
|
+
const missingTriggerPhrase = !Array.isArray(card.trigger_phrases) || card.trigger_phrases.length === 0
|
|
1505
|
+
const configuredCardRunners = Array.isArray(card.reviewers) && card.reviewers.length
|
|
1506
|
+
? card.reviewers : [card.runner]
|
|
1507
|
+
const blockedExternalExecutor = !configuredCardRunners.every((runner) =>
|
|
1508
|
+
executors.some((executor) => executor.id === runner && executor.available))
|
|
1387
1509
|
const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
|
|
1388
1510
|
const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
|
|
1511
|
+
const triggerSummary = Array.isArray(card.trigger_phrases) ? card.trigger_phrases.join('、') : ''
|
|
1389
1512
|
const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
|
|
1390
1513
|
: 'var(--dsw-alias-label-secondary)'
|
|
1391
1514
|
return h('article', { key: card.id, style: { ...collaborationCardStyle, padding: 12, gap: 8 } },
|
|
1392
1515
|
h('div', { style: { display: 'grid', gap: 6, minWidth: 0 } },
|
|
1393
1516
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1394
|
-
h('strong', { style: { overflowWrap: 'anywhere' } },
|
|
1517
|
+
h('strong', { style: { overflowWrap: 'anywhere' } },
|
|
1518
|
+
card.title ? rawLocaleText(card.title) : '未命名能力'),
|
|
1395
1519
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
|
|
1396
1520
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
|
|
1397
|
-
h('span', { style: mutedTextStyle },
|
|
1521
|
+
h('span', { style: mutedTextStyle }, translateLocaleText(
|
|
1522
|
+
'{reviewers} · {conversation} · {requesters} · 结果回复原{destination}', {
|
|
1523
|
+
reviewers: cardReviewerSummary(card), conversation, requesters: requester,
|
|
1524
|
+
destination: translateLocaleText(card.conversation_kind === 'group' ? '群' : '私聊'),
|
|
1525
|
+
})),
|
|
1398
1526
|
blockedExternalExecutor ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1399
1527
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1400
|
-
'
|
|
1528
|
+
'此任务暂不可执行:这台 DSH 尚未启用所选的全部执行器。请启用对应的 DSH 子 Agent 提供方后刷新。') : null,
|
|
1529
|
+
missingTriggerPhrase ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1530
|
+
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1531
|
+
'这项旧任务没有明确触发开头,不会自动启动。点击“编辑”,重新分析并复核触发短语。') : null,
|
|
1401
1532
|
card.rule_description
|
|
1402
|
-
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description)
|
|
1533
|
+
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, rawLocaleText(card.rule_description))
|
|
1403
1534
|
: h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1404
1535
|
'旧任务卡没有自然语言描述。请编辑并补充描述,让 AI 生成 Harness 后再开放。'),
|
|
1405
1536
|
h('details', null,
|
|
1406
1537
|
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看规则说明'),
|
|
1407
1538
|
h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1539
|
+
card.rule_description ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, rawLocaleText(card.rule_description)) : null,
|
|
1540
|
+
triggerSummary ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, '触发开头:', rawLocaleText(triggerSummary)) : null,
|
|
1541
|
+
requesterIds ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, '允许请求的 IM 用户:', rawLocaleText(requesterIds)) : null,
|
|
1542
|
+
examplesText ? h('p', { style: mutedTextStyle }, '请求示例:', rawLocaleText(examplesText)) : null,
|
|
1411
1543
|
Array.isArray(card.parameters) && card.parameters.length
|
|
1412
1544
|
? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } },
|
|
1413
1545
|
`请求时还需提供:${card.parameters.map(parameterSummary).join(';')}`)
|
|
1414
1546
|
: h('p', { style: mutedTextStyle }, '无需额外参数,按请求中的内容处理'),
|
|
1415
|
-
card.task_prompt ? h('p', { style: mutedTextStyle },
|
|
1547
|
+
card.task_prompt ? h('p', { style: mutedTextStyle }, '处理要求:', rawLocaleText(card.task_prompt)) : null))),
|
|
1416
1548
|
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end', paddingTop: 4,
|
|
1417
1549
|
borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1418
1550
|
h('button', { type: 'button', disabled: busy, onClick: () => editCard(card), style: secondaryButtonStyle }, '编辑'),
|
|
@@ -1429,20 +1561,38 @@ window.__ModuleLoader__.load({
|
|
|
1429
1561
|
),
|
|
1430
1562
|
h('div', { role: 'region', 'aria-label': '最近任务', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1431
1563
|
h('h4', { style: sectionTitleStyle }, '最近任务'),
|
|
1564
|
+
h('p', { style: mutedTextStyle }, translateLocaleText('本页显示任务失败原因和结果发送状态;完整结果仍回复到原群或私聊。')),
|
|
1432
1565
|
...(tasks.filter((task) => task.task_id).length ? tasks.filter((task) => task.task_id)
|
|
1433
1566
|
.map((task) => {
|
|
1434
|
-
const title = task.title
|
|
1435
|
-
?
|
|
1436
|
-
|
|
1567
|
+
const title = task.title ? rawLocaleText(task.title) : task.pr_number
|
|
1568
|
+
? task.repository
|
|
1569
|
+
? translateLocaleText('{repository} PR #{pr_number} 差异审查', { repository: task.repository, pr_number: task.pr_number })
|
|
1570
|
+
: translateLocaleText('PR #{pr_number} 差异审查', { pr_number: task.pr_number })
|
|
1571
|
+
: '任务'
|
|
1572
|
+
const status = translateLocaleText(task.outcome === 'canceled' ? '已取消' : task.outcome === 'failed' ? '未完成' : ({
|
|
1437
1573
|
pending: '排队中', running: '处理中', reply_pending: '结果待发送', reply_attempted: '回复状态待核对', done: '已回复',
|
|
1438
|
-
}[task.status] || task.status)
|
|
1574
|
+
}[task.status] || task.status))
|
|
1439
1575
|
const createdAt = task.created_at && Number.isFinite(Date.parse(task.created_at))
|
|
1440
|
-
?
|
|
1576
|
+
? formatLocaleDate(task.created_at) : ''
|
|
1577
|
+
const taskStatusLine = translateLocaleText('{status}{time_label} · 任务 {task_id}', {
|
|
1578
|
+
status, time_label: createdAt ? ` · ${createdAt}` : '', task_id: task.task_id,
|
|
1579
|
+
})
|
|
1441
1580
|
return h('div', { key: task.task_id, style: { display: 'flex', gap: 12, alignItems: 'center',
|
|
1442
1581
|
justifyContent: 'space-between', flexWrap: 'wrap', borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8 } },
|
|
1443
|
-
h('div', { style: { flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
1582
|
+
h('div', { style: { display: 'grid', gap: 6, flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
1444
1583
|
h('strong', null, title),
|
|
1445
|
-
h('div', { style: mutedTextStyle },
|
|
1584
|
+
h('div', { style: mutedTextStyle }, taskStatusLine),
|
|
1585
|
+
task.failure_reason ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1586
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1587
|
+
'任务未完成:{reason}\n处理建议:按错误提示修复配置或请求内容后,在原群或私聊重新发起。',
|
|
1588
|
+
{ reason: task.failure_reason }))) : null,
|
|
1589
|
+
task.delivery_error ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1590
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1591
|
+
'结果发送状态未确认:{reason}\n处理建议:先检查原群或私聊是否已收到结果;若未收到,再修复连接问题后重试。',
|
|
1592
|
+
{ reason: task.delivery_error }))) : null,
|
|
1593
|
+
task.status === 'reply_attempted' && !task.delivery_error ? h('div', { role: 'status', style: warningNoticeStyle },
|
|
1594
|
+
h('span', { style: mutedTextStyle }, translateLocaleText(
|
|
1595
|
+
'ANT 尚未确认消息平台是否收到结果。请先检查原群或私聊,再决定是否重新发起。'))) : null),
|
|
1446
1596
|
['card_task', 'legacy_pr_task'].includes(task.kind) && ['pending', 'running'].includes(task.status) ? h('button', { type: 'button', disabled: busy,
|
|
1447
1597
|
onClick: () => void cancelTask(task), style: secondaryButtonStyle }, '取消任务') : null)
|
|
1448
1598
|
})
|
|
@@ -1457,7 +1607,7 @@ window.__ModuleLoader__.load({
|
|
|
1457
1607
|
gap: 12, alignItems: 'start', marginTop: 12 } },
|
|
1458
1608
|
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
1459
1609
|
editingCardId ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1460
|
-
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${
|
|
1610
|
+
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${formatLocaleDate(editingExpiresAt * 1000)}。已提交任务继续使用原配置。`) : null,
|
|
1461
1611
|
h('p', { style: mutedTextStyle }, selectedBot
|
|
1462
1612
|
? `当前机器人:${selectedBot.name}${selectedEnrollmentBound ? ' · 已绑定此设备' : ' · 请先绑定到此设备'}`
|
|
1463
1613
|
: '请先在上方选择机器人并启用协作。'),
|
|
@@ -1474,25 +1624,26 @@ window.__ModuleLoader__.load({
|
|
|
1474
1624
|
h('div', { style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
1475
1625
|
h('label', null, '描述规则', h('textarea', { required: true, maxLength: 4000, rows: 4, value: ruleDescription,
|
|
1476
1626
|
onChange: (event) => {
|
|
1477
|
-
autoAnalyzeDescriptionRef.current = event.target.value
|
|
1478
1627
|
recognitionRequestRef.current += 1
|
|
1479
1628
|
setRuleDescription(event.target.value)
|
|
1480
1629
|
setRecognitionBusy(false)
|
|
1481
|
-
setRecognitionMessage(event.target.value.trim() ? '
|
|
1630
|
+
setRecognitionMessage(event.target.value.trim() ? '描述已更新,点击“立即分析”检查任务。' : '')
|
|
1482
1631
|
},
|
|
1483
1632
|
placeholder: '用日常语言说明要完成什么、可处理哪些资料和希望怎样返回。例如:审查来自 https://gitea.example.com/team/shop 的 PR,指出可定位的问题,不修改代码;请求人可指定 Head SHA,未指定时用最新 Head。', style: inputStyle })),
|
|
1484
|
-
h('p', { style: mutedTextStyle }, 'AI
|
|
1633
|
+
h('p', { style: mutedTextStyle }, 'AI 不会自动分析;点击“立即分析”后才会检查必需范围、缺失信息和可选条件。执行方、请求人、群范围和有效期由下方通用授权控制。'),
|
|
1485
1634
|
h('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1486
1635
|
h('button', { type: 'button', disabled: recognitionBusy || busy || !ruleDescription.trim(),
|
|
1487
1636
|
onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? 'AI 正在分析…' : ruleSummaryReady ? '立即重新分析' : '立即分析'),
|
|
1488
|
-
h('span', { style: mutedTextStyle }, '
|
|
1637
|
+
h('span', { style: mutedTextStyle }, '每次点击都会调用当前团队模型并计入用量;只生成建议和待复核草稿,不会开放任务。修改描述后需再次点击。')),
|
|
1489
1638
|
recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null),
|
|
1490
1639
|
h('section', { role: 'region', 'aria-label': 'AI 任务助手', style: { ...panelStyle, padding: 10, gap: 7, minWidth: 0 } },
|
|
1491
1640
|
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
1492
1641
|
h('strong', null, ruleSummaryPending ? 'AI 正在检查当前描述' : title || 'AI 任务助手'),
|
|
1493
|
-
h('span', { style: mutedTextStyle }, ruleSummaryPending ? '
|
|
1642
|
+
h('span', { style: mutedTextStyle }, ruleSummaryPending ? '正在分析' : ruleSummaryReady ? '待复核 · 尚未开放'
|
|
1643
|
+
: ruleSummaryDirty ? '描述已更新' : '填写描述后点击分析')),
|
|
1494
1644
|
h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, ruleSummaryPending
|
|
1495
|
-
? 'AI 正在根据当前描述更新任务理解。上一次分析结果暂不用于授权开放。'
|
|
1645
|
+
? 'AI 正在根据当前描述更新任务理解。上一次分析结果暂不用于授权开放。'
|
|
1646
|
+
: ruleSummaryDirty ? '描述已更新。点击“立即分析”生成当前任务理解;旧结果暂不用于授权开放。' : ruleSummaryNarrative),
|
|
1496
1647
|
ruleSummaryReady && missingHarnessInputs.length ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1497
1648
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1498
1649
|
`还需要明确:${missingHarnessInputs.join(';')}`) : ruleSummaryReady
|
|
@@ -1501,40 +1652,40 @@ window.__ModuleLoader__.load({
|
|
|
1501
1652
|
ruleSummaryReady && ruleExamples.length ? h('p', { style: mutedTextStyle }, `自然请求示例:${ruleExamples.join(';')}`) : null,
|
|
1502
1653
|
h('p', { style: mutedTextStyle }, ruleSummaryReady
|
|
1503
1654
|
? '草稿未开放。检查任务理解、必需范围和建议;有误就改描述,AI 会重新分析。'
|
|
1504
|
-
: ruleSummaryPending ? '分析完成前不能开放任务。'
|
|
1655
|
+
: ruleSummaryPending ? '分析完成前不能开放任务。'
|
|
1656
|
+
: ruleSummaryDirty ? '点击“立即分析”完成复核后才能开放任务。' : '填写描述后点击“立即分析”生成任务理解;草稿不会自动开放。'))),
|
|
1505
1657
|
h('section', { style: { ...panelStyle, padding: 10, gap: 9, display: 'grid',
|
|
1506
1658
|
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 280px), 1fr))' } },
|
|
1507
1659
|
h('strong', { style: { gridColumn: '1 / -1' } }, '通用授权'),
|
|
1508
1660
|
h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1509
1661
|
'上方描述规则任务要做什么;这里限定谁能请求、在哪里请求、由谁执行和授权多久。'),
|
|
1510
|
-
h('
|
|
1511
|
-
|
|
1512
|
-
h('
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
`${nativePrUnavailableReason}。当前仍可使用 DSH 团队模型处理任务。`) : null,
|
|
1662
|
+
h('div', { role: 'group', 'aria-label': '执行方', style: { display: 'grid', gap: 7 } },
|
|
1663
|
+
h('strong', null, '执行方 · 可多选'),
|
|
1664
|
+
h('p', { style: mutedTextStyle }, '勾选可用于此任务的执行方,并从已勾选项中设一个默认。普通请求只运行默认项;请求人明确要求交叉审查时并行运行其他已选项。'),
|
|
1665
|
+
...[
|
|
1666
|
+
{ id: 'dsh', title: 'DSH', detail: '使用获准的团队模型处理对话内容和 ANT 已核验资源。', available: dshAvailable },
|
|
1667
|
+
{ id: 'codex', title: 'Codex', detail: '使用本机 DSH Provider Profile 权限;ANT 不会将文件、Git 和网络权限缩小到单项任务。', available: codexAvailable },
|
|
1668
|
+
{ id: 'claude-code', title: 'Claude Code', detail: '使用本机 DSH Provider Profile 权限;ANT 不会将文件、Git 和网络权限缩小到单项任务。', available: claudeCodeAvailable },
|
|
1669
|
+
].map((option) => {
|
|
1670
|
+
const checked = reviewers.includes(option.id)
|
|
1671
|
+
return h('div', { key: option.id, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
1672
|
+
gap: 8, alignItems: 'center', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
1673
|
+
padding: '9px 11px' } },
|
|
1674
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1675
|
+
h('input', { type: 'checkbox', 'aria-label': `任务 Agent ${option.title}`, checked,
|
|
1676
|
+
disabled: !option.available && !checked,
|
|
1677
|
+
onChange: (event) => changeReviewer(option.id, event.target.checked) }),
|
|
1678
|
+
h('strong', null, option.title),
|
|
1679
|
+
h('span', { style: mutedTextStyle }, option.available ? '可用' : '未在本机启用')),
|
|
1680
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, color: mutedTextStyle.color } },
|
|
1681
|
+
h('input', { type: 'radio', name: 'ant-default-runner', 'aria-label': `默认执行方 ${option.title}`,
|
|
1682
|
+
checked: runner === option.id, disabled: !checked || !option.available,
|
|
1683
|
+
onChange: () => selectRunner(option.id) }), '默认'),
|
|
1684
|
+
checked ? h('span', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } }, option.detail) : null)
|
|
1685
|
+
}),
|
|
1686
|
+
),
|
|
1687
|
+
executorUnavailableReason ? h('p', { role: 'status', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1688
|
+
`${executorUnavailableReason} 当前仍可使用 DSH 团队模型。`) : null,
|
|
1538
1689
|
h('label', null, '使用场景', h('select', { value: conversationKind,
|
|
1539
1690
|
onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1540
1691
|
h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
|
|
@@ -1544,7 +1695,7 @@ window.__ModuleLoader__.load({
|
|
|
1544
1695
|
onChange: (event) => setGroupId(event.target.value), style: inputStyle },
|
|
1545
1696
|
h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
|
|
1546
1697
|
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
1547
|
-
groupLabel(group))))),
|
|
1698
|
+
rawLocaleText(groupLabel(group)))))),
|
|
1548
1699
|
h('button', { type: 'button', disabled: busy,
|
|
1549
1700
|
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
|
|
1550
1701
|
h('label', null, '谁可以请求', h('select', { value: senderScope,
|
|
@@ -1555,7 +1706,7 @@ window.__ModuleLoader__.load({
|
|
|
1555
1706
|
required: true, value: senderIds, onChange: (event) => setSenderIds(event.target.value),
|
|
1556
1707
|
placeholder: '多个 ID 用逗号分隔', style: inputStyle })) : null,
|
|
1557
1708
|
editingCardId ? h('p', { style: { ...mutedTextStyle, alignSelf: 'end' } },
|
|
1558
|
-
`有效期:${
|
|
1709
|
+
`有效期:${formatLocaleDate(editingExpiresAt * 1000)}(编辑时保持不变)`)
|
|
1559
1710
|
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1560
1711
|
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
|
|
1561
1712
|
(runner === 'codex' || runner === 'claude-code') ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
@@ -1568,9 +1719,19 @@ window.__ModuleLoader__.load({
|
|
|
1568
1719
|
h('h4', { style: sectionTitleStyle }, '开放前看一眼'),
|
|
1569
1720
|
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1570
1721
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '草稿 · 未开放')),
|
|
1571
|
-
h('p', { style: mutedTextStyle },
|
|
1572
|
-
|
|
1573
|
-
|
|
1722
|
+
h('p', { style: mutedTextStyle }, translateLocaleText(
|
|
1723
|
+
'这项任务将在{conversation}开放给{requesters},由{executor}执行;有效期 {expiry},{result_destination}。',
|
|
1724
|
+
{
|
|
1725
|
+
conversation: conversationKind === 'group'
|
|
1726
|
+
? translateLocaleText('群“{group_name}”',
|
|
1727
|
+
{ group_name: selectedGroupSummary || translateLocaleText('尚未选择') })
|
|
1728
|
+
: translateLocaleText('私聊'),
|
|
1729
|
+
requesters: requesterSummary,
|
|
1730
|
+
executor: executorSummary,
|
|
1731
|
+
expiry: editingCardId ? formatLocaleDate(editingExpiresAt * 1000)
|
|
1732
|
+
: `${formatLocaleNumber(days)} ${translateLocaleText('天')}`,
|
|
1733
|
+
result_destination: resultRouteSummary,
|
|
1734
|
+
})),
|
|
1574
1735
|
hasPrParameter && !selectedGiteaOrigin && taskPrOrigins.length === 1
|
|
1575
1736
|
? h('p', { role: 'note', style: mutedTextStyle },
|
|
1576
1737
|
`首次确认开放后,这台机器人会绑定到 ${taskPrOrigins[0]};后续 PR 任务只允许使用此主机,令牌不会发往其他主机。`)
|
|
@@ -1579,17 +1740,24 @@ window.__ModuleLoader__.load({
|
|
|
1579
1740
|
? h('p', { role: 'note', style: mutedTextStyle },
|
|
1580
1741
|
`这台机器人已绑定 ${selectedGiteaOrigin};任务来源需与此主机一致,令牌不会发往其他主机。`)
|
|
1581
1742
|
: null,
|
|
1582
|
-
cardBlockReason ? h('
|
|
1583
|
-
|
|
1743
|
+
cardBlockReason ? h('div', { id: 'ant-capability-block-reason', role: 'alert', 'aria-live': 'assertive',
|
|
1744
|
+
style: warningNoticeStyle }, h('p', { style: mutedTextStyle }, cardBlockReason)) : null,
|
|
1745
|
+
submissionError ? h('div', { id: 'ant-capability-submission-error', role: 'alert', 'aria-live': 'assertive', style: warningNoticeStyle },
|
|
1746
|
+
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1747
|
+
'开放失败:{reason}\n处理建议:按错误提示修正配置后重试;若提示版本不兼容,请管理员升级 ANT 插件或服务端。',
|
|
1748
|
+
{ reason: submissionError }))) : null,
|
|
1584
1749
|
h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
|
|
1585
|
-
|
|
1750
|
+
'aria-describedby': cardBlockReason ? 'ant-capability-block-reason'
|
|
1751
|
+
: submissionError ? 'ant-capability-submission-error' : undefined,
|
|
1752
|
+
style: { ...primaryButtonStyle, width: '100%' } }, busy ? translateLocaleText('正在处理…')
|
|
1753
|
+
: editingCardId ? '保存能力修改' : '确认并开放能力'))))),
|
|
1586
1754
|
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1587
1755
|
showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
|
|
1588
1756
|
h('strong', null, 'PR 只读凭据(本机)'),
|
|
1589
1757
|
h('p', { style: mutedTextStyle }, tokenConfigured
|
|
1590
1758
|
? '令牌由 DSH 本机凭据服务保存。ANT Host 在读取已授权 PR 时会使用它;不会同步到 ANT 服务端或写入审查提示词。'
|
|
1591
1759
|
: '读取 Gitea PR 需要只读访问令牌。保存后由 DSH 本机凭据服务管理,ANT Host 只在读取已授权 PR 时使用。'),
|
|
1592
|
-
h('p', { role: 'note', style: mutedTextStyle }, 'DSH 默认将凭据保存在 DSH Home 的私有文件中,环境变量也可作为回退来源。文件权限只隔离其他 OS 用户;同一用户权限下的 Agent 进程可能读取它。
|
|
1760
|
+
h('p', { role: 'note', style: mutedTextStyle }, 'DSH 默认将凭据保存在 DSH Home 的私有文件中,环境变量也可作为回退来源。文件权限只隔离其他 OS 用户;同一用户权限下的 Agent 进程可能读取它。Codex 和 Claude Code 会使用本机 DSH Provider Profile 的文件、Git、网络及工具权限;任务描述和单次审批不会创建操作系统沙箱,也不会缩小这些权限。ANT Gitea 凭据只由 Host Connector 使用,不会放入 Agent 请求。'),
|
|
1593
1761
|
h('p', { role: 'note', style: mutedTextStyle }, selectedGiteaOrigin
|
|
1594
1762
|
? `这枚只读令牌仅用于已绑定主机 ${selectedGiteaOrigin};任务来源来自自然语言描述,但必须匹配该主机。`
|
|
1595
1763
|
: '首次确认并开放包含 PR 来源的任务时,会将该来源绑定到这台机器人;确认前 ANT 不会把只读令牌发送到任务描述中的地址。'),
|
|
@@ -1623,21 +1791,21 @@ window.__ModuleLoader__.load({
|
|
|
1623
1791
|
style: { ...inputStyle, width: 128, maxWidth: '100%', boxSizing: 'border-box' } })),
|
|
1624
1792
|
h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置'),
|
|
1625
1793
|
h('p', { style: { ...mutedTextStyle, flex: '1 1 100%', margin: 0 } },
|
|
1626
|
-
'
|
|
1794
|
+
'需要完整 PR 文件时,ANT 会在本机准备已核验 Head 的临时快照;刷新授权状态时会清理已停用或到期能力的旧缓存。')),
|
|
1627
1795
|
),
|
|
1628
1796
|
h('section', { 'aria-label': '执行方状态与权限边界', style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
|
|
1629
1797
|
h('strong', null, '执行方状态与权限边界'),
|
|
1630
|
-
h('p', { style: mutedTextStyle }, '这里显示 DSH
|
|
1798
|
+
h('p', { style: mutedTextStyle }, '这里显示 DSH 本机的执行器状态。已注册的 Codex 或 Claude Code 可由主人选入预授权任务卡,或在本次 IM 审批中选择。'),
|
|
1631
1799
|
loadFailures.includes('executors') ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法读取执行方状态,请刷新后重试。') : null,
|
|
1632
1800
|
...executors.filter((item) => ['dsh', 'codex', 'claude-code'].includes(item.id)).map((item) => h('div', {
|
|
1633
1801
|
key: item.id, style: { display: 'grid', gap: 3, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1634
1802
|
h('strong', null, item.title + ' · ' + (item.id === 'dsh'
|
|
1635
1803
|
? item.available ? '可用' : item.reason || '未就绪'
|
|
1636
|
-
: item.available ? '已注册 ·
|
|
1804
|
+
: item.available ? '已注册 · 可用于协作' : '尚未注册')),
|
|
1637
1805
|
h('span', { style: mutedTextStyle }, item.id === 'codex'
|
|
1638
|
-
? '
|
|
1806
|
+
? 'Codex 使用本机 Provider Profile 权限;ANT 不会把文件、Git 或网络权限缩小到任务范围。'
|
|
1639
1807
|
: item.id === 'claude-code'
|
|
1640
|
-
? '
|
|
1808
|
+
? 'Claude Code 使用本机 Provider Profile 权限;ANT 不会把文件、Git 或网络权限缩小到任务范围。'
|
|
1641
1809
|
: '处理授权任务提供的内容,不调用本机工具。'))))))
|
|
1642
1810
|
}
|
|
1643
1811
|
|
|
@@ -1702,15 +1870,20 @@ window.__ModuleLoader__.load({
|
|
|
1702
1870
|
}
|
|
1703
1871
|
const changeInstall = async (skill, remove = false) => {
|
|
1704
1872
|
const action = remove ? '卸载' : skill.local_status === 'update-available' ? '更新' : '安装'
|
|
1873
|
+
const localizedAction = translateLocaleText(action)
|
|
1705
1874
|
const reportInDetail = selectedSkill?.id === skill.id
|
|
1706
1875
|
const prompt = remove
|
|
1707
1876
|
? skill.conflict_reason === 'other-source'
|
|
1708
1877
|
? '这台 DSH 已安装另一个来源的同名技能。确认卸载本机旧来源?已修改的本地文件不会被删除。'
|
|
1709
|
-
:
|
|
1710
|
-
:
|
|
1711
|
-
|
|
1878
|
+
: translateLocaleText('从这台 DSH 卸载 {skill_title}?已修改的本地文件不会被删除。', { skill_title: skill.title })
|
|
1879
|
+
: translateLocaleText('将 {skill_title} {action}到这台 DSH?\n\n外部数据交互:{data_interactions}\n\n本机依赖:{requirements}\n\n技能可能让 DSH 读取文件或执行脚本,仍受 DSH 的工具授权控制。ANT 不会自动安装系统依赖。',
|
|
1880
|
+
{ skill_title: skill.title, action: localizedAction,
|
|
1881
|
+
data_interactions: skill.data_interactions?.trim() || translateLocaleText('待管理员补充;使用前请先核实数据去向。'),
|
|
1882
|
+
requirements: skill.requirements })
|
|
1883
|
+
if (!confirmLocalized(prompt)) return
|
|
1712
1884
|
setWorkingId(skill.id)
|
|
1713
|
-
const progressMessage =
|
|
1885
|
+
const progressMessage = translateLocaleText('正在{action} {skill_title}…',
|
|
1886
|
+
{ action: localizedAction, skill_title: skill.title })
|
|
1714
1887
|
setMessage(progressMessage)
|
|
1715
1888
|
if (reportInDetail) setDetailActionMessage({ skillId: skill.id, text: progressMessage })
|
|
1716
1889
|
try {
|
|
@@ -1787,14 +1960,14 @@ window.__ModuleLoader__.load({
|
|
|
1787
1960
|
} }, h('div', { style: { maxWidth: 1100, margin: '0 auto', display: 'grid', gap: 16 } },
|
|
1788
1961
|
h('button', { type: 'button', onClick: () => { originalGeneration.current += 1; setOriginalView(null) },
|
|
1789
1962
|
style: { ...secondaryButtonStyle, width: 'fit-content' } }, includePlugins ? '← 返回企业扩展' : '← 返回技能中心'),
|
|
1790
|
-
h('h1', { style: { margin: 0, fontSize: 26 } },
|
|
1963
|
+
h('h1', { style: { margin: 0, fontSize: 26 } }, rawLocaleText(originalView.title), ' · ', '原始技能说明'),
|
|
1791
1964
|
h('p', { style: mutedTextStyle }, originalView.partial
|
|
1792
1965
|
? '当前服务端尚未提供完整原文,此处仅显示导入时的英文简介。'
|
|
1793
1966
|
: '这里显示发布时保存的 SKILL.md。它供 DSH 使用,与技能中心的中文介绍分别维护;ANT 可能给原技能名加前缀。'),
|
|
1794
1967
|
originalView.loading ? h('p', { role: 'status', style: mutedTextStyle }, '正在读取原文…')
|
|
1795
1968
|
: originalView.error ? h('p', { role: 'alert', style: mutedTextStyle }, originalView.error)
|
|
1796
1969
|
: h('pre', { style: { ...panelStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontFamily: 'monospace', fontSize: 13, lineHeight: 1.6 } }, originalView.markdown)))
|
|
1797
|
-
return h('section', { 'aria-label': includePlugins ? '企业扩展' : '技能中心', style: {
|
|
1970
|
+
return h('section', { dir: formatLocaleDirection(), 'aria-label': includePlugins ? '企业扩展' : '技能中心', style: {
|
|
1798
1971
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
1799
1972
|
padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
|
|
1800
1973
|
} }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
|
|
@@ -1849,7 +2022,7 @@ window.__ModuleLoader__.load({
|
|
|
1849
2022
|
backgroundColor: 'var(--dsw-alias-bg-layer-1)', boxShadow: '0 24px 80px rgba(0,0,0,.35)' } },
|
|
1850
2023
|
h('header', { style: { display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'start' } },
|
|
1851
2024
|
h('div', { style: { display: 'grid', gap: 8 } },
|
|
1852
|
-
h('h2', { style: { margin: 0, fontSize: 24 } }, selectedSkill.title),
|
|
2025
|
+
h('h2', { style: { margin: 0, fontSize: 24 } }, rawLocaleText(selectedSkill.title)),
|
|
1853
2026
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } },
|
|
1854
2027
|
h('span', { style: { ...mutedTextStyle, padding: '3px 9px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedSkill.scope === 'market' ? '全市场精选' : '本企业'),
|
|
1855
2028
|
h('span', { style: { ...mutedTextStyle, padding: '3px 9px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(skillCategory(selectedSkill))),
|
|
@@ -1860,16 +2033,21 @@ window.__ModuleLoader__.load({
|
|
|
1860
2033
|
style: { ...secondaryButtonStyle, flex: '0 0 auto' } }, '关闭')),
|
|
1861
2034
|
h('section', { style: { display: 'grid', gap: 7 } },
|
|
1862
2035
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '这个技能能做什么'),
|
|
1863
|
-
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
2036
|
+
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
2037
|
+
skillIntroductionDisplay(selectedSkill, skillIntroduction(selectedSkill)))),
|
|
1864
2038
|
h('section', { style: { display: 'grid', gap: 7, padding: 14, borderRadius: 10,
|
|
1865
2039
|
border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
1866
2040
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '外部数据交互'),
|
|
1867
2041
|
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
1868
|
-
selectedSkill.data_interactions?.trim()
|
|
2042
|
+
selectedSkill.data_interactions?.trim()
|
|
2043
|
+
? rawLocaleText(selectedSkill.data_interactions.trim())
|
|
2044
|
+
: '待管理员补充。使用前请核实这项技能是否调用外部服务,以及会发送哪些数据。')),
|
|
1869
2045
|
h('section', { style: { display: 'grid', gap: 7, overflowWrap: 'anywhere' } },
|
|
1870
2046
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '来源与使用条件'),
|
|
1871
|
-
h('p', { style: mutedTextStyle },
|
|
1872
|
-
|
|
2047
|
+
h('p', { style: mutedTextStyle }, '来源:',
|
|
2048
|
+
['GitHub', 'ClawHub'].includes(sourceLabel) ? rawLocaleText(sourceLabel) : sourceLabel,
|
|
2049
|
+
' · 许可:', rawLocaleText(selectedSkill.license)),
|
|
2050
|
+
h('p', { style: mutedTextStyle }, formatLocaleNumber(selectedSkill.file_count), ' 个文件 · 本机条件:', rawLocaleText(selectedSkill.requirements)),
|
|
1873
2051
|
h('p', { style: mutedTextStyle, title: sourceRef }, `固定版本:${versionLabel || '已锁定制品'}`),
|
|
1874
2052
|
englishSource ? h('a', { href: englishSource, target: '_blank', rel: 'noopener noreferrer',
|
|
1875
2053
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, originalEnglish ? '查看英文原文 ↗' : '查看原始说明 ↗')
|
|
@@ -1942,7 +2120,7 @@ window.__ModuleLoader__.load({
|
|
|
1942
2120
|
minHeight: 190, boxSizing: 'border-box', alignContent: 'start' } },
|
|
1943
2121
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 10 } },
|
|
1944
2122
|
h('div', { style: { minWidth: 0, display: 'grid', gap: 7 } },
|
|
1945
|
-
|
|
2123
|
+
h('h2', { style: { margin: 0, fontSize: 18, lineHeight: 1.3, overflowWrap: 'anywhere' } }, rawLocaleText(skill.title)),
|
|
1946
2124
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center' } },
|
|
1947
2125
|
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)', fontSize: 11 } }, skill.scope === 'market' ? '全市场精选' : '本企业'),
|
|
1948
2126
|
h('span', { style: { ...mutedTextStyle, fontSize: 12 } }, categoryLabel(skillCategory(skill))))),
|
|
@@ -1950,7 +2128,7 @@ window.__ModuleLoader__.load({
|
|
|
1950
2128
|
? skill.team_connected ? '待启用' : '未连接'
|
|
1951
2129
|
: statusLabel[skill.local_status] || '状态未知')),
|
|
1952
2130
|
h('p', { style: { ...mutedTextStyle, display: '-webkit-box', WebkitBoxOrient: 'vertical',
|
|
1953
|
-
WebkitLineClamp: 2, overflow: 'hidden', minHeight: 42 } }, description),
|
|
2131
|
+
WebkitLineClamp: 2, overflow: 'hidden', minHeight: 42 } }, skillIntroductionDisplay(skill, description)),
|
|
1954
2132
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginTop: 'auto' } },
|
|
1955
2133
|
h('button', { type: 'button', onClick: (event) => {
|
|
1956
2134
|
detailTriggerRef.current = event?.currentTarget || null
|
|
@@ -2044,12 +2222,15 @@ window.__ModuleLoader__.load({
|
|
|
2044
2222
|
|| artifactUrl && local.source !== artifactUrl))
|
|
2045
2223
|
const action = localAhead ? '回退到团队版本' : local ? local.version === plugin.version ? '重新安装' : '更新' : '安装'
|
|
2046
2224
|
const sourceNotice = artifactUrl
|
|
2047
|
-
? 'DSH 将安装 ANT 固定的顶层制品。'
|
|
2048
|
-
: `DSH 将优先从 npm 官方源以 ${installSpec} 安装此精确版本,并按其配置回退 registry;依赖版本可能因设备不同。`
|
|
2049
|
-
const updateNotice = 'ANT 不会批准依赖构建脚本;插件自身的更新器或 DSH 原生管理页之后仍可安装其他版本。'
|
|
2050
|
-
|
|
2225
|
+
? translateLocaleText('DSH 将安装 ANT 固定的顶层制品。')
|
|
2226
|
+
: translateLocaleText(`DSH 将优先从 npm 官方源以 ${installSpec} 安装此精确版本,并按其配置回退 registry;依赖版本可能因设备不同。`)
|
|
2227
|
+
const updateNotice = translateLocaleText('ANT 不会批准依赖构建脚本;插件自身的更新器或 DSH 原生管理页之后仍可安装其他版本。')
|
|
2228
|
+
const installConfirmation = translateLocaleText(
|
|
2229
|
+
'在这台 DSH {action}团队审核的 {plugin_title} {version}?{source_notice}{update_notice} 插件将在本机运行,完成后需要完全重启 DSH。',
|
|
2230
|
+
{ action: translateLocaleText(action), plugin_title: plugin.title, version: plugin.version, source_notice: sourceNotice, update_notice: updateNotice })
|
|
2231
|
+
if (!confirmLocalized(installConfirmation)) return
|
|
2051
2232
|
setWorkingId(plugin.id)
|
|
2052
|
-
setMessage(
|
|
2233
|
+
setMessage(translateLocaleText('正在安装 {plugin_title},请不要退出 DSH…', { plugin_title: plugin.title }))
|
|
2053
2234
|
try {
|
|
2054
2235
|
const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
|
|
2055
2236
|
const catalog = await readLocalJsonResponse(response)
|
|
@@ -2145,9 +2326,12 @@ window.__ModuleLoader__.load({
|
|
|
2145
2326
|
const recovery = await restorePreviousVersion()
|
|
2146
2327
|
if (recovery !== 'restored') setUncertainId(plugin.id)
|
|
2147
2328
|
const recoveryNotice = recovery === 'restored'
|
|
2148
|
-
?
|
|
2149
|
-
|
|
2150
|
-
|
|
2329
|
+
? translateLocaleText('旧版本号和原启用状态已恢复。{source_notice}', {
|
|
2330
|
+
source_notice: previousSourceIsRegistry
|
|
2331
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请联系管理员核对。')}` : '' })
|
|
2332
|
+
: recovery === 'target' ? translateLocaleText(
|
|
2333
|
+
'本机列表显示审核版本 {version} 已安装,请核对后再操作。', { version: plugin.version })
|
|
2334
|
+
: translateLocaleText('无法确认旧版本及启用状态,请先在 DSH 插件管理核对。')
|
|
2151
2335
|
setMessage(`无法开始更新 ${plugin.title}:DSH 未能确认旧版本移除完成。${diagnostic
|
|
2152
2336
|
? `DSH 诊断:${diagnostic}。` : ''}${recoveryNotice}`)
|
|
2153
2337
|
return
|
|
@@ -2174,9 +2358,13 @@ window.__ModuleLoader__.load({
|
|
|
2174
2358
|
if (removedForUpdate) {
|
|
2175
2359
|
const restoreState = await restorePreviousVersion()
|
|
2176
2360
|
restoreNotice = restoreState === 'restored'
|
|
2177
|
-
?
|
|
2178
|
-
|
|
2179
|
-
:
|
|
2361
|
+
? translateLocaleText('DSH 已恢复旧版本号 {version}。{source_notice}', {
|
|
2362
|
+
version: local.version,
|
|
2363
|
+
source_notice: previousSourceIsRegistry
|
|
2364
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请核对来源。')}` : '' })
|
|
2365
|
+
: restoreState === 'target' ? ` ${translateLocaleText(
|
|
2366
|
+
'本机列表显示审核版本 {version} 已安装,但操作结果未确认。', { version: plugin.version })}`
|
|
2367
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2180
2368
|
}
|
|
2181
2369
|
const details = [installFailure && `installBundle:${safeInstallDiagnostic(installFailure)}`,
|
|
2182
2370
|
waitFailure && `waitForInstall:${safeInstallDiagnostic(waitFailure)}`].filter(Boolean)
|
|
@@ -2203,13 +2391,18 @@ window.__ModuleLoader__.load({
|
|
|
2203
2391
|
let restoreNotice = ''
|
|
2204
2392
|
if (updateRestore) {
|
|
2205
2393
|
restoreNotice = ['applied', 'restart-required'].includes(updateRestore.application)
|
|
2206
|
-
? ` DSH 已恢复旧版本
|
|
2394
|
+
? ` ${translateLocaleText('DSH 已恢复旧版本 {version}。', { version: local.version })}`
|
|
2395
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2207
2396
|
} else if (removedForUpdate) {
|
|
2208
2397
|
const restoreState = await restorePreviousVersion()
|
|
2209
2398
|
restoreNotice = restoreState === 'restored'
|
|
2210
|
-
?
|
|
2211
|
-
|
|
2212
|
-
:
|
|
2399
|
+
? translateLocaleText('DSH 已恢复旧版本号 {version}。{source_notice}', {
|
|
2400
|
+
version: local.version,
|
|
2401
|
+
source_notice: previousSourceIsRegistry
|
|
2402
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请核对来源。')}` : '' })
|
|
2403
|
+
: restoreState === 'target' ? ` ${translateLocaleText(
|
|
2404
|
+
'本机列表显示审核版本 {version} 已安装,但结果未确认。', { version: plugin.version })}`
|
|
2405
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2213
2406
|
}
|
|
2214
2407
|
setMessage(`${plugin.title} 安装未完成(请求编号 ${requestReference})。${diagnostic
|
|
2215
2408
|
? `DSH 诊断:${diagnostic}。` : 'DSH 没有返回失败诊断。'}${restoreNotice}请在 DSH 插件管理查看状态或联系管理员。`)
|
|
@@ -2343,14 +2536,15 @@ window.__ModuleLoader__.load({
|
|
|
2343
2536
|
? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
|
|
2344
2537
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
2345
2538
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
2346
|
-
h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
|
|
2539
|
+
h('h2', { style: { margin: 0, fontSize: 19 } }, rawLocaleText(plugin.title)),
|
|
2347
2540
|
embedded ? h('span', { style: mutedTextStyle }, '插件') : null,
|
|
2348
2541
|
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
2349
2542
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(categoryOf(plugin)))),
|
|
2350
2543
|
h('span', { style: security
|
|
2351
2544
|
? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)', fontWeight: 650 } : mutedTextStyle }, status)),
|
|
2352
|
-
h('p', { style: mutedTextStyle }, plugin.description),
|
|
2353
|
-
h('p', { style: mutedTextStyle }, `${plugin.package_name}@${plugin.version} ·
|
|
2545
|
+
h('p', { style: mutedTextStyle }, rawLocaleText(plugin.description)),
|
|
2546
|
+
h('p', { style: mutedTextStyle }, rawLocaleText(`${plugin.package_name}@${plugin.version}`), ' · 许可:',
|
|
2547
|
+
plugin.license ? rawLocaleText(plugin.license) : '未声明'),
|
|
2354
2548
|
h('p', { style: mutedTextStyle }, `来源:${plugin.source_url || '未提供'} · DSH 版本要求:${plugin.peer_range || '包未声明'}`),
|
|
2355
2549
|
h('p', { style: mutedTextStyle }, `额外配置:${plugin.requirements || '无'}`),
|
|
2356
2550
|
installSpec && !artifactUrl ? h('p', { role: 'note', style: mutedTextStyle },
|
|
@@ -2532,11 +2726,19 @@ window.__ModuleLoader__.load({
|
|
|
2532
2726
|
return status
|
|
2533
2727
|
}
|
|
2534
2728
|
|
|
2535
|
-
const modelProvider = (model) =>
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2729
|
+
const modelProvider = (model) => {
|
|
2730
|
+
const providerId = typeof model?.provider_id === 'string' ? model.provider_id : ''
|
|
2731
|
+
if (!providerId) return null
|
|
2732
|
+
return model.protocol === 'anthropic'
|
|
2733
|
+
? 'ant-team-anthropic-' + providerId
|
|
2734
|
+
: 'ant-team-openai-' + providerId + '-' + (model.openai_api_mode === 'responses' ? 'responses' : 'chat-completions')
|
|
2735
|
+
}
|
|
2736
|
+
const oldDefaultRoutes = ['ant-team-openai-responses', 'ant-team-openai-chat-completions',
|
|
2737
|
+
'ant-team-openai', 'ant-team-anthropic']
|
|
2738
|
+
const isAntModelProviderRoute = (route, profile) => typeof route === 'string'
|
|
2739
|
+
&& profile?.apiKeyEnv === 'ANT_MODEL_API_KEY'
|
|
2740
|
+
&& (oldDefaultRoutes.includes(route)
|
|
2741
|
+
|| route.startsWith('ant-team-openai-') || route.startsWith('ant-team-anthropic-'))
|
|
2540
2742
|
const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
|
|
2541
2743
|
const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2542
2744
|
const providerSyncPreferenceKey = (rawServiceUrl) => `${PROVIDER_SYNC_EXCLUSIONS_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
@@ -2559,48 +2761,10 @@ window.__ModuleLoader__.load({
|
|
|
2559
2761
|
const saveDefaultPreference = (rawServiceUrl, value) => {
|
|
2560
2762
|
try { localStorage.setItem(defaultPreferenceKey(rawServiceUrl), JSON.stringify(value)) } catch {}
|
|
2561
2763
|
}
|
|
2562
|
-
const modelForProviderSelection = (selection, models
|
|
2764
|
+
const modelForProviderSelection = (selection, models) => {
|
|
2563
2765
|
if (!selection) return null
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
const routeModeChanged = models.find((model) => {
|
|
2567
|
-
if (model.model_id !== selection.model || model.protocol !== 'openai') return false
|
|
2568
|
-
if (model.provider_id === 'openai-default') {
|
|
2569
|
-
if (preferredModel?.provider_id === 'openai-default' && preferredModel.model_id === selection.model) return true
|
|
2570
|
-
if (selection.provider === 'ant-team-openai') return true
|
|
2571
|
-
if (selection.provider === 'ant-team-openai-chat-completions') return model.openai_api_mode !== 'responses'
|
|
2572
|
-
if (selection.provider === 'ant-team-openai-responses') return model.openai_api_mode === 'responses'
|
|
2573
|
-
return false
|
|
2574
|
-
}
|
|
2575
|
-
if (!model.provider_id) return false
|
|
2576
|
-
if (selection.provider === `ant-team-openai-${model.provider_id}`) return true
|
|
2577
|
-
if (selection.provider === `ant-team-openai-${model.provider_id}-chat-completions`) return model.openai_api_mode !== 'responses'
|
|
2578
|
-
if (selection.provider === `ant-team-openai-${model.provider_id}-responses`) return model.openai_api_mode === 'responses'
|
|
2579
|
-
return false
|
|
2580
|
-
})
|
|
2581
|
-
if (routeModeChanged) return routeModeChanged
|
|
2582
|
-
const legacyCustomOpenai = models.find((model) => model.protocol === 'openai'
|
|
2583
|
-
&& model.provider_id && model.provider_id !== 'openai-default'
|
|
2584
|
-
&& [
|
|
2585
|
-
`ant-team-openai-${model.provider_id}`,
|
|
2586
|
-
`ant-team-openai-${model.provider_id}-chat-completions`,
|
|
2587
|
-
`ant-team-openai-${model.provider_id}-responses`,
|
|
2588
|
-
].includes(selection.provider)
|
|
2589
|
-
&& model.model_id === selection.model)
|
|
2590
|
-
if (legacyCustomOpenai) return legacyCustomOpenai
|
|
2591
|
-
if (['ant-team-openai', 'ant-team-openai-chat-completions'].includes(selection.provider)) {
|
|
2592
|
-
const chatCandidates = models.filter((model) => model.protocol === 'openai'
|
|
2593
|
-
&& model.openai_api_mode !== 'responses' && model.model_id === selection.model)
|
|
2594
|
-
const preferredChat = preferredModel && chatCandidates.find((model) =>
|
|
2595
|
-
modelSelectionKey(model) === modelSelectionKey(preferredModel))
|
|
2596
|
-
if (preferredChat) return preferredChat
|
|
2597
|
-
const canonicalChat = chatCandidates.find((model) => model.provider_id === 'openai-default')
|
|
2598
|
-
if (canonicalChat || chatCandidates.length === 1) return canonicalChat || chatCandidates[0]
|
|
2599
|
-
return models.find((model) => model.provider_id === 'openai-default' && model.model_id === selection.model) || null
|
|
2600
|
-
}
|
|
2601
|
-
const legacyProvider = selection.provider === 'ant-team-openai-responses'
|
|
2602
|
-
? 'openai-default' : selection.provider === 'ant-team-anthropic' ? 'anthropic-default' : null
|
|
2603
|
-
return legacyProvider ? models.find((model) => model.provider_id === legacyProvider && model.model_id === selection.model) : null
|
|
2766
|
+
return models.find((model) => model.model_id === selection.model
|
|
2767
|
+
&& modelProvider(model) === selection.provider) || null
|
|
2604
2768
|
}
|
|
2605
2769
|
const readDshDefaultSelection = async () => {
|
|
2606
2770
|
if (typeof remote?.settings?.describe !== 'function') throw new Error('当前 DSH 未提供全局默认模型读取接口。')
|
|
@@ -2612,13 +2776,11 @@ window.__ModuleLoader__.load({
|
|
|
2612
2776
|
...(typeof value.reasoningEffort === 'string' ? { reasoningEffort: value.reasoningEffort } : {}) } : null
|
|
2613
2777
|
}
|
|
2614
2778
|
const readDshProviderProfiles = async () => {
|
|
2615
|
-
if (typeof remote?.settings?.describe !== 'function')
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
return profiles && typeof profiles === 'object' ? profiles : {}
|
|
2621
|
-
} catch { return {} }
|
|
2779
|
+
if (typeof remote?.settings?.describe !== 'function') throw new Error('无法读取 DSH 中的 ANT 提供方列表。')
|
|
2780
|
+
const response = await remote.settings.describe()
|
|
2781
|
+
if (!response?.ok) throw new Error(response?.error?.message || '无法读取 DSH 中的 ANT 提供方列表。')
|
|
2782
|
+
const profiles = response.value?.namespaces?.find((entry) => entry.ns === 'llm-pi-ai')?.value?.providers
|
|
2783
|
+
return profiles && typeof profiles === 'object' ? profiles : {}
|
|
2622
2784
|
}
|
|
2623
2785
|
const writeDshDefaultModel = async (model) => {
|
|
2624
2786
|
const response = await remote.settings.mutate('agent-default-model', [
|
|
@@ -2654,7 +2816,7 @@ window.__ModuleLoader__.load({
|
|
|
2654
2816
|
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null, options = {}) => {
|
|
2655
2817
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
2656
2818
|
if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
|
|
2657
|
-
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH
|
|
2819
|
+
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 未提供模型设置接口。')
|
|
2658
2820
|
const imageOptOuts = imageInputOptOuts.serviceUrl === normalized
|
|
2659
2821
|
? imageInputOptOuts.modelKeys : readImageInputOptOuts(normalized)
|
|
2660
2822
|
const imageOptOutSet = new Set(imageOptOuts)
|
|
@@ -2662,11 +2824,14 @@ window.__ModuleLoader__.load({
|
|
|
2662
2824
|
for (const model of models) {
|
|
2663
2825
|
const protocol = model.protocol
|
|
2664
2826
|
if (!['openai', 'anthropic'].includes(protocol)) continue
|
|
2665
|
-
|
|
2827
|
+
if (typeof model.provider_id !== 'string' || !model.provider_id) {
|
|
2828
|
+
throw new Error('ANT 返回的模型缺少 provider_id;请升级服务端后重试。')
|
|
2829
|
+
}
|
|
2830
|
+
const providerId = model.provider_id
|
|
2666
2831
|
const apiMode = protocol === 'openai'
|
|
2667
2832
|
? model.openai_api_mode === 'responses' ? 'responses' : 'chat_completions'
|
|
2668
2833
|
: null
|
|
2669
|
-
const key =
|
|
2834
|
+
const key = providerId + '\u0000' + protocol + '\u0000' + (apiMode || '')
|
|
2670
2835
|
let provider = grouped.get(key)
|
|
2671
2836
|
if (!provider) {
|
|
2672
2837
|
const providerName = model.provider_name || (protocol === 'openai'
|
|
@@ -2678,7 +2843,6 @@ window.__ModuleLoader__.load({
|
|
|
2678
2843
|
: providerName,
|
|
2679
2844
|
protocol,
|
|
2680
2845
|
apiMode,
|
|
2681
|
-
legacyEndpoint: typeof model.provider_id !== 'string' || !model.provider_id,
|
|
2682
2846
|
models: [],
|
|
2683
2847
|
}
|
|
2684
2848
|
grouped.set(key, provider)
|
|
@@ -2686,36 +2850,41 @@ window.__ModuleLoader__.load({
|
|
|
2686
2850
|
provider.models.push(model)
|
|
2687
2851
|
}
|
|
2688
2852
|
const providers = [...grouped.values()]
|
|
2689
|
-
const hasDefaultOpenaiChat = providers.some((provider) =>
|
|
2690
|
-
provider.providerId === 'openai-default'
|
|
2691
|
-
&& provider.protocol === 'openai'
|
|
2692
|
-
&& provider.apiMode !== 'responses')
|
|
2693
|
-
const defaultOpenaiModels = models.filter((model) =>
|
|
2694
|
-
model.protocol === 'openai' && (model.provider_id || 'openai-default') === 'openai-default')
|
|
2695
2853
|
const routeForProvider = (provider) => modelProvider({
|
|
2696
2854
|
provider_id: provider.providerId,
|
|
2697
2855
|
protocol: provider.protocol,
|
|
2698
2856
|
openai_api_mode: provider.apiMode,
|
|
2699
2857
|
})
|
|
2700
2858
|
const routeConfigurations = new Map()
|
|
2701
|
-
const compatibilityRoutes = []
|
|
2702
2859
|
const providerGroups = []
|
|
2703
|
-
const dshModelConfig = (model) =>
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2860
|
+
const dshModelConfig = (model) => {
|
|
2861
|
+
const hasReasoningEfforts = model.reasoning_efforts && Object.keys(model.reasoning_efforts).length > 0
|
|
2862
|
+
const compat = { ...(model.compat || {}) }
|
|
2863
|
+
if (model.protocol === 'anthropic' && hasReasoningEfforts
|
|
2864
|
+
&& compat.forceAdaptiveThinking === undefined) {
|
|
2865
|
+
compat.forceAdaptiveThinking = true
|
|
2866
|
+
}
|
|
2867
|
+
return {
|
|
2868
|
+
id: model.model_id,
|
|
2869
|
+
name: model.display_name,
|
|
2870
|
+
...(Number.isSafeInteger(model.context_window) && model.context_window > 0
|
|
2871
|
+
? { contextWindow: model.context_window } : {}),
|
|
2872
|
+
...(Number.isSafeInteger(model.max_tokens) && model.max_tokens > 0
|
|
2873
|
+
? { maxTokens: model.max_tokens } : {}),
|
|
2874
|
+
...(model.supports_images === true && !imageOptOutSet.has(modelSelectionKey(model))
|
|
2875
|
+
? { input: ['text', 'image'] } : {}),
|
|
2876
|
+
...(hasReasoningEfforts ? { reasoningEfforts: model.reasoning_efforts } : {}),
|
|
2877
|
+
...(Object.keys(compat).length > 0 ? { compat } : {}),
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2709
2880
|
for (const provider of providers) {
|
|
2710
2881
|
const route = routeForProvider(provider)
|
|
2711
2882
|
const api = provider.protocol === 'openai'
|
|
2712
2883
|
? provider.apiMode === 'responses' ? 'openai-responses' : 'openai-completions'
|
|
2713
2884
|
: 'anthropic-messages'
|
|
2714
|
-
const baseURL = provider.
|
|
2715
|
-
? new URL(
|
|
2716
|
-
: provider.
|
|
2717
|
-
? new URL(`/model-gateway/openai/providers/${encodeURIComponent(provider.providerId)}/v1`, normalized).toString()
|
|
2718
|
-
: new URL(`/model-gateway/anthropic/providers/${encodeURIComponent(provider.providerId)}`, normalized).toString()
|
|
2885
|
+
const baseURL = provider.protocol === 'openai'
|
|
2886
|
+
? new URL('/model-gateway/openai/providers/' + encodeURIComponent(provider.providerId) + '/v1', normalized).toString()
|
|
2887
|
+
: new URL('/model-gateway/anthropic/providers/' + encodeURIComponent(provider.providerId), normalized).toString()
|
|
2719
2888
|
routeConfigurations.set(route, {
|
|
2720
2889
|
displayName: provider.providerName,
|
|
2721
2890
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
@@ -2725,27 +2894,11 @@ window.__ModuleLoader__.load({
|
|
|
2725
2894
|
})
|
|
2726
2895
|
providerGroups.push({
|
|
2727
2896
|
key: route,
|
|
2728
|
-
label:
|
|
2897
|
+
label: provider.providerName + ' · ' + (provider.protocol === 'anthropic'
|
|
2898
|
+
? 'Anthropic Messages' : provider.apiMode === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'),
|
|
2729
2899
|
routes: [route],
|
|
2730
2900
|
modelCount: provider.models.length,
|
|
2731
|
-
legacyKeys: [provider.protocol === 'openai' && provider.providerId !== 'openai-default'
|
|
2732
|
-
? `ant-team-openai-${provider.providerId}` : route],
|
|
2733
|
-
legacy: false,
|
|
2734
2901
|
})
|
|
2735
|
-
if (provider.protocol === 'openai' && provider.providerId === 'openai-default') {
|
|
2736
|
-
const aliases = provider.apiMode === 'responses'
|
|
2737
|
-
? hasDefaultOpenaiChat ? [] : ['ant-team-openai-chat-completions', 'ant-team-openai']
|
|
2738
|
-
: ['ant-team-openai']
|
|
2739
|
-
compatibilityRoutes.push(...aliases)
|
|
2740
|
-
const legacyChat = {
|
|
2741
|
-
displayName: `${provider.providerName} · Chat(旧会话兼容)`,
|
|
2742
|
-
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
2743
|
-
api: 'openai-completions',
|
|
2744
|
-
baseURL: new URL('/model-gateway/openai/v1', normalized).toString(),
|
|
2745
|
-
models: defaultOpenaiModels.map(dshModelConfig),
|
|
2746
|
-
}
|
|
2747
|
-
for (const alias of aliases) routeConfigurations.set(alias, legacyChat)
|
|
2748
|
-
}
|
|
2749
2902
|
}
|
|
2750
2903
|
const ops = []
|
|
2751
2904
|
let previousRoutes = []
|
|
@@ -2753,40 +2906,12 @@ window.__ModuleLoader__.load({
|
|
|
2753
2906
|
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2754
2907
|
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2755
2908
|
} catch {}
|
|
2756
|
-
const
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
const legacyRoute = `ant-team-openai-${providerId}`
|
|
2763
|
-
const legacyProfile = existingProfiles[legacyRoute]
|
|
2764
|
-
if (!previousRoutes.includes(legacyRoute) && !legacyProfile) continue
|
|
2765
|
-
const mode = legacyProfile?.api === 'openai-responses' ? 'responses' : 'chat_completions'
|
|
2766
|
-
const candidates = providers.filter((provider) =>
|
|
2767
|
-
provider.providerId === providerId && provider.protocol === 'openai')
|
|
2768
|
-
const target = candidates.find((provider) => provider.apiMode === mode)
|
|
2769
|
-
|| candidates.find((provider) => provider.apiMode === 'chat_completions')
|
|
2770
|
-
|| candidates[0]
|
|
2771
|
-
if (!target) continue
|
|
2772
|
-
const targetRoute = routeForProvider(target)
|
|
2773
|
-
const configuration = routeConfigurations.get(targetRoute)
|
|
2774
|
-
if (!configuration) continue
|
|
2775
|
-
routeConfigurations.set(legacyRoute, {
|
|
2776
|
-
...configuration,
|
|
2777
|
-
displayName: typeof legacyProfile?.displayName === 'string'
|
|
2778
|
-
? legacyProfile.displayName : `${configuration.displayName} · 旧会话兼容`,
|
|
2779
|
-
})
|
|
2780
|
-
const group = providerGroups.find((item) => item.routes.includes(targetRoute))
|
|
2781
|
-
if (group && !group.routes.includes(legacyRoute)) group.routes.push(legacyRoute)
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
const compatibilityGroup = compatibilityRoutes.length ? [{
|
|
2785
|
-
key: 'legacy-openai-chat', label: 'ANT OpenAI · Chat(旧会话兼容)', routes: compatibilityRoutes,
|
|
2786
|
-
modelCount: defaultOpenaiModels.length,
|
|
2787
|
-
legacy: true,
|
|
2788
|
-
}] : []
|
|
2789
|
-
const allGroups = [...providerGroups, ...compatibilityGroup]
|
|
2909
|
+
const existingProfiles = await readDshProviderProfiles()
|
|
2910
|
+
const profileRoutes = Object.keys(existingProfiles).filter((route) =>
|
|
2911
|
+
isAntModelProviderRoute(route, existingProfiles[route]))
|
|
2912
|
+
const knownRoutes = new Set([...previousRoutes.filter((route) =>
|
|
2913
|
+
isAntModelProviderRoute(route, existingProfiles[route])), ...profileRoutes])
|
|
2914
|
+
const allGroups = providerGroups
|
|
2790
2915
|
let savedExcludedGroups = null
|
|
2791
2916
|
try {
|
|
2792
2917
|
const saved = JSON.parse(localStorage.getItem(providerSyncPreferenceKey(normalized)) || 'null')
|
|
@@ -2794,51 +2919,80 @@ window.__ModuleLoader__.load({
|
|
|
2794
2919
|
} catch {}
|
|
2795
2920
|
let excludedGroups = options.excludedGroups
|
|
2796
2921
|
if (!Array.isArray(excludedGroups) && savedExcludedGroups) excludedGroups = savedExcludedGroups
|
|
2797
|
-
if (!Array.isArray(excludedGroups))
|
|
2798
|
-
|
|
2799
|
-
}
|
|
2800
|
-
excludedGroups = [...new Set(excludedGroups.flatMap((key) =>
|
|
2801
|
-
allGroups.filter((group) => group.key === key || group.legacyKeys?.includes(key)).map((group) => group.key)))]
|
|
2922
|
+
if (!Array.isArray(excludedGroups)) excludedGroups = []
|
|
2923
|
+
excludedGroups = [...new Set(excludedGroups.filter((key) => allGroups.some((group) => group.key === key)))]
|
|
2802
2924
|
const excludedGroupSet = new Set(excludedGroups)
|
|
2803
2925
|
const excluded = new Set(allGroups.filter((group) => excludedGroupSet.has(group.key)).flatMap((group) => group.routes))
|
|
2804
2926
|
const routes = [...routeConfigurations.keys()].filter((route) => !excluded.has(route))
|
|
2805
2927
|
const syncedModels = models.filter((model) => !excluded.has(modelProvider(model)))
|
|
2806
2928
|
if (models.length && !syncedModels.length) throw new Error('请至少保留一个常规模型 provider 同步到本机 DSH。')
|
|
2929
|
+
const staleRoutes = [...knownRoutes].filter((route) => !routes.includes(route))
|
|
2930
|
+
const obsoleteRoutes = staleRoutes.filter((route) => !routeConfigurations.has(route))
|
|
2931
|
+
const excludedRoutes = staleRoutes.filter((route) => routeConfigurations.has(route))
|
|
2932
|
+
const cleanupWarning = obsoleteRoutes.length && excludedRoutes.length
|
|
2933
|
+
? '升级会移除旧路由和已关闭同步的 provider route。历史会话需手动选择仍同步且已获准的 ANT 模型。DSH 会将选择应用到当前会话并在后台设为本机 Profile 默认;保存成功后影响该 Profile 新会话及 ANT 对话/任务。之后可在 ANT「团队模型」页更改本机默认;重新启用 provider 可恢复标准 route。会话正文保留。仍要应用吗?'
|
|
2934
|
+
: obsoleteRoutes.length
|
|
2935
|
+
? '升级会移除旧会话路由。继续历史会话需手动选择已获准并同步的 ANT 模型。DSH 会将选择应用到当前会话并在后台设为本机 Profile 默认;保存成功后影响该 Profile 新会话及 ANT 对话/任务。之后可在 ANT「团队模型」页更改本机默认。会话正文保留。仍要应用吗?'
|
|
2936
|
+
: excludedRoutes.length
|
|
2937
|
+
? '关闭这些 provider 会从本机 DSH 移除对应模型路由,可能影响历史会话。重新勾选并刷新可恢复。仍要应用吗?'
|
|
2938
|
+
: ''
|
|
2939
|
+
if (cleanupWarning && !confirmLocalized(cleanupWarning)) {
|
|
2940
|
+
throw new Error('已取消旧 route 清理;本机 DSH 设置未修改。')
|
|
2941
|
+
}
|
|
2807
2942
|
const defaultMatch = rawDefaultModel && models.find((model) =>
|
|
2808
|
-
|
|
2943
|
+
model.provider_id === rawDefaultModel.provider_id
|
|
2809
2944
|
&& model.protocol === rawDefaultModel.protocol && model.model_id === rawDefaultModel.model_id)
|
|
2810
2945
|
const syncedDefaultMatch = defaultMatch && syncedModels.some((model) => modelSelectionKey(model) === modelSelectionKey(defaultMatch))
|
|
2811
2946
|
? defaultMatch : null
|
|
2812
2947
|
const serverDefault = syncedDefaultMatch || null
|
|
2813
2948
|
const initialDefault = syncedDefaultMatch || (rawDefaultModel == null ? syncedModels[0] : null)
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
if (needsConfirmation && !window.confirm(confirmationMessage)) {
|
|
2823
|
-
throw new Error('已取消同步范围更改;本机 DSH 设置未修改。')
|
|
2824
|
-
}
|
|
2825
|
-
const currentSelection = await readDshDefaultSelection()
|
|
2826
|
-
if (currentSelection?.provider && excluded.has(currentSelection.provider)) {
|
|
2827
|
-
const replacement = modelForProviderSelection(currentSelection, syncedModels, serverDefault) || serverDefault || syncedModels[0]
|
|
2828
|
-
if (!replacement) throw new Error('请先选择一个保留同步的模型,再移除当前 DSH 默认 provider。')
|
|
2829
|
-
await writeDshDefaultModel(replacement)
|
|
2830
|
-
}
|
|
2949
|
+
const currentSelection = await readDshDefaultSelection()
|
|
2950
|
+
let selectionChangedForRouteCleanup = null
|
|
2951
|
+
if (isAntModelProviderRoute(currentSelection?.provider, existingProfiles[currentSelection?.provider])
|
|
2952
|
+
&& !routes.includes(currentSelection.provider)) {
|
|
2953
|
+
const replacement = serverDefault || syncedModels[0]
|
|
2954
|
+
if (!replacement) throw new Error('请先配置一个获准并同步的 provider,再清理旧默认 route。')
|
|
2955
|
+
await writeDshDefaultModel(replacement)
|
|
2956
|
+
selectionChangedForRouteCleanup = currentSelection
|
|
2831
2957
|
}
|
|
2832
|
-
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2833
|
-
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2834
2958
|
for (const route of knownRoutes) {
|
|
2835
2959
|
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2836
2960
|
}
|
|
2837
2961
|
for (const [route, value] of routeConfigurations) {
|
|
2838
2962
|
ops.push({ op: 'set', path: ['providers', route], value })
|
|
2839
2963
|
}
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2964
|
+
const restoreDefaultAfterRouteWriteFailure = async (failure) => {
|
|
2965
|
+
if (!selectionChangedForRouteCleanup) return
|
|
2966
|
+
const previous = selectionChangedForRouteCleanup
|
|
2967
|
+
try {
|
|
2968
|
+
const restore = await remote.settings.mutate('agent-default-model', [
|
|
2969
|
+
{ op: 'set', path: ['provider'], value: previous.provider },
|
|
2970
|
+
{ op: 'set', path: ['model'], value: previous.model },
|
|
2971
|
+
previous.reasoningEffort
|
|
2972
|
+
? { op: 'set', path: ['reasoningEffort'], value: previous.reasoningEffort }
|
|
2973
|
+
: { op: 'unset', path: ['reasoningEffort'] },
|
|
2974
|
+
], undefined)
|
|
2975
|
+
if (!restore?.ok) throw new Error(restore?.error?.message || 'DSH 无法恢复全局默认模型。')
|
|
2976
|
+
setDshDefaultSelection(previous)
|
|
2977
|
+
const previousModel = modelForProviderSelection(previous, syncedModels)
|
|
2978
|
+
setDefaultModelDraft(previousModel ? modelSelectionKey(previousModel) : '')
|
|
2979
|
+
} catch (rollbackError) {
|
|
2980
|
+
const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : 'DSH 无法恢复全局默认模型。'
|
|
2981
|
+
throw new Error(`${failure.message || 'DSH 无法更新团队模型设置。'} 本机默认模型也未能恢复:${rollbackMessage}`)
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
let settingsWrite
|
|
2985
|
+
try {
|
|
2986
|
+
settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
2987
|
+
} catch (error) {
|
|
2988
|
+
await restoreDefaultAfterRouteWriteFailure(error)
|
|
2989
|
+
throw error
|
|
2990
|
+
}
|
|
2991
|
+
if (!settingsWrite?.ok) {
|
|
2992
|
+
const error = new Error(settingsWrite?.error?.message || 'DSH 无法更新团队模型设置。')
|
|
2993
|
+
await restoreDefaultAfterRouteWriteFailure(error)
|
|
2994
|
+
throw error
|
|
2995
|
+
}
|
|
2842
2996
|
saveImageInputOptOuts(normalized, imageOptOuts)
|
|
2843
2997
|
setImageInputOptOuts({ serviceUrl: normalized, modelKeys: imageOptOuts })
|
|
2844
2998
|
setAppliedImageInputOptOuts(imageOptOuts)
|
|
@@ -2849,7 +3003,9 @@ window.__ModuleLoader__.load({
|
|
|
2849
3003
|
setProviderSyncDraft(excludedGroups)
|
|
2850
3004
|
setServerDefaultModel(serverDefault)
|
|
2851
3005
|
const preference = readDefaultPreference(normalized)
|
|
2852
|
-
let statusMessage =
|
|
3006
|
+
let statusMessage = obsoleteRoutes.length
|
|
3007
|
+
? '旧兼容 route 已清理;继续旧会话需手动选择已获准并同步的 ANT 模型。DSH 会在后台保存为本机 Profile 默认,成功后影响新会话及 ANT 对话/任务;之后可在 ANT「团队模型」页更改默认。'
|
|
3008
|
+
: ''
|
|
2853
3009
|
let selection
|
|
2854
3010
|
if (!preference?.initialized && initialDefault) {
|
|
2855
3011
|
const previousDefault = await readDshDefaultSelection()
|
|
@@ -2860,7 +3016,7 @@ window.__ModuleLoader__.load({
|
|
|
2860
3016
|
: '当前无法使用团队默认模型;已用首个获准模型初始化 DSH 全局默认。请管理员检查默认设置和成员授权。'
|
|
2861
3017
|
} else {
|
|
2862
3018
|
selection = await readDshDefaultSelection()
|
|
2863
|
-
const currentAuthorized = modelForProviderSelection(selection, syncedModels
|
|
3019
|
+
const currentAuthorized = modelForProviderSelection(selection, syncedModels)
|
|
2864
3020
|
const baseline = syncedModels.find((model) => modelSelectionKey(model) === preference?.serverDefault)
|
|
2865
3021
|
const stillUsingBaseline = baseline && currentAuthorized && modelSelectionKey(currentAuthorized) === modelSelectionKey(baseline)
|
|
2866
3022
|
if (currentAuthorized && selection?.provider !== modelProvider(currentAuthorized)) {
|
|
@@ -2870,7 +3026,8 @@ window.__ModuleLoader__.load({
|
|
|
2870
3026
|
selection = await writeDshDefaultModel(serverDefault)
|
|
2871
3027
|
saveDefaultPreference(normalized, { ...preference, initialized: true, serverDefault: modelSelectionKey(serverDefault) })
|
|
2872
3028
|
statusMessage = '团队默认模型已更新;你尚未设置个人覆盖,因此已同步到 DSH 全局默认。'
|
|
2873
|
-
} else if (selection?.provider?.
|
|
3029
|
+
} else if (isAntModelProviderRoute(selection?.provider, existingProfiles[selection?.provider])
|
|
3030
|
+
&& !currentAuthorized && syncedModels.length) {
|
|
2874
3031
|
const fallback = serverDefault || syncedModels[0]
|
|
2875
3032
|
selection = await writeDshDefaultModel(fallback)
|
|
2876
3033
|
saveDefaultPreference(normalized, { ...preference, initialized: true,
|
|
@@ -2895,12 +3052,17 @@ window.__ModuleLoader__.load({
|
|
|
2895
3052
|
return { count: syncedModels.length, defaultSelection: selection }
|
|
2896
3053
|
}
|
|
2897
3054
|
|
|
2898
|
-
const requestTeamModelCatalog = async (version) => {
|
|
3055
|
+
const requestTeamModelCatalog = async (version = '3') => {
|
|
2899
3056
|
const response = await fetch('/api/ant-teams/models', {
|
|
2900
3057
|
method: 'POST', headers: { 'x-ant-model-provider-config': version },
|
|
2901
3058
|
})
|
|
2902
3059
|
return { response, result: await readLocalJsonResponse(response) }
|
|
2903
3060
|
}
|
|
3061
|
+
const requestCompatibleTeamModelCatalog = async () => {
|
|
3062
|
+
const current = await requestTeamModelCatalog('3')
|
|
3063
|
+
if (!current.response.ok || current.result.model_provider_config_version === 3) return current
|
|
3064
|
+
return requestTeamModelCatalog('2')
|
|
3065
|
+
}
|
|
2904
3066
|
|
|
2905
3067
|
const refreshModels = async (applyDraft = false) => {
|
|
2906
3068
|
if (!bindingReady) {
|
|
@@ -2911,19 +3073,13 @@ window.__ModuleLoader__.load({
|
|
|
2911
3073
|
setModelMessage('正在从 ANT 刷新获准模型…')
|
|
2912
3074
|
try {
|
|
2913
3075
|
await requireCompatibleService(serviceUrl, true)
|
|
2914
|
-
|
|
3076
|
+
const { response, result } = await requestCompatibleTeamModelCatalog()
|
|
2915
3077
|
if (!response.ok) {
|
|
2916
3078
|
if (response.status === 401) setNeedsReconnect(true)
|
|
2917
3079
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
2918
3080
|
}
|
|
2919
|
-
if (result.model_provider_config_version
|
|
2920
|
-
|
|
2921
|
-
response = fallbackCatalog.response
|
|
2922
|
-
result = fallbackCatalog.result
|
|
2923
|
-
if (!response.ok) {
|
|
2924
|
-
if (response.status === 401) setNeedsReconnect(true)
|
|
2925
|
-
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
2926
|
-
}
|
|
3081
|
+
if (![2, 3].includes(result.model_provider_config_version)) {
|
|
3082
|
+
throw new Error('服务端不支持 provider-config v2,请先升级 ANT 服务端。')
|
|
2927
3083
|
}
|
|
2928
3084
|
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model,
|
|
2929
3085
|
applyDraft ? { excludedGroups: providerSyncDraft } : {})
|
|
@@ -3087,10 +3243,15 @@ window.__ModuleLoader__.load({
|
|
|
3087
3243
|
if (outcome.application === 'failed' || outcome.application === 'cancelled') {
|
|
3088
3244
|
const reason = outcome.error?.diagnostic || outcome.packageResult?.output || '安装未完成。'
|
|
3089
3245
|
if (outcome.error?.code === 'incompatible-version') {
|
|
3246
|
+
const runtimeVersion = typeof outcome.error?.runtimeVersion === 'string'
|
|
3247
|
+
? outcome.error.runtimeVersion : ''
|
|
3248
|
+
const compatibilityMessage = runtimeVersion
|
|
3249
|
+
? translateLocaleText('DSH 安装器判定当前客户端 {runtime_version} 与此 ANT 版本不兼容:{reason}',
|
|
3250
|
+
{ runtime_version: runtimeVersion, reason })
|
|
3251
|
+
: translateLocaleText('DSH 安装器判定当前客户端与此 ANT 版本不兼容:{reason}', { reason })
|
|
3090
3252
|
setDshCompatibility((current) => ({ ...current, state: 'incompatible',
|
|
3091
|
-
runtime_version:
|
|
3092
|
-
message:
|
|
3093
|
-
&& outcome.error.runtimeVersion ? ` ${outcome.error.runtimeVersion} ` : ''}与此 ANT 版本不兼容:${reason}` }))
|
|
3253
|
+
runtime_version: runtimeVersion || current.runtime_version,
|
|
3254
|
+
message: compatibilityMessage }))
|
|
3094
3255
|
}
|
|
3095
3256
|
setBundleUpdate((current) => ({ ...current, state: 'failed', message: `更新失败:${reason}` }))
|
|
3096
3257
|
return
|
|
@@ -3122,11 +3283,13 @@ window.__ModuleLoader__.load({
|
|
|
3122
3283
|
|
|
3123
3284
|
const clearLocalConnection = async () => {
|
|
3124
3285
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('DSH 未提供模型设置接口。')
|
|
3286
|
+
const existingProfiles = await readDshProviderProfiles()
|
|
3287
|
+
const isManagedRoute = (route) => isAntModelProviderRoute(route, existingProfiles[route])
|
|
3125
3288
|
const selection = await readDshDefaultSelection()
|
|
3126
|
-
if (selection?.provider
|
|
3289
|
+
if (isManagedRoute(selection?.provider)) {
|
|
3127
3290
|
const previous = readDefaultPreference(serviceUrl)?.previousDefault
|
|
3128
3291
|
const restore = previous && typeof previous.provider === 'string' && typeof previous.model === 'string'
|
|
3129
|
-
&& !previous.provider
|
|
3292
|
+
&& !isManagedRoute(previous.provider)
|
|
3130
3293
|
const operations = restore
|
|
3131
3294
|
? [
|
|
3132
3295
|
{ op: 'set', path: ['provider'], value: previous.provider },
|
|
@@ -3145,21 +3308,7 @@ window.__ModuleLoader__.load({
|
|
|
3145
3308
|
], undefined)
|
|
3146
3309
|
if (!reset.ok) throw new Error(reset.error?.message || 'DSH 全局默认模型未能恢复。')
|
|
3147
3310
|
}
|
|
3148
|
-
|
|
3149
|
-
try {
|
|
3150
|
-
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
3151
|
-
if (Array.isArray(parsed)) managedRoutes = parsed.filter((route) => typeof route === 'string')
|
|
3152
|
-
} catch {}
|
|
3153
|
-
if (typeof remote?.settings?.describe === 'function') {
|
|
3154
|
-
const described = await remote.settings.describe()
|
|
3155
|
-
if (!described?.ok) throw new Error(described?.error?.message || '无法读取 DSH 中的 ANT 提供方列表。')
|
|
3156
|
-
const providers = described.value?.namespaces?.find((item) => item.ns === 'llm-pi-ai')?.value?.providers
|
|
3157
|
-
for (const [route, provider] of Object.entries(providers || {})) {
|
|
3158
|
-
if (route.startsWith('ant-team-') && provider?.apiKeyEnv === 'ANT_MODEL_API_KEY') managedRoutes.push(route)
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
const routes = new Set([...managedRoutes, 'ant-team-openai-chat-completions', 'ant-team-openai-responses',
|
|
3162
|
-
'ant-team-openai', 'ant-team-anthropic'])
|
|
3311
|
+
const routes = Object.keys(existingProfiles).filter(isManagedRoute)
|
|
3163
3312
|
const settingsResult = await remote.settings.mutate('llm-pi-ai', [...routes].map((route) => ({
|
|
3164
3313
|
op: 'unset', path: ['providers', route],
|
|
3165
3314
|
})), undefined)
|
|
@@ -3203,7 +3352,7 @@ window.__ModuleLoader__.load({
|
|
|
3203
3352
|
}
|
|
3204
3353
|
|
|
3205
3354
|
const clearOnlyLocal = async () => {
|
|
3206
|
-
if (!
|
|
3355
|
+
if (!confirmLocalized('仅清理这台 DSH 的 ANT 凭证和模型设置,不保证服务端设备令牌已撤销。请先让原团队管理员确认远端撤销;否则令牌可能仍可被使用。确定继续吗?')) return
|
|
3207
3356
|
setBusy(true)
|
|
3208
3357
|
try {
|
|
3209
3358
|
await clearLocalConnection()
|
|
@@ -3289,7 +3438,7 @@ window.__ModuleLoader__.load({
|
|
|
3289
3438
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
3290
3439
|
const response = await fetch('/api/ant-teams/request-code', {
|
|
3291
3440
|
method: 'POST',
|
|
3292
|
-
headers: { 'Content-Type': 'application/json' },
|
|
3441
|
+
headers: { 'Content-Type': 'application/json', 'X-Ant-Locale': localeRuntime?.getLocale?.().active || 'en' },
|
|
3293
3442
|
body: JSON.stringify({ service_url: normalized, email: normalizedEmail }),
|
|
3294
3443
|
})
|
|
3295
3444
|
const result = await readLocalJsonResponse(response)
|
|
@@ -3332,7 +3481,7 @@ window.__ModuleLoader__.load({
|
|
|
3332
3481
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
3333
3482
|
const response = await fetch('/api/ant-teams/connect', {
|
|
3334
3483
|
method: 'POST',
|
|
3335
|
-
headers: { 'Content-Type': 'application/json', 'x-ant-model-provider-config': '
|
|
3484
|
+
headers: { 'Content-Type': 'application/json', 'x-ant-model-provider-config': '3' },
|
|
3336
3485
|
body: JSON.stringify({
|
|
3337
3486
|
service_url: normalized,
|
|
3338
3487
|
email: normalizedEmail,
|
|
@@ -3352,12 +3501,16 @@ window.__ModuleLoader__.load({
|
|
|
3352
3501
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
3353
3502
|
credentialSaved = true
|
|
3354
3503
|
let modelCatalog = result
|
|
3355
|
-
if (result.model_provider_config_version !==
|
|
3356
|
-
const
|
|
3357
|
-
if (!
|
|
3358
|
-
|
|
3504
|
+
if (result.model_provider_config_version !== 3) {
|
|
3505
|
+
const fallback = await requestTeamModelCatalog('2')
|
|
3506
|
+
if (!fallback.response.ok) {
|
|
3507
|
+
if (fallback.response.status === 401) setNeedsReconnect(true)
|
|
3508
|
+
throw new Error(fallback.result.message || `刷新失败(${fallback.response.status})`)
|
|
3359
3509
|
}
|
|
3360
|
-
modelCatalog =
|
|
3510
|
+
modelCatalog = fallback.result
|
|
3511
|
+
}
|
|
3512
|
+
if (![2, 3].includes(modelCatalog.model_provider_config_version)) {
|
|
3513
|
+
throw new Error('服务端不支持 provider-config v2,请先升级 ANT 服务端。')
|
|
3361
3514
|
}
|
|
3362
3515
|
await applyAuthorizedModels(modelCatalog.models, normalized, modelCatalog.default_model)
|
|
3363
3516
|
setAuthorizedModels(modelCatalog.models)
|
|
@@ -3370,7 +3523,7 @@ window.__ModuleLoader__.load({
|
|
|
3370
3523
|
setAllowLocalCleanup(false)
|
|
3371
3524
|
setNeedsReconnect(false)
|
|
3372
3525
|
setReauthenticate(false)
|
|
3373
|
-
setMessage(`已连接 ${result.email || normalizedEmail},同步 ${
|
|
3526
|
+
setMessage(`已连接 ${result.email || normalizedEmail},同步 ${modelCatalog.models.length} 个获准模型。它们现已出现在 DSH 模型选择器中。`)
|
|
3374
3527
|
} catch (error) {
|
|
3375
3528
|
if (credentialSaved) {
|
|
3376
3529
|
setCredentialConfigured(true)
|
|
@@ -3406,7 +3559,7 @@ window.__ModuleLoader__.load({
|
|
|
3406
3559
|
&& imageInputOptOutSet.has(modelSelectionKey(model)) !== appliedImageInputOptOutSet.has(modelSelectionKey(model)))
|
|
3407
3560
|
const hasPendingSyncChanges = [...new Set([...providerSyncDraft, ...appliedProviderSyncExclusions])]
|
|
3408
3561
|
.some((key) => excludedGroupSet.has(key) !== appliedExcludedGroupSet.has(key))
|
|
3409
|
-
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !
|
|
3562
|
+
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !excludedGroupSet.has(group.key)).length
|
|
3410
3563
|
const pendingServerDefaultRemoval = hasPendingSyncChanges && serverDefaultModel
|
|
3411
3564
|
? providerSyncGroups.some((group) => group.routes.includes(modelProvider(serverDefaultModel))
|
|
3412
3565
|
&& !appliedExcludedGroupSet.has(group.key) && excludedGroupSet.has(group.key))
|
|
@@ -3429,7 +3582,7 @@ window.__ModuleLoader__.load({
|
|
|
3429
3582
|
return h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
3430
3583
|
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(190px, auto)',
|
|
3431
3584
|
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
3432
|
-
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, `${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id}`),
|
|
3585
|
+
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, rawLocaleText(`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id}`)),
|
|
3433
3586
|
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
3434
3587
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
3435
3588
|
model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
|
|
@@ -3445,28 +3598,6 @@ window.__ModuleLoader__.load({
|
|
|
3445
3598
|
h('span', { style: mutedTextStyle }, imageInputEnabled ? '本机允许图片输入' : '本机已关闭图片输入'))
|
|
3446
3599
|
: h('span', { style: mutedTextStyle }, '团队未开放图片输入')))
|
|
3447
3600
|
})
|
|
3448
|
-
const legacyCompatibilityGroup = providerSyncGroups.find((group) => group.legacy)
|
|
3449
|
-
const legacyCompatibilityModels = legacyCompatibilityGroup
|
|
3450
|
-
? authorizedModels.filter((model) => model.protocol === 'openai'
|
|
3451
|
-
&& (model.provider_id || 'openai-default') === 'openai-default') : []
|
|
3452
|
-
const legacyCompatibilityExcluded = legacyCompatibilityGroup
|
|
3453
|
-
? excludedGroupSet.has(legacyCompatibilityGroup.key) : true
|
|
3454
|
-
const legacyCompatibilityAppliedExcluded = legacyCompatibilityGroup
|
|
3455
|
-
? appliedExcludedGroupSet.has(legacyCompatibilityGroup.key) : true
|
|
3456
|
-
const legacyCompatibilityStatus = legacyCompatibilityExcluded === legacyCompatibilityAppliedExcluded
|
|
3457
|
-
? legacyCompatibilityExcluded ? '未同步到 DSH' : '已同步到 DSH'
|
|
3458
|
-
: legacyCompatibilityExcluded ? '刷新后从 DSH 移除' : '刷新后同步到 DSH'
|
|
3459
|
-
const legacyCompatibilityRows = legacyCompatibilityModels.map((model) => h('div', {
|
|
3460
|
-
key: `legacy-openai-chat:${model.provider_id || ''}:${model.model_id}`, role: 'listitem',
|
|
3461
|
-
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
|
|
3462
|
-
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' },
|
|
3463
|
-
},
|
|
3464
|
-
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } },
|
|
3465
|
-
`${model.display_name || model.model_id}(旧会话兼容)`),
|
|
3466
|
-
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
3467
|
-
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
3468
|
-
'OpenAI · Chat(旧会话兼容)'),
|
|
3469
|
-
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, legacyCompatibilityStatus)))
|
|
3470
3601
|
const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
3471
3602
|
h('div', null,
|
|
3472
3603
|
h('h3', { style: sectionTitleStyle }, 'ANT 团队账号'),
|
|
@@ -3485,7 +3616,7 @@ window.__ModuleLoader__.load({
|
|
|
3485
3616
|
h('button', { type: 'button', disabled: busy || !bindingReady,
|
|
3486
3617
|
onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
3487
3618
|
h('button', { type: 'button', disabled: busy,
|
|
3488
|
-
onClick: () => { if (
|
|
3619
|
+
onClick: () => { if (confirmLocalized('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
3489
3620
|
style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
3490
3621
|
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
3491
3622
|
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
@@ -3499,7 +3630,7 @@ window.__ModuleLoader__.load({
|
|
|
3499
3630
|
onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle }),
|
|
3500
3631
|
h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
3501
3632
|
credentialConfigured === true && showConnectForm ? h('button', { type: 'button', disabled: busy,
|
|
3502
|
-
onClick: () => { if (
|
|
3633
|
+
onClick: () => { if (confirmLocalized('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
3503
3634
|
style: secondaryButtonStyle }, '断开当前设备') : null,
|
|
3504
3635
|
allowLocalCleanup ? h('section', { style: { ...settingsPanelStyle, padding: 14, gap: 9 } },
|
|
3505
3636
|
h('p', { style: mutedTextStyle }, savedDeviceId()
|
|
@@ -3549,7 +3680,7 @@ window.__ModuleLoader__.load({
|
|
|
3549
3680
|
: model.provider_name
|
|
3550
3681
|
const protocolName = model.protocol === 'anthropic' ? 'Anthropic' : `OpenAI ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`
|
|
3551
3682
|
return h('option', { key: modelSelectionKey(model), value: modelSelectionKey(model) },
|
|
3552
|
-
`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`)
|
|
3683
|
+
rawLocaleText(`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`))
|
|
3553
3684
|
}))),
|
|
3554
3685
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
3555
3686
|
h('button', { type: 'button', disabled: defaultModelBusy || !selectedDefaultDraft || hasPendingSyncChanges,
|
|
@@ -3564,20 +3695,16 @@ window.__ModuleLoader__.load({
|
|
|
3564
3695
|
defaultModelMessage ? h('p', { role: 'status', style: mutedTextStyle }, defaultModelMessage) : null),
|
|
3565
3696
|
providerSyncGroups.length ? h('section', { style: { ...infoNoticeStyle, gap: 8 } },
|
|
3566
3697
|
h('strong', null, '同步到本机 DSH 的 provider'),
|
|
3567
|
-
h('p', { style: mutedTextStyle }, '
|
|
3568
|
-
h('p', { style: mutedTextStyle }, '
|
|
3698
|
+
h('p', { style: mutedTextStyle }, '取消勾选并刷新后,该 provider 会从本机 DSH 移除;使用它的历史会话可能需要重新选择模型。'),
|
|
3699
|
+
h('p', { style: mutedTextStyle }, '至少保留一个获准 provider。'),
|
|
3569
3700
|
hasPendingSyncChanges ? h('p', { role: 'status', style: mutedTextStyle }, '同步范围尚未应用;点击“刷新团队模型”后生效。') : null,
|
|
3570
|
-
...providerSyncGroups.map((group) => {
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|| (!group.legacy && !excludedGroupSet.has(group.key) && activePrimaryGroupCount <= 1),
|
|
3578
|
-
onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
|
|
3579
|
-
h('span', null, `${label} · ${group.modelCount} 个模型`))
|
|
3580
|
-
})) : null,
|
|
3701
|
+
...providerSyncGroups.map((group) => h('label', { key: group.key,
|
|
3702
|
+
style: { display: 'flex', alignItems: 'center', gap: 9, padding: '5px 0', cursor: busy ? 'default' : 'pointer' } },
|
|
3703
|
+
h('input', { type: 'checkbox', 'aria-label': group.label,
|
|
3704
|
+
checked: !excludedGroupSet.has(group.key), disabled: busy
|
|
3705
|
+
|| (!excludedGroupSet.has(group.key) && activePrimaryGroupCount <= 1),
|
|
3706
|
+
onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
|
|
3707
|
+
h('span', null, `${group.label} · ${group.modelCount} 个模型`)))) : null,
|
|
3581
3708
|
hasPendingImageInputChanges ? h('p', { role: 'status', style: mutedTextStyle },
|
|
3582
3709
|
'本机图片输入偏好尚未同步;点击“刷新团队模型”后生效。') : null,
|
|
3583
3710
|
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
@@ -3587,15 +3714,10 @@ window.__ModuleLoader__.load({
|
|
|
3587
3714
|
: '连接并验证工作邮箱后,这里会列出逐个获准模型。'),
|
|
3588
3715
|
credentialConfigured !== true ? h('button', { type: 'button', onClick: () => onSelectTab('connection'),
|
|
3589
3716
|
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
3590
|
-
legacyCompatibilityGroup ? h('section', { style: { ...infoNoticeStyle, gap: 6, marginTop: 14 } },
|
|
3591
|
-
h('strong', null, `旧会话兼容路由 · ${legacyCompatibilityModels.length} 个模型`),
|
|
3592
|
-
h('p', { style: mutedTextStyle }, '复用已获准的 OpenAI 模型,仅供已有会话使用,不增加获准模型数量。'),
|
|
3593
|
-
h('div', { role: 'list', 'aria-label': '旧会话兼容模型',
|
|
3594
|
-
style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } }, legacyCompatibilityRows)) : null,
|
|
3595
3717
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
3596
3718
|
h('button', { type: 'button', disabled: busy || defaultModelBusy || !bindingReady || credentialConfigured !== true,
|
|
3597
3719
|
onClick: () => void refreshModels(true), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
3598
|
-
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString(
|
|
3720
|
+
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString(formatLocale())}`) : null),
|
|
3599
3721
|
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
3600
3722
|
|
|
3601
3723
|
const updateStatusLabels = {
|
|
@@ -3682,6 +3804,7 @@ window.__ModuleLoader__.load({
|
|
|
3682
3804
|
}
|
|
3683
3805
|
|
|
3684
3806
|
function EnterpriseExtensionsPage({ remote, onBack, openBundle }) {
|
|
3807
|
+
useLocaleRevision()
|
|
3685
3808
|
const [tab, setTab] = useState('all')
|
|
3686
3809
|
const [query, setQuery] = useState('')
|
|
3687
3810
|
const navigation = {
|
|
@@ -3885,6 +4008,52 @@ window.__ModuleLoader__.load({
|
|
|
3885
4008
|
|
|
3886
4009
|
function apply(ctx) {
|
|
3887
4010
|
ctx.effect(() => installEnterpriseBranding(ctx), 'ANT enterprise branding synchronization')
|
|
4011
|
+
ctx.effect(() => {
|
|
4012
|
+
const runtime = ctx.locale
|
|
4013
|
+
localeRuntime = runtime
|
|
4014
|
+
localeTranslate = runtime.bind('ant.team')
|
|
4015
|
+
let disposed = false
|
|
4016
|
+
const removers = []
|
|
4017
|
+
void fetch(ANT_LOCALE_MESSAGES_PATH, { cache: 'no-store' }).then(async (response) => {
|
|
4018
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
4019
|
+
return response.json()
|
|
4020
|
+
}).then((catalogs) => {
|
|
4021
|
+
const catalogMetadata = catalogs?.locales
|
|
4022
|
+
const sourceMessages = catalogs?.messages
|
|
4023
|
+
if (disposed || !catalogMetadata?.en || !catalogMetadata?.zh || !sourceMessages) return
|
|
4024
|
+
localeMetadata = catalogMetadata
|
|
4025
|
+
const messagesFor = (id) => Object.fromEntries(Object.entries(sourceMessages)
|
|
4026
|
+
.map(([source, translations]) => [source, translations[id] ?? translations.en ?? source]))
|
|
4027
|
+
localeMessagePatterns = Object.keys(sourceMessages)
|
|
4028
|
+
.filter((key) => /\{[a-zA-Z][a-zA-Z0-9_]*\}/u.test(key))
|
|
4029
|
+
.sort((a, b) => b.length - a.length)
|
|
4030
|
+
.map(compileLocalePattern)
|
|
4031
|
+
removers.push(runtime.register('ant.team', {
|
|
4032
|
+
en: messagesFor('en'),
|
|
4033
|
+
zh: messagesFor('zh'),
|
|
4034
|
+
}))
|
|
4035
|
+
const registered = new Set(runtime.getLocale().locales.map((item) => item.id.toLowerCase()))
|
|
4036
|
+
for (const id of ANT_LOCALE_IDS) {
|
|
4037
|
+
const catalog = localeMetadata[id]
|
|
4038
|
+
if (!catalog) continue
|
|
4039
|
+
if (!registered.has(id.toLowerCase())) {
|
|
4040
|
+
removers.push(runtime.addLanguage({ id, label: catalog.label, fallback: 'en' }))
|
|
4041
|
+
registered.add(id.toLowerCase())
|
|
4042
|
+
}
|
|
4043
|
+
removers.push(runtime.register('ant.team', id, messagesFor(id)))
|
|
4044
|
+
}
|
|
4045
|
+
}).catch((error) => ctx.logger?.warn?.('ANT locale catalogs unavailable:', error?.message))
|
|
4046
|
+
return () => {
|
|
4047
|
+
disposed = true
|
|
4048
|
+
for (const remove of removers.reverse()) remove()
|
|
4049
|
+
if (localeRuntime === runtime) {
|
|
4050
|
+
localeRuntime = null
|
|
4051
|
+
localeMetadata = {}
|
|
4052
|
+
localeMessagePatterns = []
|
|
4053
|
+
localeTranslate = (key) => key
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
}, 'ANT locale catalog registration')
|
|
3888
4057
|
const remote = {
|
|
3889
4058
|
credentials: ctx.remote.credentials,
|
|
3890
4059
|
settings: ctx.remote.settings,
|