dsh-ant 0.5.12 → 0.6.0
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 +3 -3
- package/client.js +515 -372
- package/conversation.js +73 -22
- package/delegation.js +95 -40
- package/index.js +23 -1
- 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 +20054 -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,
|
|
@@ -584,6 +656,7 @@ window.__ModuleLoader__.load({
|
|
|
584
656
|
const [loadFailures, setLoadFailures] = useState([])
|
|
585
657
|
const [createOpen, setCreateOpen] = useState(false)
|
|
586
658
|
const [message, setMessage] = useState('')
|
|
659
|
+
const [submissionError, setSubmissionError] = useState('')
|
|
587
660
|
const [draftNotice, setDraftNotice] = useState('')
|
|
588
661
|
const load = async () => {
|
|
589
662
|
setLoading(true)
|
|
@@ -688,15 +761,19 @@ window.__ModuleLoader__.load({
|
|
|
688
761
|
const cardReviewerSummary = (card) => {
|
|
689
762
|
const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
|
|
690
763
|
const unavailable = selected.filter((runner) => runner !== 'dsh')
|
|
691
|
-
if (card.runner !== 'dsh') return
|
|
764
|
+
if (card.runner !== 'dsh') return translateLocaleText('{executor} 默认执行方暂不可用',
|
|
765
|
+
{ executor: reviewerLabel(card.runner) })
|
|
692
766
|
return unavailable.length
|
|
693
|
-
?
|
|
694
|
-
|
|
767
|
+
? translateLocaleText('默认 DSH;{executors} 暂不可用',
|
|
768
|
+
{ executors: unavailable.map(reviewerLabel).join('、') })
|
|
769
|
+
: translateLocaleText('DSH 任务 Agent')
|
|
695
770
|
}
|
|
696
771
|
const requesterSummary = senderScope === 'allowlist'
|
|
697
|
-
?
|
|
698
|
-
|
|
699
|
-
|
|
772
|
+
? translateLocaleText('仅指定成员:{member_ids}',
|
|
773
|
+
{ member_ids: senderIds.trim() || translateLocaleText('尚未填写成员 ID') })
|
|
774
|
+
: translateLocaleText(conversationKind === 'group' ? '群内所有成员' : '任何能私聊此机器人的人')
|
|
775
|
+
const resultRouteSummary = translateLocaleText(conversationKind === 'group'
|
|
776
|
+
? '回复到原群(群内可见)' : '回复到发起任务的私聊')
|
|
700
777
|
const ruleSummaryCurrent = recognizedDescriptionRef.current === ruleDescription.trim()
|
|
701
778
|
const ruleSummaryPending = Boolean(ruleDescription.trim() && !ruleSummaryCurrent)
|
|
702
779
|
const ruleSummaryReady = Boolean(ruleSummaryCurrent && (title || intent || parameters.length || taskPrompt))
|
|
@@ -734,8 +811,9 @@ window.__ModuleLoader__.load({
|
|
|
734
811
|
const ruleSummaryNarrative = ruleSummaryReady
|
|
735
812
|
? `${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
|
|
736
813
|
: '识别后会用一段摘要呈现系统理解的任务。'
|
|
737
|
-
const executorSummary = runner === 'codex' ? 'DSH Codex 子 Agent'
|
|
814
|
+
const executorSummary = translateLocaleText(runner === 'codex' ? 'DSH Codex 子 Agent'
|
|
738
815
|
: runner === 'claude-code' ? 'DSH Claude Code 子 Agent' : 'DSH · 当前授权模型'
|
|
816
|
+
)
|
|
739
817
|
const parallelAgentsSummary = reviewers.filter((item) => item !== runner).map(reviewerLabel).join('、')
|
|
740
818
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
741
819
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
@@ -757,7 +835,7 @@ window.__ModuleLoader__.load({
|
|
|
757
835
|
? 'PR 来源或仓库尚未从自然语言描述中明确识别,请补充完整的 PR 来源链接或仓库。'
|
|
758
836
|
: parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
759
837
|
&& selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)
|
|
760
|
-
?
|
|
838
|
+
? `${translateLocaleText('任务描述中的 Gitea 来源必须与此机器人的连接器主机一致({origin});为保护令牌,ANT 不会向其他主机发送凭据。', { origin: selectedGiteaOrigin })} ${translateLocaleText('请在描述中改用该主机下的 PR 链接并重新分析。')}`
|
|
761
839
|
: taskPrOrigins.length > 1
|
|
762
840
|
? '同一任务中的 PR 资源必须来自同一个 Gitea 主机。'
|
|
763
841
|
: nativeReviewerSelected && (parameters.filter((rule) => rule.kind === 'gitea_pr').length !== 1
|
|
@@ -785,6 +863,7 @@ window.__ModuleLoader__.load({
|
|
|
785
863
|
}
|
|
786
864
|
const startNewCard = (context = {}) => {
|
|
787
865
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
866
|
+
setSubmissionError('')
|
|
788
867
|
setDraftNotice('')
|
|
789
868
|
setRecognitionMessage('')
|
|
790
869
|
recognizedBaselineRef.current = null
|
|
@@ -821,6 +900,7 @@ window.__ModuleLoader__.load({
|
|
|
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
906
|
autoAnalyzeDescriptionRef.current = null
|
|
@@ -961,7 +1041,7 @@ window.__ModuleLoader__.load({
|
|
|
961
1041
|
if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
|
|
962
1042
|
const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
963
1043
|
&& card.conversation_kind === 'group' && card.conversation_id === targetGroupId)
|
|
964
|
-
if (!
|
|
1044
|
+
if (!confirmLocalized(enabled
|
|
965
1045
|
? targetHasTasks
|
|
966
1046
|
? '这个群当前因任务授权可以聊天。要在任务到期后仍允许普通聊天吗?聊天会使用团队模型额度。'
|
|
967
1047
|
: '允许这个群与机器人自然聊天?群成员仍不能执行未开放的能力,聊天会使用团队模型额度。'
|
|
@@ -984,7 +1064,7 @@ window.__ModuleLoader__.load({
|
|
|
984
1064
|
}
|
|
985
1065
|
const saveDmChat = async (enabled) => {
|
|
986
1066
|
if (!selectedBot || !selectedEnrollmentBound || !dmChatSettingAvailable) return
|
|
987
|
-
if (!
|
|
1067
|
+
if (!confirmLocalized(enabled
|
|
988
1068
|
? '开启后,此机器人会在私聊中正常对话;私聊任务仍逐项按已开放规则核验。'
|
|
989
1069
|
: '关闭后,新私聊消息不会进入 DSH,包括普通聊天、任务请求和状态查询。关闭前已提交的任务不会被撤销,仍按原私聊回复。已开放的私聊能力不会删除;重新开启后,未过期的能力会恢复触发。群聊设置不受影响。')) return
|
|
990
1070
|
setBusy(true)
|
|
@@ -1087,20 +1167,25 @@ window.__ModuleLoader__.load({
|
|
|
1087
1167
|
const createCard = async (event) => {
|
|
1088
1168
|
event.preventDefault()
|
|
1089
1169
|
setDraftNotice('')
|
|
1090
|
-
|
|
1170
|
+
setSubmissionError('')
|
|
1171
|
+
const reject = (reason) => {
|
|
1172
|
+
const localizedReason = translateLocaleText(reason)
|
|
1173
|
+
setSubmissionError(localizedReason); setMessage(localizedReason)
|
|
1174
|
+
}
|
|
1175
|
+
if (!selectedBot || !botConnected || !selectedEnrollmentBound) { reject('请先在这台 DSH 绑定选中的协作机器人。'); return }
|
|
1091
1176
|
if (reviewers.some((item) => item !== 'dsh')) {
|
|
1092
|
-
|
|
1177
|
+
reject('当前无法核验外部子 Agent 的逐任务文件、Git 和网络权限;请将任务执行方设为 DSH。'); return
|
|
1093
1178
|
}
|
|
1094
1179
|
if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
|
|
1095
|
-
|
|
1180
|
+
reject('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
|
|
1096
1181
|
}
|
|
1097
1182
|
if (!ruleDescription.trim() || !title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
1098
|
-
|
|
1183
|
+
reject('请填写任务描述并点击“识别规则”,检查摘要后再开放。'); return
|
|
1099
1184
|
}
|
|
1100
1185
|
const bytes = (value) => new TextEncoder().encode(value).length
|
|
1101
1186
|
if (bytes(ruleDescription.trim()) > 4000 || bytes(title.trim()) > 100
|
|
1102
1187
|
|| bytes(intent.trim()) > 1000 || bytes(taskPrompt.trim()) > 4000) {
|
|
1103
|
-
|
|
1188
|
+
reject('任务描述或识别摘要过长,请缩短描述并重新识别。'); return
|
|
1104
1189
|
}
|
|
1105
1190
|
const normalized = parameters.map((rule) => ({ name: rule.name.trim(), kind: rule.kind,
|
|
1106
1191
|
description: rule.description.trim() || null,
|
|
@@ -1112,21 +1197,21 @@ window.__ModuleLoader__.load({
|
|
|
1112
1197
|
? (typeof rule.gitea_origin === 'string' ? normalizeServiceUrl(rule.gitea_origin) : null) : null,
|
|
1113
1198
|
repository: rule.kind === 'gitea_pr' ? rule.repository.trim().toLowerCase() : null }))
|
|
1114
1199
|
if (normalized.some((rule) => !/^[A-Za-z][A-Za-z0-9_]{0,31}$/u.test(rule.name))) {
|
|
1115
|
-
|
|
1200
|
+
reject('没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
|
|
1116
1201
|
}
|
|
1117
1202
|
if (new Set(normalized.map((rule) => rule.name)).size !== normalized.length) {
|
|
1118
|
-
|
|
1203
|
+
reject('请求信息识别有重复项。请修改任务描述并重新识别。'); return
|
|
1119
1204
|
}
|
|
1120
1205
|
if (normalized.some((rule) => rule.name === 'head_sha' && rule.kind !== 'git_sha')) {
|
|
1121
|
-
|
|
1206
|
+
reject('head_sha 是 PR 提交版本的保留字段;请在自然语言描述中使用 PR Head SHA。'); return
|
|
1122
1207
|
}
|
|
1123
1208
|
if (normalized.some((rule) => rule.description && bytes(rule.description) > 100)) {
|
|
1124
|
-
|
|
1209
|
+
reject('请求信息说明过长。请缩短任务描述并重新识别。'); return
|
|
1125
1210
|
}
|
|
1126
1211
|
if (normalized.some((rule) => rule.kind === 'choice' && (!rule.choices?.length || rule.choices.length > 32
|
|
1127
1212
|
|| rule.choices.some((choice) => bytes(choice) > 100)
|
|
1128
1213
|
|| new Set(rule.choices).size !== rule.choices.length))) {
|
|
1129
|
-
|
|
1214
|
+
reject('描述中的可选范围不清楚或重复。请修改描述并重新识别。'); return
|
|
1130
1215
|
}
|
|
1131
1216
|
if (normalized.some((rule) => (rule.min != null && !Number.isInteger(rule.min))
|
|
1132
1217
|
|| (rule.max != null && !Number.isInteger(rule.max))
|
|
@@ -1134,19 +1219,19 @@ window.__ModuleLoader__.load({
|
|
|
1134
1219
|
|| (rule.kind === 'text' && rule.max != null && (rule.max < 1 || rule.max > 4096))
|
|
1135
1220
|
|| (rule.kind === 'gitea_pr' && ((rule.min != null && rule.min < 1)
|
|
1136
1221
|
|| (rule.max != null && rule.max > 1_000_000_000))))) {
|
|
1137
|
-
|
|
1222
|
+
reject('描述中的请求范围无效。请明确范围或提供完整 PR 链接,再重新识别。'); return
|
|
1138
1223
|
}
|
|
1139
1224
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && (!rule.gitea_origin || !rule.repository))) {
|
|
1140
|
-
|
|
1225
|
+
reject('没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
|
|
1141
1226
|
}
|
|
1142
1227
|
if (new Set(normalized.filter((rule) => rule.kind === 'gitea_pr')
|
|
1143
1228
|
.map((rule) => normalizeServiceUrl(rule.gitea_origin)).filter(Boolean)).size > 1) {
|
|
1144
|
-
|
|
1229
|
+
reject('同一任务中的 PR 资源必须来自同一个 Gitea 主机。')
|
|
1145
1230
|
return
|
|
1146
1231
|
}
|
|
1147
1232
|
if (normalized.some((rule) => rule.kind === 'gitea_pr'
|
|
1148
1233
|
&& selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)) {
|
|
1149
|
-
|
|
1234
|
+
reject(`${translateLocaleText('任务描述中的 Gitea 来源必须与此机器人的连接器主机一致({origin});为保护令牌,ANT 不会向其他主机发送凭据。', { origin: selectedGiteaOrigin })} ${translateLocaleText('请在描述中改用该主机下的 PR 链接并重新分析。')}`)
|
|
1150
1235
|
return
|
|
1151
1236
|
}
|
|
1152
1237
|
const validRepository = (value) => {
|
|
@@ -1155,31 +1240,38 @@ window.__ModuleLoader__.load({
|
|
|
1155
1240
|
&& /^[A-Za-z0-9_.-]{1,100}$/u.test(part))
|
|
1156
1241
|
}
|
|
1157
1242
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && !validRepository(rule.repository))) {
|
|
1158
|
-
|
|
1243
|
+
reject('仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
|
|
1159
1244
|
}
|
|
1160
1245
|
if (!reviewers.length || !reviewers.includes(runner)) {
|
|
1161
|
-
|
|
1246
|
+
reject('请至少选择一位任务执行 Agent,并设置普通请求的默认执行方。'); return
|
|
1162
1247
|
}
|
|
1163
1248
|
if (nativeReviewerSelected && !((normalized.length === 1
|
|
1164
1249
|
&& normalized[0].kind === 'gitea_pr' && normalized[0].required)
|
|
1165
1250
|
|| (normalized.length === 2 && normalized.some((rule) => rule.kind === 'gitea_pr' && rule.required)
|
|
1166
1251
|
&& normalized.some((rule) => rule.kind === 'git_sha' && rule.name === 'head_sha' && !rule.required)))) {
|
|
1167
|
-
|
|
1252
|
+
reject('Codex 和 Claude Code 当前只支持一个必填 Gitea PR 资源连接器;任务行为仍由自然语言描述决定。'); return
|
|
1168
1253
|
}
|
|
1169
|
-
const scope = conversationKind === 'group'
|
|
1170
|
-
|
|
1254
|
+
const scope = conversationKind === 'group'
|
|
1255
|
+
? translateLocaleText('群 {group_id}', { group_id: groupId })
|
|
1256
|
+
: translateLocaleText('私聊')
|
|
1257
|
+
const resultDestination = translateLocaleText(conversationKind === 'group'
|
|
1258
|
+
? '原群(群内可见)' : '发起任务的私聊')
|
|
1171
1259
|
const executionDetails = {
|
|
1172
|
-
dsh: 'DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。',
|
|
1173
|
-
codex: 'Codex 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
|
|
1174
|
-
'claude-code': 'Claude Code 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
|
|
1260
|
+
dsh: translateLocaleText('DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。'),
|
|
1261
|
+
codex: translateLocaleText('Codex 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。'),
|
|
1262
|
+
'claude-code': translateLocaleText('Claude Code 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。'),
|
|
1175
1263
|
}
|
|
1176
1264
|
const executionInfo = reviewers.length > 1
|
|
1177
|
-
?
|
|
1265
|
+
? translateLocaleText('普通请求默认由 {runner} 执行;请求人明确要求多 Agent 分别处理时,会并行启动 {reviewers},分别返回结果。{details}',
|
|
1266
|
+
{ runner: reviewerLabel(runner), reviewers: reviewers.map(reviewerLabel).join('、'),
|
|
1267
|
+
details: reviewers.map((item) => executionDetails[item]).join(' ') })
|
|
1178
1268
|
: executionDetails[reviewers[0]]
|
|
1179
1269
|
const actionDescription = editingCardId
|
|
1180
|
-
?
|
|
1181
|
-
|
|
1182
|
-
|
|
1270
|
+
? translateLocaleText('更新“{title}”后,{requesterSummary}可在{scope}按任务描述中的规则发起请求。旧待确认请求将失效,已提交任务继续使用提交时的配置。结果{resultDestination}。到期时间保持为 {expiry}。',
|
|
1271
|
+
{ title, requesterSummary, scope, resultDestination, expiry: formatLocaleDate(editingExpiresAt * 1000) })
|
|
1272
|
+
: translateLocaleText('开放“{title}”后,{requesterSummary}可在{scope}按任务描述中的规则发起请求,有效期 {days} 天;结果{resultDestination}。',
|
|
1273
|
+
{ title, requesterSummary, scope, resultDestination, days: formatLocaleNumber(days) })
|
|
1274
|
+
if (!confirmLocalized(`${actionDescription}${executionInfo}${translateLocaleText('请确认。')}`)) return
|
|
1183
1275
|
setBusy(true)
|
|
1184
1276
|
try {
|
|
1185
1277
|
const response = await fetch('/api/ant-teams/delegation/cards', {
|
|
@@ -1227,14 +1319,15 @@ window.__ModuleLoader__.load({
|
|
|
1227
1319
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1228
1320
|
await load()
|
|
1229
1321
|
setCreateOpen(false)
|
|
1322
|
+
setSubmissionError('')
|
|
1230
1323
|
setMessage(editingCardId
|
|
1231
1324
|
? '能力配置已更新。新请求将使用新配置,已提交任务保留原配置。'
|
|
1232
1325
|
: '任务已开放。机器人会按已确认的描述判断请求;缺少必要信息时会向请求人追问。')
|
|
1233
|
-
} catch (error) {
|
|
1326
|
+
} catch (error) { reject(error.message || translateLocaleText('能力保存失败。')) }
|
|
1234
1327
|
finally { setBusy(false) }
|
|
1235
1328
|
}
|
|
1236
1329
|
const revokeCard = async (card) => {
|
|
1237
|
-
if (!
|
|
1330
|
+
if (!confirmLocalized(`停止开放“${card.title}”?正在处理的任务无法撤销已经发生的读取。`)) return
|
|
1238
1331
|
setBusy(true)
|
|
1239
1332
|
try {
|
|
1240
1333
|
const response = await fetch('/api/ant-teams/delegation/cards', {
|
|
@@ -1256,7 +1349,9 @@ window.__ModuleLoader__.load({
|
|
|
1256
1349
|
? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
|
|
1257
1350
|
: '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
|
|
1258
1351
|
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
1259
|
-
const titleChars = Array.from(
|
|
1352
|
+
const titleChars = Array.from(translateLocaleText('复制:{title}', {
|
|
1353
|
+
title: card.title || translateLocaleText('未命名能力'),
|
|
1354
|
+
}))
|
|
1260
1355
|
while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
|
|
1261
1356
|
const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
|
|
1262
1357
|
recognizedBaselineRef.current = taskDraft
|
|
@@ -1278,7 +1373,7 @@ window.__ModuleLoader__.load({
|
|
|
1278
1373
|
onSelectTab('collaboration')
|
|
1279
1374
|
}
|
|
1280
1375
|
const cancelTask = async (task) => {
|
|
1281
|
-
if (!
|
|
1376
|
+
if (!confirmLocalized(`取消任务 ${task.task_id}?已经发生的读取不能撤回。`)) return
|
|
1282
1377
|
setBusy(true)
|
|
1283
1378
|
try {
|
|
1284
1379
|
const response = await fetch('/api/ant-teams/delegation/cancel', { method: 'POST',
|
|
@@ -1300,7 +1395,8 @@ window.__ModuleLoader__.load({
|
|
|
1300
1395
|
onChange: (event) => { setBotId(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1301
1396
|
h('option', { value: '' }, '请选择机器人'),
|
|
1302
1397
|
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1303
|
-
|
|
1398
|
+
disabled: !bot.available || !bot.connected }, rawLocaleText(bot.name),
|
|
1399
|
+
bot.available && bot.connected ? null : '(未连接)')))) : null,
|
|
1304
1400
|
connectedBots.length === 0 && !loading && !botLoadError ? h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1305
1401
|
h('button', { type: 'button', onClick: openImSettings, style: secondaryButtonStyle }, '打开 DSH-IM 设置'),
|
|
1306
1402
|
h('span', { style: mutedTextStyle }, '先在 DSH 的插件列表安装并连接飞书机器人。')) : null,
|
|
@@ -1327,13 +1423,13 @@ window.__ModuleLoader__.load({
|
|
|
1327
1423
|
&& card.conversation_kind === 'group' && card.conversation_id === group.group_id).length
|
|
1328
1424
|
return h('article', { key: group.group_id, style: { ...collaborationCardStyle, padding: 10, gap: 8 } },
|
|
1329
1425
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1330
|
-
h('strong', { title: `完整识别码 ${group.group_code}` }, groupLabel(group)),
|
|
1426
|
+
h('strong', { title: `完整识别码 ${group.group_code}` }, rawLocaleText(groupLabel(group))),
|
|
1331
1427
|
h('span', { style: mutedTextStyle }, `已开放任务 ${taskCount} 项`)),
|
|
1332
1428
|
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
1333
1429
|
h('input', { type: 'checkbox', checked: group.chat_enabled,
|
|
1334
1430
|
disabled: busy || groupChatLoadFailed,
|
|
1335
1431
|
onChange: (event) => void saveGroupChat(event.target.checked, group.group_id),
|
|
1336
|
-
'aria-label': '允许群 '
|
|
1432
|
+
'aria-label': translateLocaleText('允许群 {group} 普通聊天', { group: groupLabel(group) }) }), '普通聊天'),
|
|
1337
1433
|
h('button', { type: 'button', disabled: busy, style: secondaryButtonStyle,
|
|
1338
1434
|
onClick: () => { startNewCard({ conversationKind: 'group', groupId: group.group_id }); focusAfterTabChange('collaboration', 'ant-new-task-summary') } },
|
|
1339
1435
|
'为此群开放任务'))
|
|
@@ -1343,7 +1439,9 @@ window.__ModuleLoader__.load({
|
|
|
1343
1439
|
return h('section', { style: { ...settingsPanelStyle,
|
|
1344
1440
|
display: ['collaboration', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
1345
1441
|
'aria-label': '群聊协作设置' },
|
|
1346
|
-
message
|
|
1442
|
+
message && !(activeTab === 'collaboration' && createOpen
|
|
1443
|
+
&& submissionError && message === submissionError)
|
|
1444
|
+
? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1347
1445
|
h('div', { id: 'ant-chat-entry', role: 'region', 'aria-label': '聊天入口和机器人状态', tabIndex: -1,
|
|
1348
1446
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1349
1447
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
@@ -1368,7 +1466,8 @@ window.__ModuleLoader__.load({
|
|
|
1368
1466
|
h('div', { id: 'ant-capability-list', role: 'region', 'aria-label': '已开放任务规则', tabIndex: -1,
|
|
1369
1467
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1370
1468
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap' } },
|
|
1371
|
-
h('h4', { style: sectionTitleStyle }, '我的任务规则 · ' +
|
|
1469
|
+
h('h4', { style: sectionTitleStyle }, '我的任务规则 · ' +
|
|
1470
|
+
(authorizationLoadError ? translateLocaleText('暂未读取') : activeCards.length)),
|
|
1372
1471
|
h('div', { role: 'toolbar', 'aria-label': '任务规则操作', style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
1373
1472
|
h('button', { type: 'button', onClick: () => { startNewCard(); focusAfterTabChange('collaboration', 'ant-new-task-summary') },
|
|
1374
1473
|
style: primaryButtonStyle }, '新建任务规则'))),
|
|
@@ -1377,12 +1476,15 @@ window.__ModuleLoader__.load({
|
|
|
1377
1476
|
...activeCards.map((card) => {
|
|
1378
1477
|
const expiresAt = Number(card.expires_at) * 1000
|
|
1379
1478
|
const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
|
|
1380
|
-
const expiry = remainingDays <= 7
|
|
1479
|
+
const expiry = remainingDays <= 7
|
|
1480
|
+
? translateLocaleText('{days} 天后到期', { days: formatLocaleNumber(remainingDays) })
|
|
1481
|
+
: translateLocaleText('至 {expiry_date}', { expiry_date: formatLocaleDate(expiresAt, { dateStyle: 'short' }) })
|
|
1381
1482
|
const conversation = card.conversation_kind === 'group'
|
|
1382
|
-
?
|
|
1383
|
-
&& group.group_id === card.conversation_id)) || card.conversation_id}`
|
|
1384
|
-
|
|
1385
|
-
|
|
1483
|
+
? `${translateLocaleText('群')} ${groupLabel(groups.find((group) => group.bot_fingerprint === card.bot_fingerprint
|
|
1484
|
+
&& group.group_id === card.conversation_id)) || card.conversation_id}`
|
|
1485
|
+
: translateLocaleText('私聊')
|
|
1486
|
+
const requester = translateLocaleText(card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1487
|
+
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求')
|
|
1386
1488
|
const blockedExternalExecutor = card.runner !== 'dsh' || card.cross_review_always === true
|
|
1387
1489
|
const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
|
|
1388
1490
|
const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
|
|
@@ -1391,28 +1493,33 @@ window.__ModuleLoader__.load({
|
|
|
1391
1493
|
return h('article', { key: card.id, style: { ...collaborationCardStyle, padding: 12, gap: 8 } },
|
|
1392
1494
|
h('div', { style: { display: 'grid', gap: 6, minWidth: 0 } },
|
|
1393
1495
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1394
|
-
h('strong', { style: { overflowWrap: 'anywhere' } },
|
|
1496
|
+
h('strong', { style: { overflowWrap: 'anywhere' } },
|
|
1497
|
+
card.title ? rawLocaleText(card.title) : '未命名能力'),
|
|
1395
1498
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
|
|
1396
1499
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
|
|
1397
|
-
h('span', { style: mutedTextStyle },
|
|
1500
|
+
h('span', { style: mutedTextStyle }, translateLocaleText(
|
|
1501
|
+
'{reviewers} · {conversation} · {requesters} · 结果回复原{destination}', {
|
|
1502
|
+
reviewers: cardReviewerSummary(card), conversation, requesters: requester,
|
|
1503
|
+
destination: translateLocaleText(card.conversation_kind === 'group' ? '群' : '私聊'),
|
|
1504
|
+
})),
|
|
1398
1505
|
blockedExternalExecutor ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1399
1506
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1400
1507
|
'此任务暂不可执行:当前无法核验外部 Agent 的逐任务权限。点击“编辑”,改用 DSH 并移除外部 Agent 后可重新开放。') : null,
|
|
1401
1508
|
card.rule_description
|
|
1402
|
-
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description)
|
|
1509
|
+
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, rawLocaleText(card.rule_description))
|
|
1403
1510
|
: h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1404
1511
|
'旧任务卡没有自然语言描述。请编辑并补充描述,让 AI 生成 Harness 后再开放。'),
|
|
1405
1512
|
h('details', null,
|
|
1406
1513
|
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看规则说明'),
|
|
1407
1514
|
h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
|
|
1408
|
-
|
|
1409
|
-
requesterIds ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } },
|
|
1410
|
-
examplesText ? h('p', { style: mutedTextStyle },
|
|
1515
|
+
card.rule_description ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, rawLocaleText(card.rule_description)) : null,
|
|
1516
|
+
requesterIds ? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, '允许请求的 IM 用户:', rawLocaleText(requesterIds)) : null,
|
|
1517
|
+
examplesText ? h('p', { style: mutedTextStyle }, '请求示例:', rawLocaleText(examplesText)) : null,
|
|
1411
1518
|
Array.isArray(card.parameters) && card.parameters.length
|
|
1412
1519
|
? h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } },
|
|
1413
1520
|
`请求时还需提供:${card.parameters.map(parameterSummary).join(';')}`)
|
|
1414
1521
|
: h('p', { style: mutedTextStyle }, '无需额外参数,按请求中的内容处理'),
|
|
1415
|
-
card.task_prompt ? h('p', { style: mutedTextStyle },
|
|
1522
|
+
card.task_prompt ? h('p', { style: mutedTextStyle }, '处理要求:', rawLocaleText(card.task_prompt)) : null))),
|
|
1416
1523
|
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end', paddingTop: 4,
|
|
1417
1524
|
borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1418
1525
|
h('button', { type: 'button', disabled: busy, onClick: () => editCard(card), style: secondaryButtonStyle }, '编辑'),
|
|
@@ -1429,20 +1536,38 @@ window.__ModuleLoader__.load({
|
|
|
1429
1536
|
),
|
|
1430
1537
|
h('div', { role: 'region', 'aria-label': '最近任务', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1431
1538
|
h('h4', { style: sectionTitleStyle }, '最近任务'),
|
|
1539
|
+
h('p', { style: mutedTextStyle }, translateLocaleText('本页显示任务失败原因和结果发送状态;完整结果仍回复到原群或私聊。')),
|
|
1432
1540
|
...(tasks.filter((task) => task.task_id).length ? tasks.filter((task) => task.task_id)
|
|
1433
1541
|
.map((task) => {
|
|
1434
|
-
const title = task.title
|
|
1435
|
-
?
|
|
1436
|
-
|
|
1542
|
+
const title = task.title ? rawLocaleText(task.title) : task.pr_number
|
|
1543
|
+
? task.repository
|
|
1544
|
+
? translateLocaleText('{repository} PR #{pr_number} 差异审查', { repository: task.repository, pr_number: task.pr_number })
|
|
1545
|
+
: translateLocaleText('PR #{pr_number} 差异审查', { pr_number: task.pr_number })
|
|
1546
|
+
: '任务'
|
|
1547
|
+
const status = translateLocaleText(task.outcome === 'canceled' ? '已取消' : task.outcome === 'failed' ? '未完成' : ({
|
|
1437
1548
|
pending: '排队中', running: '处理中', reply_pending: '结果待发送', reply_attempted: '回复状态待核对', done: '已回复',
|
|
1438
|
-
}[task.status] || task.status)
|
|
1549
|
+
}[task.status] || task.status))
|
|
1439
1550
|
const createdAt = task.created_at && Number.isFinite(Date.parse(task.created_at))
|
|
1440
|
-
?
|
|
1551
|
+
? formatLocaleDate(task.created_at) : ''
|
|
1552
|
+
const taskStatusLine = translateLocaleText('{status}{time_label} · 任务 {task_id}', {
|
|
1553
|
+
status, time_label: createdAt ? ` · ${createdAt}` : '', task_id: task.task_id,
|
|
1554
|
+
})
|
|
1441
1555
|
return h('div', { key: task.task_id, style: { display: 'flex', gap: 12, alignItems: 'center',
|
|
1442
1556
|
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' } },
|
|
1557
|
+
h('div', { style: { display: 'grid', gap: 6, flex: '1 1 280px', minWidth: 0, overflowWrap: 'anywhere' } },
|
|
1444
1558
|
h('strong', null, title),
|
|
1445
|
-
h('div', { style: mutedTextStyle },
|
|
1559
|
+
h('div', { style: mutedTextStyle }, taskStatusLine),
|
|
1560
|
+
task.failure_reason ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1561
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1562
|
+
'任务未完成:{reason}\n处理建议:按错误提示修复配置或请求内容后,在原群或私聊重新发起。',
|
|
1563
|
+
{ reason: task.failure_reason }))) : null,
|
|
1564
|
+
task.delivery_error ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1565
|
+
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1566
|
+
'结果发送状态未确认:{reason}\n处理建议:先检查原群或私聊是否已收到结果;若未收到,再修复连接问题后重试。',
|
|
1567
|
+
{ reason: task.delivery_error }))) : null,
|
|
1568
|
+
task.status === 'reply_attempted' && !task.delivery_error ? h('div', { role: 'status', style: warningNoticeStyle },
|
|
1569
|
+
h('span', { style: mutedTextStyle }, translateLocaleText(
|
|
1570
|
+
'ANT 尚未确认消息平台是否收到结果。请先检查原群或私聊,再决定是否重新发起。'))) : null),
|
|
1446
1571
|
['card_task', 'legacy_pr_task'].includes(task.kind) && ['pending', 'running'].includes(task.status) ? h('button', { type: 'button', disabled: busy,
|
|
1447
1572
|
onClick: () => void cancelTask(task), style: secondaryButtonStyle }, '取消任务') : null)
|
|
1448
1573
|
})
|
|
@@ -1457,7 +1582,7 @@ window.__ModuleLoader__.load({
|
|
|
1457
1582
|
gap: 12, alignItems: 'start', marginTop: 12 } },
|
|
1458
1583
|
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
1459
1584
|
editingCardId ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1460
|
-
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${
|
|
1585
|
+
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${formatLocaleDate(editingExpiresAt * 1000)}。已提交任务继续使用原配置。`) : null,
|
|
1461
1586
|
h('p', { style: mutedTextStyle }, selectedBot
|
|
1462
1587
|
? `当前机器人:${selectedBot.name}${selectedEnrollmentBound ? ' · 已绑定此设备' : ' · 请先绑定到此设备'}`
|
|
1463
1588
|
: '请先在上方选择机器人并启用协作。'),
|
|
@@ -1544,7 +1669,7 @@ window.__ModuleLoader__.load({
|
|
|
1544
1669
|
onChange: (event) => setGroupId(event.target.value), style: inputStyle },
|
|
1545
1670
|
h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
|
|
1546
1671
|
...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
|
|
1547
|
-
groupLabel(group))))),
|
|
1672
|
+
rawLocaleText(groupLabel(group)))))),
|
|
1548
1673
|
h('button', { type: 'button', disabled: busy,
|
|
1549
1674
|
onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
|
|
1550
1675
|
h('label', null, '谁可以请求', h('select', { value: senderScope,
|
|
@@ -1555,7 +1680,7 @@ window.__ModuleLoader__.load({
|
|
|
1555
1680
|
required: true, value: senderIds, onChange: (event) => setSenderIds(event.target.value),
|
|
1556
1681
|
placeholder: '多个 ID 用逗号分隔', style: inputStyle })) : null,
|
|
1557
1682
|
editingCardId ? h('p', { style: { ...mutedTextStyle, alignSelf: 'end' } },
|
|
1558
|
-
`有效期:${
|
|
1683
|
+
`有效期:${formatLocaleDate(editingExpiresAt * 1000)}(编辑时保持不变)`)
|
|
1559
1684
|
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1560
1685
|
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
|
|
1561
1686
|
(runner === 'codex' || runner === 'claude-code') ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
@@ -1568,9 +1693,19 @@ window.__ModuleLoader__.load({
|
|
|
1568
1693
|
h('h4', { style: sectionTitleStyle }, '开放前看一眼'),
|
|
1569
1694
|
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1570
1695
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '草稿 · 未开放')),
|
|
1571
|
-
h('p', { style: mutedTextStyle },
|
|
1572
|
-
|
|
1573
|
-
|
|
1696
|
+
h('p', { style: mutedTextStyle }, translateLocaleText(
|
|
1697
|
+
'这项任务将在{conversation}开放给{requesters},由{executor}执行;有效期 {expiry},{result_destination}。',
|
|
1698
|
+
{
|
|
1699
|
+
conversation: conversationKind === 'group'
|
|
1700
|
+
? translateLocaleText('群“{group_name}”',
|
|
1701
|
+
{ group_name: selectedGroupSummary || translateLocaleText('尚未选择') })
|
|
1702
|
+
: translateLocaleText('私聊'),
|
|
1703
|
+
requesters: requesterSummary,
|
|
1704
|
+
executor: executorSummary,
|
|
1705
|
+
expiry: editingCardId ? formatLocaleDate(editingExpiresAt * 1000)
|
|
1706
|
+
: `${formatLocaleNumber(days)} ${translateLocaleText('天')}`,
|
|
1707
|
+
result_destination: resultRouteSummary,
|
|
1708
|
+
})),
|
|
1574
1709
|
hasPrParameter && !selectedGiteaOrigin && taskPrOrigins.length === 1
|
|
1575
1710
|
? h('p', { role: 'note', style: mutedTextStyle },
|
|
1576
1711
|
`首次确认开放后,这台机器人会绑定到 ${taskPrOrigins[0]};后续 PR 任务只允许使用此主机,令牌不会发往其他主机。`)
|
|
@@ -1579,10 +1714,17 @@ window.__ModuleLoader__.load({
|
|
|
1579
1714
|
? h('p', { role: 'note', style: mutedTextStyle },
|
|
1580
1715
|
`这台机器人已绑定 ${selectedGiteaOrigin};任务来源需与此主机一致,令牌不会发往其他主机。`)
|
|
1581
1716
|
: null,
|
|
1582
|
-
cardBlockReason ? h('
|
|
1583
|
-
|
|
1717
|
+
cardBlockReason ? h('div', { id: 'ant-capability-block-reason', role: 'alert', 'aria-live': 'assertive',
|
|
1718
|
+
style: warningNoticeStyle }, h('p', { style: mutedTextStyle }, cardBlockReason)) : null,
|
|
1719
|
+
submissionError ? h('div', { id: 'ant-capability-submission-error', role: 'alert', 'aria-live': 'assertive', style: warningNoticeStyle },
|
|
1720
|
+
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1721
|
+
'开放失败:{reason}\n处理建议:按错误提示修正配置后重试;若提示版本不兼容,请管理员升级 ANT 插件或服务端。',
|
|
1722
|
+
{ reason: submissionError }))) : null,
|
|
1584
1723
|
h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
|
|
1585
|
-
|
|
1724
|
+
'aria-describedby': cardBlockReason ? 'ant-capability-block-reason'
|
|
1725
|
+
: submissionError ? 'ant-capability-submission-error' : undefined,
|
|
1726
|
+
style: { ...primaryButtonStyle, width: '100%' } }, busy ? translateLocaleText('正在处理…')
|
|
1727
|
+
: editingCardId ? '保存能力修改' : '确认并开放能力'))))),
|
|
1586
1728
|
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1587
1729
|
showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
|
|
1588
1730
|
h('strong', null, 'PR 只读凭据(本机)'),
|
|
@@ -1702,15 +1844,20 @@ window.__ModuleLoader__.load({
|
|
|
1702
1844
|
}
|
|
1703
1845
|
const changeInstall = async (skill, remove = false) => {
|
|
1704
1846
|
const action = remove ? '卸载' : skill.local_status === 'update-available' ? '更新' : '安装'
|
|
1847
|
+
const localizedAction = translateLocaleText(action)
|
|
1705
1848
|
const reportInDetail = selectedSkill?.id === skill.id
|
|
1706
1849
|
const prompt = remove
|
|
1707
1850
|
? skill.conflict_reason === 'other-source'
|
|
1708
1851
|
? '这台 DSH 已安装另一个来源的同名技能。确认卸载本机旧来源?已修改的本地文件不会被删除。'
|
|
1709
|
-
:
|
|
1710
|
-
:
|
|
1711
|
-
|
|
1852
|
+
: translateLocaleText('从这台 DSH 卸载 {skill_title}?已修改的本地文件不会被删除。', { skill_title: skill.title })
|
|
1853
|
+
: translateLocaleText('将 {skill_title} {action}到这台 DSH?\n\n外部数据交互:{data_interactions}\n\n本机依赖:{requirements}\n\n技能可能让 DSH 读取文件或执行脚本,仍受 DSH 的工具授权控制。ANT 不会自动安装系统依赖。',
|
|
1854
|
+
{ skill_title: skill.title, action: localizedAction,
|
|
1855
|
+
data_interactions: skill.data_interactions?.trim() || translateLocaleText('待管理员补充;使用前请先核实数据去向。'),
|
|
1856
|
+
requirements: skill.requirements })
|
|
1857
|
+
if (!confirmLocalized(prompt)) return
|
|
1712
1858
|
setWorkingId(skill.id)
|
|
1713
|
-
const progressMessage =
|
|
1859
|
+
const progressMessage = translateLocaleText('正在{action} {skill_title}…',
|
|
1860
|
+
{ action: localizedAction, skill_title: skill.title })
|
|
1714
1861
|
setMessage(progressMessage)
|
|
1715
1862
|
if (reportInDetail) setDetailActionMessage({ skillId: skill.id, text: progressMessage })
|
|
1716
1863
|
try {
|
|
@@ -1787,14 +1934,14 @@ window.__ModuleLoader__.load({
|
|
|
1787
1934
|
} }, h('div', { style: { maxWidth: 1100, margin: '0 auto', display: 'grid', gap: 16 } },
|
|
1788
1935
|
h('button', { type: 'button', onClick: () => { originalGeneration.current += 1; setOriginalView(null) },
|
|
1789
1936
|
style: { ...secondaryButtonStyle, width: 'fit-content' } }, includePlugins ? '← 返回企业扩展' : '← 返回技能中心'),
|
|
1790
|
-
h('h1', { style: { margin: 0, fontSize: 26 } },
|
|
1937
|
+
h('h1', { style: { margin: 0, fontSize: 26 } }, rawLocaleText(originalView.title), ' · ', '原始技能说明'),
|
|
1791
1938
|
h('p', { style: mutedTextStyle }, originalView.partial
|
|
1792
1939
|
? '当前服务端尚未提供完整原文,此处仅显示导入时的英文简介。'
|
|
1793
1940
|
: '这里显示发布时保存的 SKILL.md。它供 DSH 使用,与技能中心的中文介绍分别维护;ANT 可能给原技能名加前缀。'),
|
|
1794
1941
|
originalView.loading ? h('p', { role: 'status', style: mutedTextStyle }, '正在读取原文…')
|
|
1795
1942
|
: originalView.error ? h('p', { role: 'alert', style: mutedTextStyle }, originalView.error)
|
|
1796
1943
|
: 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: {
|
|
1944
|
+
return h('section', { dir: formatLocaleDirection(), 'aria-label': includePlugins ? '企业扩展' : '技能中心', style: {
|
|
1798
1945
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
1799
1946
|
padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
|
|
1800
1947
|
} }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
|
|
@@ -1849,7 +1996,7 @@ window.__ModuleLoader__.load({
|
|
|
1849
1996
|
backgroundColor: 'var(--dsw-alias-bg-layer-1)', boxShadow: '0 24px 80px rgba(0,0,0,.35)' } },
|
|
1850
1997
|
h('header', { style: { display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'start' } },
|
|
1851
1998
|
h('div', { style: { display: 'grid', gap: 8 } },
|
|
1852
|
-
h('h2', { style: { margin: 0, fontSize: 24 } }, selectedSkill.title),
|
|
1999
|
+
h('h2', { style: { margin: 0, fontSize: 24 } }, rawLocaleText(selectedSkill.title)),
|
|
1853
2000
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' } },
|
|
1854
2001
|
h('span', { style: { ...mutedTextStyle, padding: '3px 9px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedSkill.scope === 'market' ? '全市场精选' : '本企业'),
|
|
1855
2002
|
h('span', { style: { ...mutedTextStyle, padding: '3px 9px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(skillCategory(selectedSkill))),
|
|
@@ -1860,16 +2007,21 @@ window.__ModuleLoader__.load({
|
|
|
1860
2007
|
style: { ...secondaryButtonStyle, flex: '0 0 auto' } }, '关闭')),
|
|
1861
2008
|
h('section', { style: { display: 'grid', gap: 7 } },
|
|
1862
2009
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '这个技能能做什么'),
|
|
1863
|
-
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
2010
|
+
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
2011
|
+
skillIntroductionDisplay(selectedSkill, skillIntroduction(selectedSkill)))),
|
|
1864
2012
|
h('section', { style: { display: 'grid', gap: 7, padding: 14, borderRadius: 10,
|
|
1865
2013
|
border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
1866
2014
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '外部数据交互'),
|
|
1867
2015
|
h('p', { style: { ...mutedTextStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' } },
|
|
1868
|
-
selectedSkill.data_interactions?.trim()
|
|
2016
|
+
selectedSkill.data_interactions?.trim()
|
|
2017
|
+
? rawLocaleText(selectedSkill.data_interactions.trim())
|
|
2018
|
+
: '待管理员补充。使用前请核实这项技能是否调用外部服务,以及会发送哪些数据。')),
|
|
1869
2019
|
h('section', { style: { display: 'grid', gap: 7, overflowWrap: 'anywhere' } },
|
|
1870
2020
|
h('h3', { style: { margin: 0, fontSize: 15 } }, '来源与使用条件'),
|
|
1871
|
-
h('p', { style: mutedTextStyle },
|
|
1872
|
-
|
|
2021
|
+
h('p', { style: mutedTextStyle }, '来源:',
|
|
2022
|
+
['GitHub', 'ClawHub'].includes(sourceLabel) ? rawLocaleText(sourceLabel) : sourceLabel,
|
|
2023
|
+
' · 许可:', rawLocaleText(selectedSkill.license)),
|
|
2024
|
+
h('p', { style: mutedTextStyle }, formatLocaleNumber(selectedSkill.file_count), ' 个文件 · 本机条件:', rawLocaleText(selectedSkill.requirements)),
|
|
1873
2025
|
h('p', { style: mutedTextStyle, title: sourceRef }, `固定版本:${versionLabel || '已锁定制品'}`),
|
|
1874
2026
|
englishSource ? h('a', { href: englishSource, target: '_blank', rel: 'noopener noreferrer',
|
|
1875
2027
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, originalEnglish ? '查看英文原文 ↗' : '查看原始说明 ↗')
|
|
@@ -1942,7 +2094,7 @@ window.__ModuleLoader__.load({
|
|
|
1942
2094
|
minHeight: 190, boxSizing: 'border-box', alignContent: 'start' } },
|
|
1943
2095
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 10 } },
|
|
1944
2096
|
h('div', { style: { minWidth: 0, display: 'grid', gap: 7 } },
|
|
1945
|
-
|
|
2097
|
+
h('h2', { style: { margin: 0, fontSize: 18, lineHeight: 1.3, overflowWrap: 'anywhere' } }, rawLocaleText(skill.title)),
|
|
1946
2098
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center' } },
|
|
1947
2099
|
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 999, backgroundColor: 'var(--dsw-alias-bg-layer-2)', fontSize: 11 } }, skill.scope === 'market' ? '全市场精选' : '本企业'),
|
|
1948
2100
|
h('span', { style: { ...mutedTextStyle, fontSize: 12 } }, categoryLabel(skillCategory(skill))))),
|
|
@@ -1950,7 +2102,7 @@ window.__ModuleLoader__.load({
|
|
|
1950
2102
|
? skill.team_connected ? '待启用' : '未连接'
|
|
1951
2103
|
: statusLabel[skill.local_status] || '状态未知')),
|
|
1952
2104
|
h('p', { style: { ...mutedTextStyle, display: '-webkit-box', WebkitBoxOrient: 'vertical',
|
|
1953
|
-
WebkitLineClamp: 2, overflow: 'hidden', minHeight: 42 } }, description),
|
|
2105
|
+
WebkitLineClamp: 2, overflow: 'hidden', minHeight: 42 } }, skillIntroductionDisplay(skill, description)),
|
|
1954
2106
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginTop: 'auto' } },
|
|
1955
2107
|
h('button', { type: 'button', onClick: (event) => {
|
|
1956
2108
|
detailTriggerRef.current = event?.currentTarget || null
|
|
@@ -2044,12 +2196,15 @@ window.__ModuleLoader__.load({
|
|
|
2044
2196
|
|| artifactUrl && local.source !== artifactUrl))
|
|
2045
2197
|
const action = localAhead ? '回退到团队版本' : local ? local.version === plugin.version ? '重新安装' : '更新' : '安装'
|
|
2046
2198
|
const sourceNotice = artifactUrl
|
|
2047
|
-
? 'DSH 将安装 ANT 固定的顶层制品。'
|
|
2048
|
-
: `DSH 将优先从 npm 官方源以 ${installSpec} 安装此精确版本,并按其配置回退 registry;依赖版本可能因设备不同。`
|
|
2049
|
-
const updateNotice = 'ANT 不会批准依赖构建脚本;插件自身的更新器或 DSH 原生管理页之后仍可安装其他版本。'
|
|
2050
|
-
|
|
2199
|
+
? translateLocaleText('DSH 将安装 ANT 固定的顶层制品。')
|
|
2200
|
+
: translateLocaleText(`DSH 将优先从 npm 官方源以 ${installSpec} 安装此精确版本,并按其配置回退 registry;依赖版本可能因设备不同。`)
|
|
2201
|
+
const updateNotice = translateLocaleText('ANT 不会批准依赖构建脚本;插件自身的更新器或 DSH 原生管理页之后仍可安装其他版本。')
|
|
2202
|
+
const installConfirmation = translateLocaleText(
|
|
2203
|
+
'在这台 DSH {action}团队审核的 {plugin_title} {version}?{source_notice}{update_notice} 插件将在本机运行,完成后需要完全重启 DSH。',
|
|
2204
|
+
{ action: translateLocaleText(action), plugin_title: plugin.title, version: plugin.version, source_notice: sourceNotice, update_notice: updateNotice })
|
|
2205
|
+
if (!confirmLocalized(installConfirmation)) return
|
|
2051
2206
|
setWorkingId(plugin.id)
|
|
2052
|
-
setMessage(
|
|
2207
|
+
setMessage(translateLocaleText('正在安装 {plugin_title},请不要退出 DSH…', { plugin_title: plugin.title }))
|
|
2053
2208
|
try {
|
|
2054
2209
|
const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
|
|
2055
2210
|
const catalog = await readLocalJsonResponse(response)
|
|
@@ -2145,9 +2300,12 @@ window.__ModuleLoader__.load({
|
|
|
2145
2300
|
const recovery = await restorePreviousVersion()
|
|
2146
2301
|
if (recovery !== 'restored') setUncertainId(plugin.id)
|
|
2147
2302
|
const recoveryNotice = recovery === 'restored'
|
|
2148
|
-
?
|
|
2149
|
-
|
|
2150
|
-
|
|
2303
|
+
? translateLocaleText('旧版本号和原启用状态已恢复。{source_notice}', {
|
|
2304
|
+
source_notice: previousSourceIsRegistry
|
|
2305
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请联系管理员核对。')}` : '' })
|
|
2306
|
+
: recovery === 'target' ? translateLocaleText(
|
|
2307
|
+
'本机列表显示审核版本 {version} 已安装,请核对后再操作。', { version: plugin.version })
|
|
2308
|
+
: translateLocaleText('无法确认旧版本及启用状态,请先在 DSH 插件管理核对。')
|
|
2151
2309
|
setMessage(`无法开始更新 ${plugin.title}:DSH 未能确认旧版本移除完成。${diagnostic
|
|
2152
2310
|
? `DSH 诊断:${diagnostic}。` : ''}${recoveryNotice}`)
|
|
2153
2311
|
return
|
|
@@ -2174,9 +2332,13 @@ window.__ModuleLoader__.load({
|
|
|
2174
2332
|
if (removedForUpdate) {
|
|
2175
2333
|
const restoreState = await restorePreviousVersion()
|
|
2176
2334
|
restoreNotice = restoreState === 'restored'
|
|
2177
|
-
?
|
|
2178
|
-
|
|
2179
|
-
:
|
|
2335
|
+
? translateLocaleText('DSH 已恢复旧版本号 {version}。{source_notice}', {
|
|
2336
|
+
version: local.version,
|
|
2337
|
+
source_notice: previousSourceIsRegistry
|
|
2338
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请核对来源。')}` : '' })
|
|
2339
|
+
: restoreState === 'target' ? ` ${translateLocaleText(
|
|
2340
|
+
'本机列表显示审核版本 {version} 已安装,但操作结果未确认。', { version: plugin.version })}`
|
|
2341
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2180
2342
|
}
|
|
2181
2343
|
const details = [installFailure && `installBundle:${safeInstallDiagnostic(installFailure)}`,
|
|
2182
2344
|
waitFailure && `waitForInstall:${safeInstallDiagnostic(waitFailure)}`].filter(Boolean)
|
|
@@ -2203,13 +2365,18 @@ window.__ModuleLoader__.load({
|
|
|
2203
2365
|
let restoreNotice = ''
|
|
2204
2366
|
if (updateRestore) {
|
|
2205
2367
|
restoreNotice = ['applied', 'restart-required'].includes(updateRestore.application)
|
|
2206
|
-
? ` DSH 已恢复旧版本
|
|
2368
|
+
? ` ${translateLocaleText('DSH 已恢复旧版本 {version}。', { version: local.version })}`
|
|
2369
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2207
2370
|
} else if (removedForUpdate) {
|
|
2208
2371
|
const restoreState = await restorePreviousVersion()
|
|
2209
2372
|
restoreNotice = restoreState === 'restored'
|
|
2210
|
-
?
|
|
2211
|
-
|
|
2212
|
-
:
|
|
2373
|
+
? translateLocaleText('DSH 已恢复旧版本号 {version}。{source_notice}', {
|
|
2374
|
+
version: local.version,
|
|
2375
|
+
source_notice: previousSourceIsRegistry
|
|
2376
|
+
? ` ${translateLocaleText('恢复按团队目录使用 npm 官方源;若此前来自私有源,请核对来源。')}` : '' })
|
|
2377
|
+
: restoreState === 'target' ? ` ${translateLocaleText(
|
|
2378
|
+
'本机列表显示审核版本 {version} 已安装,但结果未确认。', { version: plugin.version })}`
|
|
2379
|
+
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2213
2380
|
}
|
|
2214
2381
|
setMessage(`${plugin.title} 安装未完成(请求编号 ${requestReference})。${diagnostic
|
|
2215
2382
|
? `DSH 诊断:${diagnostic}。` : 'DSH 没有返回失败诊断。'}${restoreNotice}请在 DSH 插件管理查看状态或联系管理员。`)
|
|
@@ -2343,14 +2510,15 @@ window.__ModuleLoader__.load({
|
|
|
2343
2510
|
? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
|
|
2344
2511
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
2345
2512
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
2346
|
-
h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
|
|
2513
|
+
h('h2', { style: { margin: 0, fontSize: 19 } }, rawLocaleText(plugin.title)),
|
|
2347
2514
|
embedded ? h('span', { style: mutedTextStyle }, '插件') : null,
|
|
2348
2515
|
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
2349
2516
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(categoryOf(plugin)))),
|
|
2350
2517
|
h('span', { style: security
|
|
2351
2518
|
? { ...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} ·
|
|
2519
|
+
h('p', { style: mutedTextStyle }, rawLocaleText(plugin.description)),
|
|
2520
|
+
h('p', { style: mutedTextStyle }, rawLocaleText(`${plugin.package_name}@${plugin.version}`), ' · 许可:',
|
|
2521
|
+
plugin.license ? rawLocaleText(plugin.license) : '未声明'),
|
|
2354
2522
|
h('p', { style: mutedTextStyle }, `来源:${plugin.source_url || '未提供'} · DSH 版本要求:${plugin.peer_range || '包未声明'}`),
|
|
2355
2523
|
h('p', { style: mutedTextStyle }, `额外配置:${plugin.requirements || '无'}`),
|
|
2356
2524
|
installSpec && !artifactUrl ? h('p', { role: 'note', style: mutedTextStyle },
|
|
@@ -2532,11 +2700,19 @@ window.__ModuleLoader__.load({
|
|
|
2532
2700
|
return status
|
|
2533
2701
|
}
|
|
2534
2702
|
|
|
2535
|
-
const modelProvider = (model) =>
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2703
|
+
const modelProvider = (model) => {
|
|
2704
|
+
const providerId = typeof model?.provider_id === 'string' ? model.provider_id : ''
|
|
2705
|
+
if (!providerId) return null
|
|
2706
|
+
return model.protocol === 'anthropic'
|
|
2707
|
+
? 'ant-team-anthropic-' + providerId
|
|
2708
|
+
: 'ant-team-openai-' + providerId + '-' + (model.openai_api_mode === 'responses' ? 'responses' : 'chat-completions')
|
|
2709
|
+
}
|
|
2710
|
+
const oldDefaultRoutes = ['ant-team-openai-responses', 'ant-team-openai-chat-completions',
|
|
2711
|
+
'ant-team-openai', 'ant-team-anthropic']
|
|
2712
|
+
const isAntModelProviderRoute = (route, profile) => typeof route === 'string'
|
|
2713
|
+
&& profile?.apiKeyEnv === 'ANT_MODEL_API_KEY'
|
|
2714
|
+
&& (oldDefaultRoutes.includes(route)
|
|
2715
|
+
|| route.startsWith('ant-team-openai-') || route.startsWith('ant-team-anthropic-'))
|
|
2540
2716
|
const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
|
|
2541
2717
|
const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2542
2718
|
const providerSyncPreferenceKey = (rawServiceUrl) => `${PROVIDER_SYNC_EXCLUSIONS_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
@@ -2559,48 +2735,10 @@ window.__ModuleLoader__.load({
|
|
|
2559
2735
|
const saveDefaultPreference = (rawServiceUrl, value) => {
|
|
2560
2736
|
try { localStorage.setItem(defaultPreferenceKey(rawServiceUrl), JSON.stringify(value)) } catch {}
|
|
2561
2737
|
}
|
|
2562
|
-
const modelForProviderSelection = (selection, models
|
|
2738
|
+
const modelForProviderSelection = (selection, models) => {
|
|
2563
2739
|
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
|
|
2740
|
+
return models.find((model) => model.model_id === selection.model
|
|
2741
|
+
&& modelProvider(model) === selection.provider) || null
|
|
2604
2742
|
}
|
|
2605
2743
|
const readDshDefaultSelection = async () => {
|
|
2606
2744
|
if (typeof remote?.settings?.describe !== 'function') throw new Error('当前 DSH 未提供全局默认模型读取接口。')
|
|
@@ -2612,13 +2750,11 @@ window.__ModuleLoader__.load({
|
|
|
2612
2750
|
...(typeof value.reasoningEffort === 'string' ? { reasoningEffort: value.reasoningEffort } : {}) } : null
|
|
2613
2751
|
}
|
|
2614
2752
|
const readDshProviderProfiles = async () => {
|
|
2615
|
-
if (typeof remote?.settings?.describe !== 'function')
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
return profiles && typeof profiles === 'object' ? profiles : {}
|
|
2621
|
-
} catch { return {} }
|
|
2753
|
+
if (typeof remote?.settings?.describe !== 'function') throw new Error('无法读取 DSH 中的 ANT 提供方列表。')
|
|
2754
|
+
const response = await remote.settings.describe()
|
|
2755
|
+
if (!response?.ok) throw new Error(response?.error?.message || '无法读取 DSH 中的 ANT 提供方列表。')
|
|
2756
|
+
const profiles = response.value?.namespaces?.find((entry) => entry.ns === 'llm-pi-ai')?.value?.providers
|
|
2757
|
+
return profiles && typeof profiles === 'object' ? profiles : {}
|
|
2622
2758
|
}
|
|
2623
2759
|
const writeDshDefaultModel = async (model) => {
|
|
2624
2760
|
const response = await remote.settings.mutate('agent-default-model', [
|
|
@@ -2654,7 +2790,7 @@ window.__ModuleLoader__.load({
|
|
|
2654
2790
|
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null, options = {}) => {
|
|
2655
2791
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
2656
2792
|
if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
|
|
2657
|
-
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH
|
|
2793
|
+
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 未提供模型设置接口。')
|
|
2658
2794
|
const imageOptOuts = imageInputOptOuts.serviceUrl === normalized
|
|
2659
2795
|
? imageInputOptOuts.modelKeys : readImageInputOptOuts(normalized)
|
|
2660
2796
|
const imageOptOutSet = new Set(imageOptOuts)
|
|
@@ -2662,11 +2798,14 @@ window.__ModuleLoader__.load({
|
|
|
2662
2798
|
for (const model of models) {
|
|
2663
2799
|
const protocol = model.protocol
|
|
2664
2800
|
if (!['openai', 'anthropic'].includes(protocol)) continue
|
|
2665
|
-
|
|
2801
|
+
if (typeof model.provider_id !== 'string' || !model.provider_id) {
|
|
2802
|
+
throw new Error('ANT 返回的模型缺少 provider_id;请升级服务端后重试。')
|
|
2803
|
+
}
|
|
2804
|
+
const providerId = model.provider_id
|
|
2666
2805
|
const apiMode = protocol === 'openai'
|
|
2667
2806
|
? model.openai_api_mode === 'responses' ? 'responses' : 'chat_completions'
|
|
2668
2807
|
: null
|
|
2669
|
-
const key =
|
|
2808
|
+
const key = providerId + '\u0000' + protocol + '\u0000' + (apiMode || '')
|
|
2670
2809
|
let provider = grouped.get(key)
|
|
2671
2810
|
if (!provider) {
|
|
2672
2811
|
const providerName = model.provider_name || (protocol === 'openai'
|
|
@@ -2678,7 +2817,6 @@ window.__ModuleLoader__.load({
|
|
|
2678
2817
|
: providerName,
|
|
2679
2818
|
protocol,
|
|
2680
2819
|
apiMode,
|
|
2681
|
-
legacyEndpoint: typeof model.provider_id !== 'string' || !model.provider_id,
|
|
2682
2820
|
models: [],
|
|
2683
2821
|
}
|
|
2684
2822
|
grouped.set(key, provider)
|
|
@@ -2686,36 +2824,41 @@ window.__ModuleLoader__.load({
|
|
|
2686
2824
|
provider.models.push(model)
|
|
2687
2825
|
}
|
|
2688
2826
|
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
2827
|
const routeForProvider = (provider) => modelProvider({
|
|
2696
2828
|
provider_id: provider.providerId,
|
|
2697
2829
|
protocol: provider.protocol,
|
|
2698
2830
|
openai_api_mode: provider.apiMode,
|
|
2699
2831
|
})
|
|
2700
2832
|
const routeConfigurations = new Map()
|
|
2701
|
-
const compatibilityRoutes = []
|
|
2702
2833
|
const providerGroups = []
|
|
2703
|
-
const dshModelConfig = (model) =>
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2834
|
+
const dshModelConfig = (model) => {
|
|
2835
|
+
const hasReasoningEfforts = model.reasoning_efforts && Object.keys(model.reasoning_efforts).length > 0
|
|
2836
|
+
const compat = { ...(model.compat || {}) }
|
|
2837
|
+
if (model.protocol === 'anthropic' && hasReasoningEfforts
|
|
2838
|
+
&& compat.forceAdaptiveThinking === undefined) {
|
|
2839
|
+
compat.forceAdaptiveThinking = true
|
|
2840
|
+
}
|
|
2841
|
+
return {
|
|
2842
|
+
id: model.model_id,
|
|
2843
|
+
name: model.display_name,
|
|
2844
|
+
...(Number.isSafeInteger(model.context_window) && model.context_window > 0
|
|
2845
|
+
? { contextWindow: model.context_window } : {}),
|
|
2846
|
+
...(Number.isSafeInteger(model.max_tokens) && model.max_tokens > 0
|
|
2847
|
+
? { maxTokens: model.max_tokens } : {}),
|
|
2848
|
+
...(model.supports_images === true && !imageOptOutSet.has(modelSelectionKey(model))
|
|
2849
|
+
? { input: ['text', 'image'] } : {}),
|
|
2850
|
+
...(hasReasoningEfforts ? { reasoningEfforts: model.reasoning_efforts } : {}),
|
|
2851
|
+
...(Object.keys(compat).length > 0 ? { compat } : {}),
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2709
2854
|
for (const provider of providers) {
|
|
2710
2855
|
const route = routeForProvider(provider)
|
|
2711
2856
|
const api = provider.protocol === 'openai'
|
|
2712
2857
|
? provider.apiMode === 'responses' ? 'openai-responses' : 'openai-completions'
|
|
2713
2858
|
: '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()
|
|
2859
|
+
const baseURL = provider.protocol === 'openai'
|
|
2860
|
+
? new URL('/model-gateway/openai/providers/' + encodeURIComponent(provider.providerId) + '/v1', normalized).toString()
|
|
2861
|
+
: new URL('/model-gateway/anthropic/providers/' + encodeURIComponent(provider.providerId), normalized).toString()
|
|
2719
2862
|
routeConfigurations.set(route, {
|
|
2720
2863
|
displayName: provider.providerName,
|
|
2721
2864
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
@@ -2725,27 +2868,11 @@ window.__ModuleLoader__.load({
|
|
|
2725
2868
|
})
|
|
2726
2869
|
providerGroups.push({
|
|
2727
2870
|
key: route,
|
|
2728
|
-
label:
|
|
2871
|
+
label: provider.providerName + ' · ' + (provider.protocol === 'anthropic'
|
|
2872
|
+
? 'Anthropic Messages' : provider.apiMode === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'),
|
|
2729
2873
|
routes: [route],
|
|
2730
2874
|
modelCount: provider.models.length,
|
|
2731
|
-
legacyKeys: [provider.protocol === 'openai' && provider.providerId !== 'openai-default'
|
|
2732
|
-
? `ant-team-openai-${provider.providerId}` : route],
|
|
2733
|
-
legacy: false,
|
|
2734
2875
|
})
|
|
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
2876
|
}
|
|
2750
2877
|
const ops = []
|
|
2751
2878
|
let previousRoutes = []
|
|
@@ -2753,40 +2880,12 @@ window.__ModuleLoader__.load({
|
|
|
2753
2880
|
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2754
2881
|
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2755
2882
|
} 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]
|
|
2883
|
+
const existingProfiles = await readDshProviderProfiles()
|
|
2884
|
+
const profileRoutes = Object.keys(existingProfiles).filter((route) =>
|
|
2885
|
+
isAntModelProviderRoute(route, existingProfiles[route]))
|
|
2886
|
+
const knownRoutes = new Set([...previousRoutes.filter((route) =>
|
|
2887
|
+
isAntModelProviderRoute(route, existingProfiles[route])), ...profileRoutes])
|
|
2888
|
+
const allGroups = providerGroups
|
|
2790
2889
|
let savedExcludedGroups = null
|
|
2791
2890
|
try {
|
|
2792
2891
|
const saved = JSON.parse(localStorage.getItem(providerSyncPreferenceKey(normalized)) || 'null')
|
|
@@ -2794,51 +2893,80 @@ window.__ModuleLoader__.load({
|
|
|
2794
2893
|
} catch {}
|
|
2795
2894
|
let excludedGroups = options.excludedGroups
|
|
2796
2895
|
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)))]
|
|
2896
|
+
if (!Array.isArray(excludedGroups)) excludedGroups = []
|
|
2897
|
+
excludedGroups = [...new Set(excludedGroups.filter((key) => allGroups.some((group) => group.key === key)))]
|
|
2802
2898
|
const excludedGroupSet = new Set(excludedGroups)
|
|
2803
2899
|
const excluded = new Set(allGroups.filter((group) => excludedGroupSet.has(group.key)).flatMap((group) => group.routes))
|
|
2804
2900
|
const routes = [...routeConfigurations.keys()].filter((route) => !excluded.has(route))
|
|
2805
2901
|
const syncedModels = models.filter((model) => !excluded.has(modelProvider(model)))
|
|
2806
2902
|
if (models.length && !syncedModels.length) throw new Error('请至少保留一个常规模型 provider 同步到本机 DSH。')
|
|
2903
|
+
const staleRoutes = [...knownRoutes].filter((route) => !routes.includes(route))
|
|
2904
|
+
const obsoleteRoutes = staleRoutes.filter((route) => !routeConfigurations.has(route))
|
|
2905
|
+
const excludedRoutes = staleRoutes.filter((route) => routeConfigurations.has(route))
|
|
2906
|
+
const cleanupWarning = obsoleteRoutes.length && excludedRoutes.length
|
|
2907
|
+
? '升级会移除旧路由和已关闭同步的 provider route。历史会话需手动选择仍同步且已获准的 ANT 模型。DSH 会将选择应用到当前会话并在后台设为本机 Profile 默认;保存成功后影响该 Profile 新会话及 ANT 对话/任务。之后可在 ANT「团队模型」页更改本机默认;重新启用 provider 可恢复标准 route。会话正文保留。仍要应用吗?'
|
|
2908
|
+
: obsoleteRoutes.length
|
|
2909
|
+
? '升级会移除旧会话路由。继续历史会话需手动选择已获准并同步的 ANT 模型。DSH 会将选择应用到当前会话并在后台设为本机 Profile 默认;保存成功后影响该 Profile 新会话及 ANT 对话/任务。之后可在 ANT「团队模型」页更改本机默认。会话正文保留。仍要应用吗?'
|
|
2910
|
+
: excludedRoutes.length
|
|
2911
|
+
? '关闭这些 provider 会从本机 DSH 移除对应模型路由,可能影响历史会话。重新勾选并刷新可恢复。仍要应用吗?'
|
|
2912
|
+
: ''
|
|
2913
|
+
if (cleanupWarning && !confirmLocalized(cleanupWarning)) {
|
|
2914
|
+
throw new Error('已取消旧 route 清理;本机 DSH 设置未修改。')
|
|
2915
|
+
}
|
|
2807
2916
|
const defaultMatch = rawDefaultModel && models.find((model) =>
|
|
2808
|
-
|
|
2917
|
+
model.provider_id === rawDefaultModel.provider_id
|
|
2809
2918
|
&& model.protocol === rawDefaultModel.protocol && model.model_id === rawDefaultModel.model_id)
|
|
2810
2919
|
const syncedDefaultMatch = defaultMatch && syncedModels.some((model) => modelSelectionKey(model) === modelSelectionKey(defaultMatch))
|
|
2811
2920
|
? defaultMatch : null
|
|
2812
2921
|
const serverDefault = syncedDefaultMatch || null
|
|
2813
2922
|
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
|
-
}
|
|
2923
|
+
const currentSelection = await readDshDefaultSelection()
|
|
2924
|
+
let selectionChangedForRouteCleanup = null
|
|
2925
|
+
if (isAntModelProviderRoute(currentSelection?.provider, existingProfiles[currentSelection?.provider])
|
|
2926
|
+
&& !routes.includes(currentSelection.provider)) {
|
|
2927
|
+
const replacement = serverDefault || syncedModels[0]
|
|
2928
|
+
if (!replacement) throw new Error('请先配置一个获准并同步的 provider,再清理旧默认 route。')
|
|
2929
|
+
await writeDshDefaultModel(replacement)
|
|
2930
|
+
selectionChangedForRouteCleanup = currentSelection
|
|
2831
2931
|
}
|
|
2832
|
-
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2833
|
-
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2834
2932
|
for (const route of knownRoutes) {
|
|
2835
2933
|
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2836
2934
|
}
|
|
2837
2935
|
for (const [route, value] of routeConfigurations) {
|
|
2838
2936
|
ops.push({ op: 'set', path: ['providers', route], value })
|
|
2839
2937
|
}
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2938
|
+
const restoreDefaultAfterRouteWriteFailure = async (failure) => {
|
|
2939
|
+
if (!selectionChangedForRouteCleanup) return
|
|
2940
|
+
const previous = selectionChangedForRouteCleanup
|
|
2941
|
+
try {
|
|
2942
|
+
const restore = await remote.settings.mutate('agent-default-model', [
|
|
2943
|
+
{ op: 'set', path: ['provider'], value: previous.provider },
|
|
2944
|
+
{ op: 'set', path: ['model'], value: previous.model },
|
|
2945
|
+
previous.reasoningEffort
|
|
2946
|
+
? { op: 'set', path: ['reasoningEffort'], value: previous.reasoningEffort }
|
|
2947
|
+
: { op: 'unset', path: ['reasoningEffort'] },
|
|
2948
|
+
], undefined)
|
|
2949
|
+
if (!restore?.ok) throw new Error(restore?.error?.message || 'DSH 无法恢复全局默认模型。')
|
|
2950
|
+
setDshDefaultSelection(previous)
|
|
2951
|
+
const previousModel = modelForProviderSelection(previous, syncedModels)
|
|
2952
|
+
setDefaultModelDraft(previousModel ? modelSelectionKey(previousModel) : '')
|
|
2953
|
+
} catch (rollbackError) {
|
|
2954
|
+
const rollbackMessage = rollbackError instanceof Error ? rollbackError.message : 'DSH 无法恢复全局默认模型。'
|
|
2955
|
+
throw new Error(`${failure.message || 'DSH 无法更新团队模型设置。'} 本机默认模型也未能恢复:${rollbackMessage}`)
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
let settingsWrite
|
|
2959
|
+
try {
|
|
2960
|
+
settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
2961
|
+
} catch (error) {
|
|
2962
|
+
await restoreDefaultAfterRouteWriteFailure(error)
|
|
2963
|
+
throw error
|
|
2964
|
+
}
|
|
2965
|
+
if (!settingsWrite?.ok) {
|
|
2966
|
+
const error = new Error(settingsWrite?.error?.message || 'DSH 无法更新团队模型设置。')
|
|
2967
|
+
await restoreDefaultAfterRouteWriteFailure(error)
|
|
2968
|
+
throw error
|
|
2969
|
+
}
|
|
2842
2970
|
saveImageInputOptOuts(normalized, imageOptOuts)
|
|
2843
2971
|
setImageInputOptOuts({ serviceUrl: normalized, modelKeys: imageOptOuts })
|
|
2844
2972
|
setAppliedImageInputOptOuts(imageOptOuts)
|
|
@@ -2849,7 +2977,9 @@ window.__ModuleLoader__.load({
|
|
|
2849
2977
|
setProviderSyncDraft(excludedGroups)
|
|
2850
2978
|
setServerDefaultModel(serverDefault)
|
|
2851
2979
|
const preference = readDefaultPreference(normalized)
|
|
2852
|
-
let statusMessage =
|
|
2980
|
+
let statusMessage = obsoleteRoutes.length
|
|
2981
|
+
? '旧兼容 route 已清理;继续旧会话需手动选择已获准并同步的 ANT 模型。DSH 会在后台保存为本机 Profile 默认,成功后影响新会话及 ANT 对话/任务;之后可在 ANT「团队模型」页更改默认。'
|
|
2982
|
+
: ''
|
|
2853
2983
|
let selection
|
|
2854
2984
|
if (!preference?.initialized && initialDefault) {
|
|
2855
2985
|
const previousDefault = await readDshDefaultSelection()
|
|
@@ -2860,7 +2990,7 @@ window.__ModuleLoader__.load({
|
|
|
2860
2990
|
: '当前无法使用团队默认模型;已用首个获准模型初始化 DSH 全局默认。请管理员检查默认设置和成员授权。'
|
|
2861
2991
|
} else {
|
|
2862
2992
|
selection = await readDshDefaultSelection()
|
|
2863
|
-
const currentAuthorized = modelForProviderSelection(selection, syncedModels
|
|
2993
|
+
const currentAuthorized = modelForProviderSelection(selection, syncedModels)
|
|
2864
2994
|
const baseline = syncedModels.find((model) => modelSelectionKey(model) === preference?.serverDefault)
|
|
2865
2995
|
const stillUsingBaseline = baseline && currentAuthorized && modelSelectionKey(currentAuthorized) === modelSelectionKey(baseline)
|
|
2866
2996
|
if (currentAuthorized && selection?.provider !== modelProvider(currentAuthorized)) {
|
|
@@ -2870,7 +3000,8 @@ window.__ModuleLoader__.load({
|
|
|
2870
3000
|
selection = await writeDshDefaultModel(serverDefault)
|
|
2871
3001
|
saveDefaultPreference(normalized, { ...preference, initialized: true, serverDefault: modelSelectionKey(serverDefault) })
|
|
2872
3002
|
statusMessage = '团队默认模型已更新;你尚未设置个人覆盖,因此已同步到 DSH 全局默认。'
|
|
2873
|
-
} else if (selection?.provider?.
|
|
3003
|
+
} else if (isAntModelProviderRoute(selection?.provider, existingProfiles[selection?.provider])
|
|
3004
|
+
&& !currentAuthorized && syncedModels.length) {
|
|
2874
3005
|
const fallback = serverDefault || syncedModels[0]
|
|
2875
3006
|
selection = await writeDshDefaultModel(fallback)
|
|
2876
3007
|
saveDefaultPreference(normalized, { ...preference, initialized: true,
|
|
@@ -2895,12 +3026,17 @@ window.__ModuleLoader__.load({
|
|
|
2895
3026
|
return { count: syncedModels.length, defaultSelection: selection }
|
|
2896
3027
|
}
|
|
2897
3028
|
|
|
2898
|
-
const requestTeamModelCatalog = async (version) => {
|
|
3029
|
+
const requestTeamModelCatalog = async (version = '3') => {
|
|
2899
3030
|
const response = await fetch('/api/ant-teams/models', {
|
|
2900
3031
|
method: 'POST', headers: { 'x-ant-model-provider-config': version },
|
|
2901
3032
|
})
|
|
2902
3033
|
return { response, result: await readLocalJsonResponse(response) }
|
|
2903
3034
|
}
|
|
3035
|
+
const requestCompatibleTeamModelCatalog = async () => {
|
|
3036
|
+
const current = await requestTeamModelCatalog('3')
|
|
3037
|
+
if (!current.response.ok || current.result.model_provider_config_version === 3) return current
|
|
3038
|
+
return requestTeamModelCatalog('2')
|
|
3039
|
+
}
|
|
2904
3040
|
|
|
2905
3041
|
const refreshModels = async (applyDraft = false) => {
|
|
2906
3042
|
if (!bindingReady) {
|
|
@@ -2911,19 +3047,13 @@ window.__ModuleLoader__.load({
|
|
|
2911
3047
|
setModelMessage('正在从 ANT 刷新获准模型…')
|
|
2912
3048
|
try {
|
|
2913
3049
|
await requireCompatibleService(serviceUrl, true)
|
|
2914
|
-
|
|
3050
|
+
const { response, result } = await requestCompatibleTeamModelCatalog()
|
|
2915
3051
|
if (!response.ok) {
|
|
2916
3052
|
if (response.status === 401) setNeedsReconnect(true)
|
|
2917
3053
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
2918
3054
|
}
|
|
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
|
-
}
|
|
3055
|
+
if (![2, 3].includes(result.model_provider_config_version)) {
|
|
3056
|
+
throw new Error('服务端不支持 provider-config v2,请先升级 ANT 服务端。')
|
|
2927
3057
|
}
|
|
2928
3058
|
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model,
|
|
2929
3059
|
applyDraft ? { excludedGroups: providerSyncDraft } : {})
|
|
@@ -3087,10 +3217,15 @@ window.__ModuleLoader__.load({
|
|
|
3087
3217
|
if (outcome.application === 'failed' || outcome.application === 'cancelled') {
|
|
3088
3218
|
const reason = outcome.error?.diagnostic || outcome.packageResult?.output || '安装未完成。'
|
|
3089
3219
|
if (outcome.error?.code === 'incompatible-version') {
|
|
3220
|
+
const runtimeVersion = typeof outcome.error?.runtimeVersion === 'string'
|
|
3221
|
+
? outcome.error.runtimeVersion : ''
|
|
3222
|
+
const compatibilityMessage = runtimeVersion
|
|
3223
|
+
? translateLocaleText('DSH 安装器判定当前客户端 {runtime_version} 与此 ANT 版本不兼容:{reason}',
|
|
3224
|
+
{ runtime_version: runtimeVersion, reason })
|
|
3225
|
+
: translateLocaleText('DSH 安装器判定当前客户端与此 ANT 版本不兼容:{reason}', { reason })
|
|
3090
3226
|
setDshCompatibility((current) => ({ ...current, state: 'incompatible',
|
|
3091
|
-
runtime_version:
|
|
3092
|
-
message:
|
|
3093
|
-
&& outcome.error.runtimeVersion ? ` ${outcome.error.runtimeVersion} ` : ''}与此 ANT 版本不兼容:${reason}` }))
|
|
3227
|
+
runtime_version: runtimeVersion || current.runtime_version,
|
|
3228
|
+
message: compatibilityMessage }))
|
|
3094
3229
|
}
|
|
3095
3230
|
setBundleUpdate((current) => ({ ...current, state: 'failed', message: `更新失败:${reason}` }))
|
|
3096
3231
|
return
|
|
@@ -3122,11 +3257,13 @@ window.__ModuleLoader__.load({
|
|
|
3122
3257
|
|
|
3123
3258
|
const clearLocalConnection = async () => {
|
|
3124
3259
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('DSH 未提供模型设置接口。')
|
|
3260
|
+
const existingProfiles = await readDshProviderProfiles()
|
|
3261
|
+
const isManagedRoute = (route) => isAntModelProviderRoute(route, existingProfiles[route])
|
|
3125
3262
|
const selection = await readDshDefaultSelection()
|
|
3126
|
-
if (selection?.provider
|
|
3263
|
+
if (isManagedRoute(selection?.provider)) {
|
|
3127
3264
|
const previous = readDefaultPreference(serviceUrl)?.previousDefault
|
|
3128
3265
|
const restore = previous && typeof previous.provider === 'string' && typeof previous.model === 'string'
|
|
3129
|
-
&& !previous.provider
|
|
3266
|
+
&& !isManagedRoute(previous.provider)
|
|
3130
3267
|
const operations = restore
|
|
3131
3268
|
? [
|
|
3132
3269
|
{ op: 'set', path: ['provider'], value: previous.provider },
|
|
@@ -3145,21 +3282,7 @@ window.__ModuleLoader__.load({
|
|
|
3145
3282
|
], undefined)
|
|
3146
3283
|
if (!reset.ok) throw new Error(reset.error?.message || 'DSH 全局默认模型未能恢复。')
|
|
3147
3284
|
}
|
|
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'])
|
|
3285
|
+
const routes = Object.keys(existingProfiles).filter(isManagedRoute)
|
|
3163
3286
|
const settingsResult = await remote.settings.mutate('llm-pi-ai', [...routes].map((route) => ({
|
|
3164
3287
|
op: 'unset', path: ['providers', route],
|
|
3165
3288
|
})), undefined)
|
|
@@ -3203,7 +3326,7 @@ window.__ModuleLoader__.load({
|
|
|
3203
3326
|
}
|
|
3204
3327
|
|
|
3205
3328
|
const clearOnlyLocal = async () => {
|
|
3206
|
-
if (!
|
|
3329
|
+
if (!confirmLocalized('仅清理这台 DSH 的 ANT 凭证和模型设置,不保证服务端设备令牌已撤销。请先让原团队管理员确认远端撤销;否则令牌可能仍可被使用。确定继续吗?')) return
|
|
3207
3330
|
setBusy(true)
|
|
3208
3331
|
try {
|
|
3209
3332
|
await clearLocalConnection()
|
|
@@ -3289,7 +3412,7 @@ window.__ModuleLoader__.load({
|
|
|
3289
3412
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
3290
3413
|
const response = await fetch('/api/ant-teams/request-code', {
|
|
3291
3414
|
method: 'POST',
|
|
3292
|
-
headers: { 'Content-Type': 'application/json' },
|
|
3415
|
+
headers: { 'Content-Type': 'application/json', 'X-Ant-Locale': localeRuntime?.getLocale?.().active || 'en' },
|
|
3293
3416
|
body: JSON.stringify({ service_url: normalized, email: normalizedEmail }),
|
|
3294
3417
|
})
|
|
3295
3418
|
const result = await readLocalJsonResponse(response)
|
|
@@ -3332,7 +3455,7 @@ window.__ModuleLoader__.load({
|
|
|
3332
3455
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
3333
3456
|
const response = await fetch('/api/ant-teams/connect', {
|
|
3334
3457
|
method: 'POST',
|
|
3335
|
-
headers: { 'Content-Type': 'application/json', 'x-ant-model-provider-config': '
|
|
3458
|
+
headers: { 'Content-Type': 'application/json', 'x-ant-model-provider-config': '3' },
|
|
3336
3459
|
body: JSON.stringify({
|
|
3337
3460
|
service_url: normalized,
|
|
3338
3461
|
email: normalizedEmail,
|
|
@@ -3352,12 +3475,16 @@ window.__ModuleLoader__.load({
|
|
|
3352
3475
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
3353
3476
|
credentialSaved = true
|
|
3354
3477
|
let modelCatalog = result
|
|
3355
|
-
if (result.model_provider_config_version !==
|
|
3356
|
-
const
|
|
3357
|
-
if (!
|
|
3358
|
-
|
|
3478
|
+
if (result.model_provider_config_version !== 3) {
|
|
3479
|
+
const fallback = await requestTeamModelCatalog('2')
|
|
3480
|
+
if (!fallback.response.ok) {
|
|
3481
|
+
if (fallback.response.status === 401) setNeedsReconnect(true)
|
|
3482
|
+
throw new Error(fallback.result.message || `刷新失败(${fallback.response.status})`)
|
|
3359
3483
|
}
|
|
3360
|
-
modelCatalog =
|
|
3484
|
+
modelCatalog = fallback.result
|
|
3485
|
+
}
|
|
3486
|
+
if (![2, 3].includes(modelCatalog.model_provider_config_version)) {
|
|
3487
|
+
throw new Error('服务端不支持 provider-config v2,请先升级 ANT 服务端。')
|
|
3361
3488
|
}
|
|
3362
3489
|
await applyAuthorizedModels(modelCatalog.models, normalized, modelCatalog.default_model)
|
|
3363
3490
|
setAuthorizedModels(modelCatalog.models)
|
|
@@ -3370,7 +3497,7 @@ window.__ModuleLoader__.load({
|
|
|
3370
3497
|
setAllowLocalCleanup(false)
|
|
3371
3498
|
setNeedsReconnect(false)
|
|
3372
3499
|
setReauthenticate(false)
|
|
3373
|
-
setMessage(`已连接 ${result.email || normalizedEmail},同步 ${
|
|
3500
|
+
setMessage(`已连接 ${result.email || normalizedEmail},同步 ${modelCatalog.models.length} 个获准模型。它们现已出现在 DSH 模型选择器中。`)
|
|
3374
3501
|
} catch (error) {
|
|
3375
3502
|
if (credentialSaved) {
|
|
3376
3503
|
setCredentialConfigured(true)
|
|
@@ -3406,7 +3533,7 @@ window.__ModuleLoader__.load({
|
|
|
3406
3533
|
&& imageInputOptOutSet.has(modelSelectionKey(model)) !== appliedImageInputOptOutSet.has(modelSelectionKey(model)))
|
|
3407
3534
|
const hasPendingSyncChanges = [...new Set([...providerSyncDraft, ...appliedProviderSyncExclusions])]
|
|
3408
3535
|
.some((key) => excludedGroupSet.has(key) !== appliedExcludedGroupSet.has(key))
|
|
3409
|
-
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !
|
|
3536
|
+
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !excludedGroupSet.has(group.key)).length
|
|
3410
3537
|
const pendingServerDefaultRemoval = hasPendingSyncChanges && serverDefaultModel
|
|
3411
3538
|
? providerSyncGroups.some((group) => group.routes.includes(modelProvider(serverDefaultModel))
|
|
3412
3539
|
&& !appliedExcludedGroupSet.has(group.key) && excludedGroupSet.has(group.key))
|
|
@@ -3429,7 +3556,7 @@ window.__ModuleLoader__.load({
|
|
|
3429
3556
|
return h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
3430
3557
|
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(190px, auto)',
|
|
3431
3558
|
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}`),
|
|
3559
|
+
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, rawLocaleText(`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id}`)),
|
|
3433
3560
|
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
3434
3561
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
3435
3562
|
model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
|
|
@@ -3445,28 +3572,6 @@ window.__ModuleLoader__.load({
|
|
|
3445
3572
|
h('span', { style: mutedTextStyle }, imageInputEnabled ? '本机允许图片输入' : '本机已关闭图片输入'))
|
|
3446
3573
|
: h('span', { style: mutedTextStyle }, '团队未开放图片输入')))
|
|
3447
3574
|
})
|
|
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
3575
|
const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
3471
3576
|
h('div', null,
|
|
3472
3577
|
h('h3', { style: sectionTitleStyle }, 'ANT 团队账号'),
|
|
@@ -3485,7 +3590,7 @@ window.__ModuleLoader__.load({
|
|
|
3485
3590
|
h('button', { type: 'button', disabled: busy || !bindingReady,
|
|
3486
3591
|
onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
3487
3592
|
h('button', { type: 'button', disabled: busy,
|
|
3488
|
-
onClick: () => { if (
|
|
3593
|
+
onClick: () => { if (confirmLocalized('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
3489
3594
|
style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
3490
3595
|
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
3491
3596
|
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
@@ -3499,7 +3604,7 @@ window.__ModuleLoader__.load({
|
|
|
3499
3604
|
onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle }),
|
|
3500
3605
|
h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
3501
3606
|
credentialConfigured === true && showConnectForm ? h('button', { type: 'button', disabled: busy,
|
|
3502
|
-
onClick: () => { if (
|
|
3607
|
+
onClick: () => { if (confirmLocalized('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
3503
3608
|
style: secondaryButtonStyle }, '断开当前设备') : null,
|
|
3504
3609
|
allowLocalCleanup ? h('section', { style: { ...settingsPanelStyle, padding: 14, gap: 9 } },
|
|
3505
3610
|
h('p', { style: mutedTextStyle }, savedDeviceId()
|
|
@@ -3549,7 +3654,7 @@ window.__ModuleLoader__.load({
|
|
|
3549
3654
|
: model.provider_name
|
|
3550
3655
|
const protocolName = model.protocol === 'anthropic' ? 'Anthropic' : `OpenAI ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`
|
|
3551
3656
|
return h('option', { key: modelSelectionKey(model), value: modelSelectionKey(model) },
|
|
3552
|
-
`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`)
|
|
3657
|
+
rawLocaleText(`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`))
|
|
3553
3658
|
}))),
|
|
3554
3659
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
3555
3660
|
h('button', { type: 'button', disabled: defaultModelBusy || !selectedDefaultDraft || hasPendingSyncChanges,
|
|
@@ -3564,20 +3669,16 @@ window.__ModuleLoader__.load({
|
|
|
3564
3669
|
defaultModelMessage ? h('p', { role: 'status', style: mutedTextStyle }, defaultModelMessage) : null),
|
|
3565
3670
|
providerSyncGroups.length ? h('section', { style: { ...infoNoticeStyle, gap: 8 } },
|
|
3566
3671
|
h('strong', null, '同步到本机 DSH 的 provider'),
|
|
3567
|
-
h('p', { style: mutedTextStyle }, '
|
|
3568
|
-
h('p', { style: mutedTextStyle }, '
|
|
3672
|
+
h('p', { style: mutedTextStyle }, '取消勾选并刷新后,该 provider 会从本机 DSH 移除;使用它的历史会话可能需要重新选择模型。'),
|
|
3673
|
+
h('p', { style: mutedTextStyle }, '至少保留一个获准 provider。'),
|
|
3569
3674
|
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,
|
|
3675
|
+
...providerSyncGroups.map((group) => h('label', { key: group.key,
|
|
3676
|
+
style: { display: 'flex', alignItems: 'center', gap: 9, padding: '5px 0', cursor: busy ? 'default' : 'pointer' } },
|
|
3677
|
+
h('input', { type: 'checkbox', 'aria-label': group.label,
|
|
3678
|
+
checked: !excludedGroupSet.has(group.key), disabled: busy
|
|
3679
|
+
|| (!excludedGroupSet.has(group.key) && activePrimaryGroupCount <= 1),
|
|
3680
|
+
onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
|
|
3681
|
+
h('span', null, `${group.label} · ${group.modelCount} 个模型`)))) : null,
|
|
3581
3682
|
hasPendingImageInputChanges ? h('p', { role: 'status', style: mutedTextStyle },
|
|
3582
3683
|
'本机图片输入偏好尚未同步;点击“刷新团队模型”后生效。') : null,
|
|
3583
3684
|
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
@@ -3587,15 +3688,10 @@ window.__ModuleLoader__.load({
|
|
|
3587
3688
|
: '连接并验证工作邮箱后,这里会列出逐个获准模型。'),
|
|
3588
3689
|
credentialConfigured !== true ? h('button', { type: 'button', onClick: () => onSelectTab('connection'),
|
|
3589
3690
|
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
3691
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
3596
3692
|
h('button', { type: 'button', disabled: busy || defaultModelBusy || !bindingReady || credentialConfigured !== true,
|
|
3597
3693
|
onClick: () => void refreshModels(true), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
3598
|
-
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString(
|
|
3694
|
+
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString(formatLocale())}`) : null),
|
|
3599
3695
|
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
3600
3696
|
|
|
3601
3697
|
const updateStatusLabels = {
|
|
@@ -3682,6 +3778,7 @@ window.__ModuleLoader__.load({
|
|
|
3682
3778
|
}
|
|
3683
3779
|
|
|
3684
3780
|
function EnterpriseExtensionsPage({ remote, onBack, openBundle }) {
|
|
3781
|
+
useLocaleRevision()
|
|
3685
3782
|
const [tab, setTab] = useState('all')
|
|
3686
3783
|
const [query, setQuery] = useState('')
|
|
3687
3784
|
const navigation = {
|
|
@@ -3885,6 +3982,52 @@ window.__ModuleLoader__.load({
|
|
|
3885
3982
|
|
|
3886
3983
|
function apply(ctx) {
|
|
3887
3984
|
ctx.effect(() => installEnterpriseBranding(ctx), 'ANT enterprise branding synchronization')
|
|
3985
|
+
ctx.effect(() => {
|
|
3986
|
+
const runtime = ctx.locale
|
|
3987
|
+
localeRuntime = runtime
|
|
3988
|
+
localeTranslate = runtime.bind('ant.team')
|
|
3989
|
+
let disposed = false
|
|
3990
|
+
const removers = []
|
|
3991
|
+
void fetch(ANT_LOCALE_MESSAGES_PATH, { cache: 'no-store' }).then(async (response) => {
|
|
3992
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
3993
|
+
return response.json()
|
|
3994
|
+
}).then((catalogs) => {
|
|
3995
|
+
const catalogMetadata = catalogs?.locales
|
|
3996
|
+
const sourceMessages = catalogs?.messages
|
|
3997
|
+
if (disposed || !catalogMetadata?.en || !catalogMetadata?.zh || !sourceMessages) return
|
|
3998
|
+
localeMetadata = catalogMetadata
|
|
3999
|
+
const messagesFor = (id) => Object.fromEntries(Object.entries(sourceMessages)
|
|
4000
|
+
.map(([source, translations]) => [source, translations[id] ?? translations.en ?? source]))
|
|
4001
|
+
localeMessagePatterns = Object.keys(sourceMessages)
|
|
4002
|
+
.filter((key) => /\{[a-zA-Z][a-zA-Z0-9_]*\}/u.test(key))
|
|
4003
|
+
.sort((a, b) => b.length - a.length)
|
|
4004
|
+
.map(compileLocalePattern)
|
|
4005
|
+
removers.push(runtime.register('ant.team', {
|
|
4006
|
+
en: messagesFor('en'),
|
|
4007
|
+
zh: messagesFor('zh'),
|
|
4008
|
+
}))
|
|
4009
|
+
const registered = new Set(runtime.getLocale().locales.map((item) => item.id.toLowerCase()))
|
|
4010
|
+
for (const id of ANT_LOCALE_IDS) {
|
|
4011
|
+
const catalog = localeMetadata[id]
|
|
4012
|
+
if (!catalog) continue
|
|
4013
|
+
if (!registered.has(id.toLowerCase())) {
|
|
4014
|
+
removers.push(runtime.addLanguage({ id, label: catalog.label, fallback: 'en' }))
|
|
4015
|
+
registered.add(id.toLowerCase())
|
|
4016
|
+
}
|
|
4017
|
+
removers.push(runtime.register('ant.team', id, messagesFor(id)))
|
|
4018
|
+
}
|
|
4019
|
+
}).catch((error) => ctx.logger?.warn?.('ANT locale catalogs unavailable:', error?.message))
|
|
4020
|
+
return () => {
|
|
4021
|
+
disposed = true
|
|
4022
|
+
for (const remove of removers.reverse()) remove()
|
|
4023
|
+
if (localeRuntime === runtime) {
|
|
4024
|
+
localeRuntime = null
|
|
4025
|
+
localeMetadata = {}
|
|
4026
|
+
localeMessagePatterns = []
|
|
4027
|
+
localeTranslate = (key) => key
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
}, 'ANT locale catalog registration')
|
|
3888
4031
|
const remote = {
|
|
3889
4032
|
credentials: ctx.remote.credentials,
|
|
3890
4033
|
settings: ctx.remote.settings,
|