@ai-wendao/ant 0.4.9 → 0.4.11
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 +10 -2
- package/client.js +371 -87
- package/conversation.js +94 -31
- package/delegation.js +18 -11
- package/index.js +17 -7
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -7,6 +7,7 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const h = createElement
|
|
8
8
|
const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
|
|
9
9
|
const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
|
|
10
|
+
const DEFAULT_MODEL_INITIALIZED_KEY = 'ant-team-default-model-initialized'
|
|
10
11
|
const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
|
|
11
12
|
const ANT_API_PROTOCOL_VERSIONS = [1]
|
|
12
13
|
const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
|
|
@@ -227,6 +228,12 @@ window.__ModuleLoader__.load({
|
|
|
227
228
|
? url.href : null
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
function pluginInstallSpec(plugin) {
|
|
232
|
+
if (plugin?.status !== 'published' || typeof plugin.package_name !== 'string'
|
|
233
|
+
|| typeof plugin.version !== 'string' || !plugin.package_name || !plugin.version) return ''
|
|
234
|
+
return approvedPluginArtifactUrl(plugin) || directInstallSpec(plugin)
|
|
235
|
+
}
|
|
236
|
+
|
|
230
237
|
function previouslyInstalledReviewedPlugin(plugin) {
|
|
231
238
|
try { return localStorage.getItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`) === plugin.artifact_sha256 }
|
|
232
239
|
catch { return false }
|
|
@@ -467,12 +474,15 @@ window.__ModuleLoader__.load({
|
|
|
467
474
|
const [examples, setExamples] = useState('')
|
|
468
475
|
const [taskPrompt, setTaskPrompt] = useState('')
|
|
469
476
|
const [runner, setRunner] = useState('dsh')
|
|
477
|
+
const [reviewers, setReviewers] = useState(['dsh'])
|
|
470
478
|
const [parameters, setParameters] = useState([])
|
|
471
479
|
const [giteaToken, setGiteaToken] = useState('')
|
|
472
480
|
const [tokenConfigured, setTokenConfigured] = useState(false)
|
|
473
481
|
const [senderScope, setSenderScope] = useState('any')
|
|
474
482
|
const [senderIds, setSenderIds] = useState('')
|
|
475
483
|
const [days, setDays] = useState(7)
|
|
484
|
+
const [editingCardId, setEditingCardId] = useState(null)
|
|
485
|
+
const [editingExpiresAt, setEditingExpiresAt] = useState(null)
|
|
476
486
|
const [busy, setBusy] = useState(false)
|
|
477
487
|
const [loading, setLoading] = useState(true)
|
|
478
488
|
const [botLoadError, setBotLoadError] = useState(false)
|
|
@@ -548,17 +558,28 @@ window.__ModuleLoader__.load({
|
|
|
548
558
|
const enrolledCount = enrolled.filter((item) => item.device_bound_to_current
|
|
549
559
|
&& connectedBots.some((bot) => bot.fingerprint === item.bot_fingerprint)).length
|
|
550
560
|
const selectedTaskCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint).length
|
|
551
|
-
const
|
|
561
|
+
const nativeReviewerSelected = reviewers.some((item) => ['codex', 'claude-code'].includes(item))
|
|
562
|
+
const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
|
|
552
563
|
const codexAvailable = executors.some((item) => item.id === 'codex-readonly' && item.available)
|
|
553
564
|
const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
|
|
565
|
+
const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
|
|
554
566
|
const nativePrUnavailableReason = executors.find((item) => item.reason)?.reason || null
|
|
567
|
+
const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
|
|
568
|
+
const cardReviewerSummary = (card) => {
|
|
569
|
+
const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
|
|
570
|
+
return selected.length > 1
|
|
571
|
+
? `交叉可用:${selected.map(reviewerLabel).join('、')};默认 ${reviewerLabel(card.runner)}`
|
|
572
|
+
: card.runner === 'codex' ? 'Codex PR 快照审查'
|
|
573
|
+
: card.runner === 'claude-code' ? 'Claude Code PR 快照审查'
|
|
574
|
+
: 'DSH 文字处理'
|
|
575
|
+
}
|
|
555
576
|
const requesterSummary = senderScope === 'allowlist'
|
|
556
577
|
? `仅指定成员:${senderIds.trim() || '尚未填写成员 ID'}`
|
|
557
578
|
: conversationKind === 'group' ? `群 ${groupId || '尚未选择'} 的所有成员` : '任何能私聊此机器人的人'
|
|
558
579
|
const resultRouteSummary = conversationKind === 'group' ? '回复到原群(群内可见)' : '回复到发起任务的私聊'
|
|
559
580
|
const executorSummary = runner === 'codex' ? '本机 Codex · PR 快照'
|
|
560
581
|
: runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH · 当前授权模型'
|
|
561
|
-
const resourceSummary =
|
|
582
|
+
const resourceSummary = nativeReviewerSelected ? '指定仓库的已核验 PR 快照'
|
|
562
583
|
: '当前会话中 DSH 实际可见的内容'
|
|
563
584
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
564
585
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
@@ -571,20 +592,56 @@ window.__ModuleLoader__.load({
|
|
|
571
592
|
: parameters.some((rule) => rule.kind === 'gitea_pr')
|
|
572
593
|
&& normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
|
|
573
594
|
? '请先保存机器人级可信 Gitea 地址;PR 参数会自动使用这个地址。'
|
|
574
|
-
:
|
|
595
|
+
: nativeReviewerSelected && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
|
|
575
596
|
? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
|
|
576
|
-
:
|
|
597
|
+
: nativeReviewerSelected && !parameters[0].required
|
|
577
598
|
? '本机代码审查的 Gitea PR 编号必须设为必填。'
|
|
578
|
-
:
|
|
579
|
-
? '这台 DSH
|
|
580
|
-
|
|
581
|
-
|
|
599
|
+
: reviewers.includes('dsh') && !dshAvailable
|
|
600
|
+
? '这台 DSH 尚未准备好文字审查执行器。'
|
|
601
|
+
: reviewers.includes('codex') && !codexAvailable
|
|
602
|
+
? '这台 DSH 尚未找到可用的 Codex CLI,请先安装或检查 DSH 的 PATH。'
|
|
603
|
+
: reviewers.includes('claude-code') && !claudeCodeAvailable
|
|
604
|
+
? '这台 DSH 尚未注册 Claude Code 子代理,请先在 DSH 中安装并启用它。'
|
|
582
605
|
: ''
|
|
583
606
|
const loadNames = { bots: '机器人', 'enrolled-bots': '协作启用状态', groups: '群聊列表',
|
|
584
607
|
cards: '任务授权', settings: '团队默认值', executors: '本机执行器',
|
|
585
608
|
'local-settings': '本机设置', status: '最近任务' }
|
|
586
609
|
const updateParameter = (index, patch) => setParameters((current) => current.map((rule, i) => i === index ? { ...rule, ...patch } : rule))
|
|
610
|
+
const changeReviewer = (id, enabled) => {
|
|
611
|
+
const next = enabled ? [...reviewers, id] : reviewers.filter((item) => item !== id)
|
|
612
|
+
if (!next.length) { setMessage('至少保留一位审查 Agent。'); return }
|
|
613
|
+
setReviewers([...new Set(next)])
|
|
614
|
+
if (!next.includes(runner)) setRunner(next[0])
|
|
615
|
+
}
|
|
616
|
+
const startNewCard = () => {
|
|
617
|
+
setEditingCardId(null); setEditingExpiresAt(null)
|
|
618
|
+
setDraftNotice('')
|
|
619
|
+
setTitle(''); setIntent(''); setExamples(''); setTaskPrompt(''); setRunner('dsh')
|
|
620
|
+
setReviewers(['dsh'])
|
|
621
|
+
setParameters([]); setConversationKind('dm'); setGroupId('')
|
|
622
|
+
setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
|
|
623
|
+
}
|
|
624
|
+
const editCard = (card, copy = false) => {
|
|
625
|
+
setBotId(card.bot_id)
|
|
626
|
+
setTrustedOrigin(enrolled.find((bot) => bot.bot_id === card.bot_id)?.gitea_origin || '')
|
|
627
|
+
setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
|
|
628
|
+
setTitle(copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
|
|
629
|
+
if (copy) setDays(7)
|
|
630
|
+
setIntent(card.intent); setExamples((card.examples || []).join('\n'))
|
|
631
|
+
setTaskPrompt(card.task_prompt); setRunner(card.runner)
|
|
632
|
+
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
|
|
633
|
+
setParameters((card.parameters || []).map((rule) => ({
|
|
634
|
+
name: rule.name, kind: rule.kind, required: rule.required,
|
|
635
|
+
description: rule.description || '', min: rule.min == null ? '' : String(rule.min),
|
|
636
|
+
max: rule.max == null ? '' : String(rule.max), choices: (rule.choices || []).join(', '),
|
|
637
|
+
gitea_origin: rule.gitea_origin || '', repository: rule.repository || '',
|
|
638
|
+
})))
|
|
639
|
+
setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
|
|
640
|
+
setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
|
|
641
|
+
setCreateOpen(true)
|
|
642
|
+
}
|
|
587
643
|
const preparePrExample = () => {
|
|
644
|
+
setEditingCardId(null); setEditingExpiresAt(null)
|
|
588
645
|
setTitle('审查 Gitea PR 差异')
|
|
589
646
|
setIntent('同事希望查看指定 Gitea PR 的代码差异并获得审查意见。')
|
|
590
647
|
setExamples('请帮我审查 123 号 PR\n看看刚才提到的 PR 有什么风险')
|
|
@@ -592,7 +649,13 @@ window.__ModuleLoader__.load({
|
|
|
592
649
|
setParameters([{ name: 'pr_number', kind: 'gitea_pr', required: true,
|
|
593
650
|
description: '要审查的 PR 编号', min: '1', max: '', choices: '',
|
|
594
651
|
gitea_origin: selectedEnrollment?.gitea_origin || trustedOrigin || '', repository: '' }])
|
|
595
|
-
|
|
652
|
+
const availableReviewers = [
|
|
653
|
+
...(codexAvailable ? ['codex'] : []),
|
|
654
|
+
...(claudeCodeAvailable ? ['claude-code'] : []),
|
|
655
|
+
]
|
|
656
|
+
const selected = availableReviewers.length ? availableReviewers : ['dsh']
|
|
657
|
+
setReviewers(selected)
|
|
658
|
+
setRunner(selected[0])
|
|
596
659
|
setCreateOpen(true)
|
|
597
660
|
}
|
|
598
661
|
const saveGiteaToken = async () => {
|
|
@@ -798,6 +861,9 @@ window.__ModuleLoader__.load({
|
|
|
798
861
|
event.preventDefault()
|
|
799
862
|
setDraftNotice('')
|
|
800
863
|
if (!selectedBot || !botConnected || !selectedEnrollmentBound) { setMessage('请先在这台 DSH 绑定选中的协作机器人。'); return }
|
|
864
|
+
if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
|
|
865
|
+
setMessage('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
|
|
866
|
+
}
|
|
801
867
|
if (!title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
802
868
|
setMessage('请填写能力名称、适用请求说明和处理要求。'); return
|
|
803
869
|
}
|
|
@@ -849,45 +915,67 @@ window.__ModuleLoader__.load({
|
|
|
849
915
|
if (normalized.some((rule) => rule.kind === 'gitea_pr' && !validRepository(rule.repository))) {
|
|
850
916
|
setMessage('Gitea 仓库请填写“团队名/仓库名”,不要包含空格或多余路径。'); return
|
|
851
917
|
}
|
|
852
|
-
if (
|
|
853
|
-
|
|
854
|
-
setMessage('Codex 和 Claude Code 目前需要一个必填的 Gitea PR 编号参数。'); return
|
|
855
|
-
}
|
|
856
|
-
if (runner === 'codex' && !codexAvailable) {
|
|
857
|
-
setMessage('这台 DSH 尚未找到可用的 Codex CLI。'); return
|
|
918
|
+
if (!reviewers.length || !reviewers.includes(runner)) {
|
|
919
|
+
setMessage('请至少选择一位审查 Agent,并设置普通请求的默认执行方。'); return
|
|
858
920
|
}
|
|
859
|
-
if (
|
|
860
|
-
|
|
921
|
+
if (nativeReviewerSelected && (normalized.length !== 1
|
|
922
|
+
|| normalized[0].kind !== 'gitea_pr' || !normalized[0].required)) {
|
|
923
|
+
setMessage('选择 Codex 或 Claude Code 时,需要一个必填的 Gitea PR 编号参数。'); return
|
|
861
924
|
}
|
|
862
925
|
const scope = conversationKind === 'group' ? `群 ${groupId}` : '私聊'
|
|
863
926
|
const resultDestination = conversationKind === 'group' ? '原群(群内可见)' : '发起任务的私聊'
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
:
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
927
|
+
const executionDetails = {
|
|
928
|
+
dsh: 'DSH 只根据已提供的 PR diff 审查,不读取完整仓库。',
|
|
929
|
+
codex: 'Codex 使用已核验 PR Head 的临时代码快照,并启用本机固定只读权限;不接收 Gitea 令牌。',
|
|
930
|
+
'claude-code': 'Claude Code 使用同一份临时代码快照,沿用这台 DSH 的本机权限设置;请确认该权限适合由获准成员请求审查。',
|
|
931
|
+
}
|
|
932
|
+
const executionInfo = reviewers.length > 1
|
|
933
|
+
? `普通请求默认由 ${reviewerLabel(runner)} 执行;请求人明确说“交叉 review”时,会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回审查结果。${reviewers.map((item) => executionDetails[item]).join(' ')}`
|
|
934
|
+
: executionDetails[reviewers[0]]
|
|
935
|
+
const actionDescription = editingCardId
|
|
936
|
+
? `更新“${title}”后,${requesterSummary}可在${scope}使用新配置;旧待确认请求将失效,已提交任务继续使用提交时的配置。结果${resultDestination}。到期时间保持为 ${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。`
|
|
937
|
+
: `开放“${title}”后,${requesterSummary}可在${scope}请求,有效期 ${days} 天;结果${resultDestination}。`
|
|
938
|
+
if (!window.confirm(`${actionDescription}${executionInfo}请确认。`)) return
|
|
870
939
|
setBusy(true)
|
|
871
940
|
try {
|
|
872
941
|
const response = await fetch('/api/ant-teams/delegation/cards', {
|
|
873
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
874
|
-
body: JSON.stringify({
|
|
942
|
+
method: editingCardId ? 'PUT' : 'POST', headers: { 'content-type': 'application/json' },
|
|
943
|
+
body: JSON.stringify({ ...(editingCardId ? { id: editingCardId } : {}),
|
|
944
|
+
bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
|
|
875
945
|
title, intent, examples: exampleLines,
|
|
876
|
-
parameters: normalized, task_prompt: taskPrompt, runner, workspace: null,
|
|
946
|
+
parameters: normalized, task_prompt: taskPrompt, runner, reviewers, workspace: null,
|
|
877
947
|
sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
|
|
878
948
|
? senderIds.split(/[\s,,]+/u).filter(Boolean) : [], conversation_kind: conversationKind,
|
|
879
949
|
...(conversationKind === 'group' ? { conversation_id: groupId } : {}),
|
|
880
|
-
expires_at: Math.floor(Date.now() / 1000) + Number(days) * 86400 }),
|
|
950
|
+
expires_at: editingCardId ? editingExpiresAt : Math.floor(Date.now() / 1000) + Number(days) * 86400 }),
|
|
881
951
|
})
|
|
882
952
|
const result = await readLocalJsonResponse(response)
|
|
883
|
-
if (!response.ok &&
|
|
884
|
-
throw new Error('当前团队服务端可能尚不支持 Claude Code
|
|
953
|
+
if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
|
|
954
|
+
throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
|
|
885
955
|
}
|
|
886
956
|
if (!response.ok) throw new Error(result.message || '能力保存失败。')
|
|
887
|
-
|
|
957
|
+
const savedReviewers = Array.isArray(result.reviewers) && result.reviewers.length
|
|
958
|
+
? result.reviewers : [result.runner]
|
|
959
|
+
if ([...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')) {
|
|
960
|
+
if (!editingCardId && typeof result.id === 'string') {
|
|
961
|
+
const cleanup = await fetch('/api/ant-teams/delegation/cards', {
|
|
962
|
+
method: 'DELETE', headers: { 'content-type': 'application/json' },
|
|
963
|
+
body: JSON.stringify({ id: result.id }),
|
|
964
|
+
}).catch(() => null)
|
|
965
|
+
if (!cleanup?.ok) {
|
|
966
|
+
await load().catch(() => undefined)
|
|
967
|
+
throw new Error('团队服务端尚不支持多 Agent 审查配置;旧服务端可能已保存单 Agent 授权,请刷新列表并停止它,再升级 ANT 服务端。')
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
throw new Error('当前团队服务端尚不支持多 Agent 审查配置;请管理员升级 ANT 服务端后重试。')
|
|
971
|
+
}
|
|
972
|
+
setTitle(''); setIntent(''); setExamples(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
|
|
973
|
+
setEditingCardId(null); setEditingExpiresAt(null)
|
|
888
974
|
await load()
|
|
889
975
|
setCreateOpen(false)
|
|
890
|
-
setMessage(
|
|
976
|
+
setMessage(editingCardId
|
|
977
|
+
? '能力配置已更新。新请求将使用新配置,已提交任务保留原配置。'
|
|
978
|
+
: '能力已开放。同事可自然地提出这类请求;参数不完整时机器人会追问。')
|
|
891
979
|
} catch (error) { setMessage(error.message) }
|
|
892
980
|
finally { setBusy(false) }
|
|
893
981
|
}
|
|
@@ -904,6 +992,7 @@ window.__ModuleLoader__.load({
|
|
|
904
992
|
finally { setBusy(false) }
|
|
905
993
|
}
|
|
906
994
|
const copyCardToDraft = (card) => {
|
|
995
|
+
setEditingCardId(null); setEditingExpiresAt(null)
|
|
907
996
|
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
908
997
|
const cardEnrollment = enrolled.find((bot) => bot.bot_fingerprint === card.bot_fingerprint)
|
|
909
998
|
const titleChars = Array.from(`复制:${card.title || '未命名能力'}`)
|
|
@@ -915,6 +1004,7 @@ window.__ModuleLoader__.load({
|
|
|
915
1004
|
setExamples(Array.isArray(card.examples) ? card.examples.join('\n') : '')
|
|
916
1005
|
setTaskPrompt(card.task_prompt || '')
|
|
917
1006
|
setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
|
|
1007
|
+
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh'])
|
|
918
1008
|
setParameters(Array.isArray(card.parameters) ? card.parameters.map((rule) => ({
|
|
919
1009
|
...rule, min: String(rule.min ?? ''), max: String(rule.max ?? ''), choices: Array.isArray(rule.choices) ? rule.choices.join(',') : String(rule.choices ?? ''),
|
|
920
1010
|
})) : [])
|
|
@@ -1061,8 +1151,6 @@ window.__ModuleLoader__.load({
|
|
|
1061
1151
|
? `群 ${groups.find((group) => group.group_id === card.conversation_id)?.group_code || card.conversation_id}` : '私聊'
|
|
1062
1152
|
const requester = card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1063
1153
|
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'
|
|
1064
|
-
const runnerName = card.runner === 'codex' ? '本机 Codex · PR 快照'
|
|
1065
|
-
: card.runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH 文字处理'
|
|
1066
1154
|
const parametersText = (card.parameters || []).map((rule) => {
|
|
1067
1155
|
const constraint = rule.kind === 'gitea_pr' && rule.repository ? `仓库 ${rule.repository}`
|
|
1068
1156
|
: rule.kind === 'choice' ? `选项 ${(rule.choices || []).join('、')}`
|
|
@@ -1081,7 +1169,7 @@ window.__ModuleLoader__.load({
|
|
|
1081
1169
|
h('strong', { style: { overflowWrap: 'anywhere' } }, card.title || '未命名能力'),
|
|
1082
1170
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
|
|
1083
1171
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
|
|
1084
|
-
|
|
1172
|
+
h('span', { style: mutedTextStyle }, `${cardReviewerSummary(card)} · ${conversation} · ${requester} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'}`),
|
|
1085
1173
|
h('details', null,
|
|
1086
1174
|
h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看配置'),
|
|
1087
1175
|
h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
|
|
@@ -1092,6 +1180,7 @@ window.__ModuleLoader__.load({
|
|
|
1092
1180
|
examplesText ? h('p', { style: mutedTextStyle }, `请求示例:${examplesText}`) : null,
|
|
1093
1181
|
card.task_prompt ? h('p', { style: mutedTextStyle }, `处理要求:${card.task_prompt}`) : null))),
|
|
1094
1182
|
h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end' } },
|
|
1183
|
+
h('button', { type: 'button', disabled: busy, onClick: () => editCard(card), style: secondaryButtonStyle }, '编辑'),
|
|
1095
1184
|
h('button', { type: 'button', disabled: busy, onClick: () => copyCardToDraft(card), style: secondaryButtonStyle }, '复制'),
|
|
1096
1185
|
h('button', { type: 'button', disabled: busy, onClick: () => void revokeCard(card), style: secondaryButtonStyle }, '停止')))
|
|
1097
1186
|
}),
|
|
@@ -1127,10 +1216,12 @@ window.__ModuleLoader__.load({
|
|
|
1127
1216
|
h('details', { id: 'ant-new-task', open: createOpen,
|
|
1128
1217
|
onToggle: (event) => setCreateOpen(event.currentTarget.open),
|
|
1129
1218
|
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
|
|
1130
|
-
h('summary', { id: 'ant-new-task-summary', style: { cursor: 'pointer', fontWeight: 600 } }, '开放一项能力'),
|
|
1219
|
+
h('summary', { id: 'ant-new-task-summary', style: { cursor: 'pointer', fontWeight: 600 } }, editingCardId ? '编辑能力配置' : '开放一项能力'),
|
|
1131
1220
|
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))',
|
|
1132
1221
|
gap: 16, alignItems: 'start', marginTop: 12 } },
|
|
1133
1222
|
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 9, minWidth: 0 } },
|
|
1223
|
+
editingCardId ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1224
|
+
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。已提交任务继续使用原配置。`) : null,
|
|
1134
1225
|
h('p', { style: mutedTextStyle }, selectedBot
|
|
1135
1226
|
? `当前机器人:${selectedBot.name}${selectedEnrollmentBound ? ' · 已绑定此设备' : ' · 请先绑定到此设备'}`
|
|
1136
1227
|
: '请先在上方选择机器人并启用协作。'),
|
|
@@ -1150,20 +1241,29 @@ window.__ModuleLoader__.load({
|
|
|
1150
1241
|
onChange: (event) => setExamples(event.target.value), placeholder: '帮我总结一下刚才的讨论', style: inputStyle })),
|
|
1151
1242
|
h('label', null, '处理要求', h('textarea', { required: true, maxLength: 4000, rows: 3, value: taskPrompt,
|
|
1152
1243
|
onChange: (event) => setTaskPrompt(event.target.value), placeholder: '只使用当前会话可见的内容,列出结论和待办', style: inputStyle })),
|
|
1153
|
-
h('
|
|
1154
|
-
|
|
1155
|
-
h('
|
|
1156
|
-
h('
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
'
|
|
1244
|
+
h('fieldset', { style: { ...panelStyle, padding: 10, display: 'grid', gap: 7 } },
|
|
1245
|
+
h('legend', null, '审查 Agent(可多选)'),
|
|
1246
|
+
h('p', { style: mutedTextStyle }, '普通请求使用默认 Agent;明确说“交叉 review”时,所有勾选项会并行审查同一份 PR 快照(至少勾选两位才构成交叉审查,最多三位,并会同时消耗各自的本机或团队模型额度)。'),
|
|
1247
|
+
h('div', { style: { display: 'flex', gap: 14, flexWrap: 'wrap' } },
|
|
1248
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent DSH', checked: reviewers.includes('dsh'), disabled: !dshAvailable && !reviewers.includes('dsh'),
|
|
1249
|
+
onChange: (event) => changeReviewer('dsh', event.target.checked) }), ' DSH · 仅审查已提供的 diff'),
|
|
1250
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Codex', checked: reviewers.includes('codex'),
|
|
1251
|
+
disabled: (!codexAvailable || !hasPrParameter) && !reviewers.includes('codex'),
|
|
1252
|
+
onChange: (event) => changeReviewer('codex', event.target.checked) }), ' Codex · 完整快照只读审查'),
|
|
1253
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Claude Code', checked: reviewers.includes('claude-code'),
|
|
1254
|
+
disabled: (!claudeCodeAvailable || !hasPrParameter) && !reviewers.includes('claude-code'),
|
|
1255
|
+
onChange: (event) => changeReviewer('claude-code', event.target.checked) }), ' Claude Code · 使用本机权限审查')),
|
|
1256
|
+
!hasPrParameter ? h('p', { style: mutedTextStyle }, 'Codex 与 Claude Code 需要先添加一个必填的 Gitea PR 编号参数。') : null,
|
|
1257
|
+
reviewers.length > 1 ? h('label', null, '普通请求默认执行方', h('select', { value: runner,
|
|
1258
|
+
onChange: (event) => setRunner(event.target.value), style: inputStyle },
|
|
1259
|
+
...reviewers.map((item) => h('option', { key: item, value: item }, reviewerLabel(item))))) : null),
|
|
1160
1260
|
nativePrUnavailableReason ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1161
1261
|
`${nativePrUnavailableReason},已禁用 Codex/Claude Code 完整快照选项;可选择 DSH 差异审查。`) : null,
|
|
1162
|
-
|
|
1163
|
-
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head 的临时代码快照。不会收到 Gitea
|
|
1164
|
-
|
|
1165
|
-
'Claude Code 通过 DSH
|
|
1166
|
-
|
|
1262
|
+
reviewers.includes('codex') ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1263
|
+
'Codex 使用这台电脑的登录账号及其用量;ANT 为每项能力维护仓库缓存,每次审查创建固定到 PR Head 的临时代码快照。不会收到 Gitea 令牌;Git LFS 实际文件不会下载。') : null,
|
|
1264
|
+
reviewers.includes('claude-code') ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1265
|
+
'Claude Code 通过 DSH 已注册的子代理运行,可查看同一 PR 快照。ANT 不会传 Gitea 令牌;它沿用 DSH 的本机权限设置,不具备 Codex 路径相同的固定只读隔离。Git LFS 实际文件不会下载。') : null,
|
|
1266
|
+
nativeReviewerSelected ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1167
1267
|
'本机 PR 执行方需要 Gitea granular token 的只读权限 read:repository 和 read:user;read:user 仅用于这台 DSH 获取 Git HTTPS 登录名。') : null,
|
|
1168
1268
|
h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 10 } },
|
|
1169
1269
|
h('summary', { style: { cursor: 'pointer' } }, '参数与授权范围'),
|
|
@@ -1180,7 +1280,7 @@ window.__ModuleLoader__.load({
|
|
|
1180
1280
|
onChange: (event) => updateParameter(index, { kind: event.target.value }), style: { ...inputStyle, flex: '1 1 150px' } },
|
|
1181
1281
|
h('option', { value: 'text' }, '文本'), h('option', { value: 'integer' }, '整数'),
|
|
1182
1282
|
h('option', { value: 'choice' }, '选项'), h('option', { value: 'gitea_pr' }, 'Gitea PR 编号')),
|
|
1183
|
-
h('label', null, h('input', { type: 'checkbox',
|
|
1283
|
+
h('label', null, h('input', { type: 'checkbox', 'aria-label': '参数必填', checked: rule.required,
|
|
1184
1284
|
onChange: (event) => updateParameter(index, { required: event.target.checked }) }), ' 必填'),
|
|
1185
1285
|
['integer', 'gitea_pr'].includes(rule.kind) ? h('input', { 'aria-label': '最小值', type: 'number', value: rule.min,
|
|
1186
1286
|
onChange: (event) => updateParameter(index, { min: event.target.value }), placeholder: '最小值', style: { ...inputStyle, width: 120 } }) : null,
|
|
@@ -1219,8 +1319,9 @@ window.__ModuleLoader__.load({
|
|
|
1219
1319
|
h('option', { value: 'allowlist' }, '指定 IM 用户 ID'))),
|
|
1220
1320
|
senderScope === 'allowlist' ? h('input', { 'aria-label': '可请求的 IM 用户 ID', required: true, value: senderIds,
|
|
1221
1321
|
onChange: (event) => setSenderIds(event.target.value), placeholder: '多个 ID 用逗号分隔', style: inputStyle }) : null,
|
|
1222
|
-
|
|
1223
|
-
h('
|
|
1322
|
+
editingCardId ? h('p', { style: mutedTextStyle }, `有效期:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}(编辑时保持不变)`)
|
|
1323
|
+
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1324
|
+
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天')))),
|
|
1224
1325
|
),
|
|
1225
1326
|
h('aside', { id: 'ant-authorization-review', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
|
|
1226
1327
|
style: { ...settingsPanelStyle, padding: 14, gap: 10,
|
|
@@ -1236,7 +1337,7 @@ window.__ModuleLoader__.load({
|
|
|
1236
1337
|
['可请求人', requesterSummary],
|
|
1237
1338
|
['执行方', executorSummary],
|
|
1238
1339
|
['读取资料', resourceSummary],
|
|
1239
|
-
['有效期', `${days} 天`],
|
|
1340
|
+
['有效期', editingCardId ? new Date(editingExpiresAt * 1000).toLocaleString('zh-CN') : `${days} 天`],
|
|
1240
1341
|
['回复位置', resultRouteSummary],
|
|
1241
1342
|
].map(([label, value]) => h('div', { key: label, style: { display: 'grid', gridTemplateColumns: '84px minmax(0, 1fr)', gap: 8 } },
|
|
1242
1343
|
h('dt', { style: mutedTextStyle }, label),
|
|
@@ -1244,7 +1345,7 @@ window.__ModuleLoader__.load({
|
|
|
1244
1345
|
cardBlockReason ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1245
1346
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, cardBlockReason) : null,
|
|
1246
1347
|
h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
|
|
1247
|
-
style: { ...primaryButtonStyle, width: '100%' } }, '确认并开放能力'))))),
|
|
1348
|
+
style: { ...primaryButtonStyle, width: '100%' } }, editingCardId ? '保存能力修改' : '确认并开放能力'))))),
|
|
1248
1349
|
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1249
1350
|
h('section', { style: { ...settingsPanelStyle, padding: 14 } },
|
|
1250
1351
|
h('strong', null, 'Gitea 本机凭据'),
|
|
@@ -1596,15 +1697,21 @@ window.__ModuleLoader__.load({
|
|
|
1596
1697
|
useEffect(() => { void refresh() }, [refreshToken])
|
|
1597
1698
|
const install = async (plugin) => {
|
|
1598
1699
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
1599
|
-
|
|
1700
|
+
const installSpec = pluginInstallSpec(plugin)
|
|
1701
|
+
if (!installSpec || workingId || uncertainId === plugin.id || completedId === plugin.id) return
|
|
1600
1702
|
const manager = remote?.pluginManager
|
|
1601
1703
|
if (typeof manager?.installBundle !== 'function') {
|
|
1602
1704
|
setMessage('当前 DSH 未提供插件安装接口,请联系管理员。')
|
|
1603
1705
|
return
|
|
1604
1706
|
}
|
|
1605
1707
|
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1708
|
+
const localAhead = local?.version && newerVersion(local.version, plugin.version)
|
|
1709
|
+
const action = localAhead ? '回退到团队版本' : local ? local.version === plugin.version ? '重新安装' : '更新' : '安装'
|
|
1710
|
+
const sourceNotice = artifactUrl
|
|
1711
|
+
? 'DSH 将安装 ANT 固定的顶层制品。'
|
|
1712
|
+
: `DSH 将优先从 npm 官方源以 ${installSpec} 安装此精确版本,并按其配置回退 registry;依赖版本可能因设备不同。`
|
|
1713
|
+
const updateNotice = 'ANT 不会批准依赖构建脚本;插件自身的更新器或 DSH 原生管理页之后仍可安装其他版本。'
|
|
1714
|
+
if (!window.confirm(`在这台 DSH ${action}团队审核的 ${plugin.title} ${plugin.version}?${sourceNotice}${updateNotice} 插件将在本机运行,完成后需要完全重启 DSH。`)) return
|
|
1608
1715
|
setWorkingId(plugin.id)
|
|
1609
1716
|
setMessage(`正在安装 ${plugin.title},请不要退出 DSH…`)
|
|
1610
1717
|
try {
|
|
@@ -1613,13 +1720,15 @@ window.__ModuleLoader__.load({
|
|
|
1613
1720
|
const latest = Array.isArray(catalog?.plugins)
|
|
1614
1721
|
? catalog.plugins.find((item) => item.id === plugin.id) : null
|
|
1615
1722
|
if (!response.ok || latest?.package_name !== plugin.package_name
|
|
1616
|
-
|| latest?.version !== plugin.version || latest
|
|
1617
|
-
|| approvedPluginArtifactUrl(latest) !== artifactUrl) {
|
|
1723
|
+
|| latest?.version !== plugin.version || pluginInstallSpec(latest) !== installSpec) {
|
|
1618
1724
|
throw new Error('插件审核信息已变化,请刷新目录后重试。')
|
|
1619
1725
|
}
|
|
1620
1726
|
const requestId = crypto.randomUUID()
|
|
1621
1727
|
let result
|
|
1622
|
-
|
|
1728
|
+
const options = artifactUrl
|
|
1729
|
+
? { enabled: true, requestId }
|
|
1730
|
+
: { enabled: true, requestId, registry: 'https://registry.npmjs.org/' }
|
|
1731
|
+
try { result = await manager.installBundle(installSpec, options) }
|
|
1623
1732
|
catch { result = null }
|
|
1624
1733
|
if (!result?.ok) {
|
|
1625
1734
|
let recovered = null
|
|
@@ -1671,7 +1780,9 @@ window.__ModuleLoader__.load({
|
|
|
1671
1780
|
return
|
|
1672
1781
|
}
|
|
1673
1782
|
await refresh()
|
|
1674
|
-
|
|
1783
|
+
if (artifactUrl && plugin.artifact_sha256) {
|
|
1784
|
+
try { localStorage.setItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`, plugin.artifact_sha256) } catch {}
|
|
1785
|
+
}
|
|
1675
1786
|
setCompletedId(plugin.id)
|
|
1676
1787
|
setMessage(`${plugin.title} 已安装。请完全退出并重新启动 DSH 后使用。`)
|
|
1677
1788
|
} catch (error) {
|
|
@@ -1682,10 +1793,12 @@ window.__ModuleLoader__.load({
|
|
|
1682
1793
|
const visible = plugins.filter((plugin) => {
|
|
1683
1794
|
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1684
1795
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
1796
|
+
const installSpec = pluginInstallSpec(plugin)
|
|
1797
|
+
const localAhead = installed?.version && newerVersion(installed.version, plugin.version)
|
|
1685
1798
|
const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
|
|
1686
1799
|
return matches && (activeFilter === 'all' || activeFilter === 'installed' && Boolean(installed)
|
|
1687
|
-
|| activeFilter === 'available' &&
|
|
1688
|
-
|| activeFilter === 'updates' &&
|
|
1800
|
+
|| activeFilter === 'available' && installSpec && !localAhead && (!installed || (installed.version && installed.version !== plugin.version))
|
|
1801
|
+
|| activeFilter === 'updates' && installSpec && installed && installed.version && installed.version !== plugin.version && !localAhead
|
|
1689
1802
|
|| activeFilter === 'retired' && plugin.status !== 'published')
|
|
1690
1803
|
})
|
|
1691
1804
|
const noMatches = embedded
|
|
@@ -1707,7 +1820,7 @@ window.__ModuleLoader__.load({
|
|
|
1707
1820
|
embedded ? null : h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 } },
|
|
1708
1821
|
h('div', null,
|
|
1709
1822
|
h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选插件'),
|
|
1710
|
-
h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。')),
|
|
1823
|
+
h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。DSH 0.2.0-rc.2 不会自动更新 Bundle;插件自身的更新器可能绕过此目录安装其他版本。')),
|
|
1711
1824
|
h('button', { type: 'button', disabled: loading, onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
|
|
1712
1825
|
!loading && !plugins.length && !loadError ? embedded
|
|
1713
1826
|
? h('p', { role: 'status', style: mutedTextStyle }, '团队暂未发布精选插件。')
|
|
@@ -1729,16 +1842,19 @@ window.__ModuleLoader__.load({
|
|
|
1729
1842
|
const sameVersion = local?.version === plugin.version
|
|
1730
1843
|
const security = plugin.status === 'security_withdrawn'
|
|
1731
1844
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
1845
|
+
const installSpec = pluginInstallSpec(plugin)
|
|
1846
|
+
const localAhead = local?.version && newerVersion(local.version, plugin.version)
|
|
1732
1847
|
const sourceKnown = typeof local?.source === 'string'
|
|
1733
1848
|
const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
|
|
1734
1849
|
const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
|
|
1735
1850
|
const status = security ? '已废弃(安全风险)'
|
|
1736
|
-
: plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' :
|
|
1851
|
+
: plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' : localAhead
|
|
1852
|
+
? '本机版本高于团队目录版本' : sameVersion
|
|
1737
1853
|
? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
|
|
1738
1854
|
: sourceKnown ? '本机同版,来源不同' : recordedInstall
|
|
1739
1855
|
? '本机同版,曾由企业扩展安装;当前 DSH 无法复核来源' : '本机同版,当前 DSH 无法核对来源'
|
|
1740
|
-
:
|
|
1741
|
-
: local ? `本机版本 ${local.version || '暂无法读取'}` : '
|
|
1856
|
+
: installSpec ? local ? local.version ? `可更新 · 本机 ${local.version}` : '本机版本暂无法读取' : artifactUrl ? '可安装' : '可从 npm 安装'
|
|
1857
|
+
: local ? `本机版本 ${local.version || '暂无法读取'}` : '已发布版本暂不可安装'
|
|
1742
1858
|
return h('article', { key: plugin.id, style: security
|
|
1743
1859
|
? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
|
|
1744
1860
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
@@ -1751,6 +1867,10 @@ window.__ModuleLoader__.load({
|
|
|
1751
1867
|
h('p', { style: mutedTextStyle }, `${plugin.package_name}@${plugin.version} · 许可证:${plugin.license || '未声明'}`),
|
|
1752
1868
|
h('p', { style: mutedTextStyle }, `来源:${plugin.source_url || '未提供'} · DSH 版本要求:${plugin.peer_range || '包未声明'}`),
|
|
1753
1869
|
h('p', { style: mutedTextStyle }, `额外配置:${plugin.requirements || '无'}`),
|
|
1870
|
+
installSpec && !artifactUrl ? h('p', { role: 'note', style: mutedTextStyle },
|
|
1871
|
+
'ANT 通过 DSH 包管理器安装目录中的精确 npm 版本;实际下载和依赖解析由 DSH 执行。ANT 不批准依赖构建脚本;若 DSH 因此拒绝安装,请按 DSH 提示在原生插件管理页处理。插件自身更新器或 DSH 原生管理也可更新到目录外的版本。') : null,
|
|
1872
|
+
plugin.dependencies && artifactUrl ? h('p', { role: 'note', style: mutedTextStyle },
|
|
1873
|
+
'此插件声明了依赖。ANT 固定插件包本身;DSH 会在本机按当前配置解析依赖,不同设备可能得到不同版本。依赖构建脚本由 DSH 单独请求批准。') : null,
|
|
1754
1874
|
plugin.scripts ? h('p', { style: mutedTextStyle }, `包自身的安装脚本:${plugin.scripts}`) : null,
|
|
1755
1875
|
plugin.status !== 'published' ? h('p', { role: 'status', style: security
|
|
1756
1876
|
? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)' } : mutedTextStyle },
|
|
@@ -1760,16 +1880,17 @@ window.__ModuleLoader__.load({
|
|
|
1760
1880
|
? '此前曾由企业扩展安装;当前 DSH 无法再次核对来源。正常使用无需重复安装;若后来换过来源,可以重新安装。'
|
|
1761
1881
|
: '已装同版且使用正常时无需重复安装;若想改用团队审核包,可自愿安装。') : null,
|
|
1762
1882
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
1763
|
-
|
|
1883
|
+
installSpec && (!local || (local.version && local.version !== plugin.version && (!localAhead || artifactUrl))
|
|
1884
|
+
|| (artifactUrl && sameVersion && !verifiedLocal))
|
|
1764
1885
|
? h('button', { type: 'button', disabled: Boolean(workingId) || uncertainId === plugin.id || completedId === plugin.id,
|
|
1765
1886
|
onClick: () => void install(plugin), style: primaryButtonStyle },
|
|
1766
1887
|
workingId === plugin.id ? '安装中…' : uncertainId === plugin.id ? '先核对安装结果'
|
|
1767
1888
|
: completedId === plugin.id ? '重启 DSH 后生效'
|
|
1768
|
-
: local ? sameVersion ? '按团队审核包重新安装' : '更新到审核版本' : '安装到这台 DSH') : null,
|
|
1769
|
-
plugin.status === 'published' && !local && !
|
|
1889
|
+
: local ? sameVersion || localAhead ? '按团队审核包重新安装' : '更新到审核版本' : '安装到这台 DSH') : null,
|
|
1890
|
+
plugin.status === 'published' && !local && !installSpec
|
|
1770
1891
|
? h('p', { style: mutedTextStyle }, '如需安装,请联系企业管理员安排。') : null,
|
|
1771
1892
|
local || uncertainId === plugin.id ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
|
|
1772
|
-
local && !sameVersion && plugin.status === 'published' && !
|
|
1893
|
+
local && !sameVersion && plugin.status === 'published' && !installSpec
|
|
1773
1894
|
? h('p', { style: mutedTextStyle }, '本机版本与团队审核版本不同;请联系企业管理员核对。') : null))
|
|
1774
1895
|
})) : !loading && plugins.length && (!embedded || !hasSkillMatches)
|
|
1775
1896
|
? h('p', { style: mutedTextStyle }, noMatches) : null))
|
|
@@ -1790,6 +1911,11 @@ window.__ModuleLoader__.load({
|
|
|
1790
1911
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
1791
1912
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
1792
1913
|
const [authorizedModels, setAuthorizedModels] = useState([])
|
|
1914
|
+
const [serverDefaultModel, setServerDefaultModel] = useState(null)
|
|
1915
|
+
const [dshDefaultSelection, setDshDefaultSelection] = useState(null)
|
|
1916
|
+
const [defaultModelDraft, setDefaultModelDraft] = useState('')
|
|
1917
|
+
const [defaultModelBusy, setDefaultModelBusy] = useState(false)
|
|
1918
|
+
const [defaultModelMessage, setDefaultModelMessage] = useState('')
|
|
1793
1919
|
const [lastModelSync, setLastModelSync] = useState(null)
|
|
1794
1920
|
const [serviceCompatibility, setServiceCompatibility] = useState({ state: 'idle', message: '' })
|
|
1795
1921
|
const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
|
|
@@ -1863,27 +1989,87 @@ window.__ModuleLoader__.load({
|
|
|
1863
1989
|
return status
|
|
1864
1990
|
}
|
|
1865
1991
|
|
|
1866
|
-
const
|
|
1992
|
+
const modelProvider = (model) => model.protocol === 'anthropic' ? 'ant-team-anthropic'
|
|
1993
|
+
: model.openai_api_mode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
|
|
1994
|
+
const modelSelectionKey = (model) => JSON.stringify({ protocol: model.protocol, model_id: model.model_id })
|
|
1995
|
+
const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
1996
|
+
const readDefaultPreference = (rawServiceUrl) => {
|
|
1997
|
+
try { return JSON.parse(localStorage.getItem(defaultPreferenceKey(rawServiceUrl)) || 'null') } catch { return null }
|
|
1998
|
+
}
|
|
1999
|
+
const saveDefaultPreference = (rawServiceUrl, value) => {
|
|
2000
|
+
try { localStorage.setItem(defaultPreferenceKey(rawServiceUrl), JSON.stringify(value)) } catch {}
|
|
2001
|
+
}
|
|
2002
|
+
const modelForProviderSelection = (selection, models) => {
|
|
2003
|
+
if (!selection) return null
|
|
2004
|
+
const exact = models.find((model) => model.model_id === selection.model && modelProvider(model) === selection.provider)
|
|
2005
|
+
if (exact) return exact
|
|
2006
|
+
const legacyProtocol = selection.provider === 'ant-team-openai' || selection.provider?.startsWith('ant-team-openai-')
|
|
2007
|
+
? 'openai' : selection.provider === 'ant-team-anthropic' ? 'anthropic' : null
|
|
2008
|
+
return legacyProtocol ? models.find((model) => model.protocol === legacyProtocol && model.model_id === selection.model) : null
|
|
2009
|
+
}
|
|
2010
|
+
const readDshDefaultSelection = async () => {
|
|
2011
|
+
if (typeof remote?.settings?.describe !== 'function') throw new Error('当前 DSH 未提供全局默认模型读取接口。')
|
|
2012
|
+
const response = await remote.settings.describe()
|
|
2013
|
+
if (!response?.ok) throw new Error(response?.error?.message || '无法读取这台 DSH 的全局默认模型。')
|
|
2014
|
+
const value = response.value?.namespaces?.find((entry) => entry.ns === 'agent-default-model')?.value
|
|
2015
|
+
return value && typeof value.provider === 'string' && typeof value.model === 'string'
|
|
2016
|
+
? { provider: value.provider, model: value.model,
|
|
2017
|
+
...(typeof value.reasoningEffort === 'string' ? { reasoningEffort: value.reasoningEffort } : {}) } : null
|
|
2018
|
+
}
|
|
2019
|
+
const writeDshDefaultModel = async (model) => {
|
|
2020
|
+
const response = await remote.settings.mutate('agent-default-model', [
|
|
2021
|
+
{ op: 'set', path: ['provider'], value: modelProvider(model) },
|
|
2022
|
+
{ op: 'set', path: ['model'], value: model.model_id },
|
|
2023
|
+
{ op: 'unset', path: ['reasoningEffort'] },
|
|
2024
|
+
], undefined)
|
|
2025
|
+
if (!response?.ok) throw new Error(response?.error?.message || 'DSH 无法保存全局默认模型。')
|
|
2026
|
+
const selection = { provider: modelProvider(model), model: model.model_id }
|
|
2027
|
+
setDshDefaultSelection(selection)
|
|
2028
|
+
setDefaultModelDraft(modelSelectionKey(model))
|
|
2029
|
+
return selection
|
|
2030
|
+
}
|
|
2031
|
+
const applyUserDefaultModel = async (model) => {
|
|
2032
|
+
if (!model || !authorizedModels.some((item) => modelSelectionKey(item) === modelSelectionKey(model))) {
|
|
2033
|
+
setDefaultModelMessage('请选择当前已获准的模型。')
|
|
2034
|
+
return
|
|
2035
|
+
}
|
|
2036
|
+
setDefaultModelBusy(true)
|
|
2037
|
+
try {
|
|
2038
|
+
const selection = await writeDshDefaultModel(model)
|
|
2039
|
+
const preference = readDefaultPreference(serviceUrl) || { initialized: true, serverDefault: '' }
|
|
2040
|
+
const isServerDefault = serverDefaultModel && modelSelectionKey(model) === modelSelectionKey(serverDefaultModel)
|
|
2041
|
+
if (isServerDefault) preference.serverDefault = modelSelectionKey(serverDefaultModel)
|
|
2042
|
+
preference.initialized = true
|
|
2043
|
+
saveDefaultPreference(serviceUrl, preference)
|
|
2044
|
+
setDshDefaultSelection(selection)
|
|
2045
|
+
setDefaultModelMessage(`已将 ${model.display_name || model.model_id} 设为 DSH 全局默认模型。`)
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
setDefaultModelMessage(error instanceof Error ? error.message : '保存本机默认模型失败。')
|
|
2048
|
+
} finally { setDefaultModelBusy(false) }
|
|
2049
|
+
}
|
|
2050
|
+
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null) => {
|
|
1867
2051
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
1868
2052
|
if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
|
|
1869
2053
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 版本未提供模型设置接口。')
|
|
1870
|
-
const
|
|
1871
|
-
openai:
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
const protocolModels =
|
|
2054
|
+
const groups = [
|
|
2055
|
+
{ route: 'ant-team-openai-chat-completions', protocol: 'openai', mode: 'chat_completions', api: 'openai-completions' },
|
|
2056
|
+
{ route: 'ant-team-openai-responses', protocol: 'openai', mode: 'responses', api: 'openai-responses' },
|
|
2057
|
+
{ route: 'ant-team-anthropic', protocol: 'anthropic', api: 'anthropic-messages' },
|
|
2058
|
+
]
|
|
2059
|
+
const ops = [{ op: 'unset', path: ['providers', 'ant-team-openai'] }]
|
|
2060
|
+
for (const group of groups) {
|
|
2061
|
+
const protocolModels = models.filter((model) => model.protocol === group.protocol
|
|
2062
|
+
&& (group.protocol !== 'openai' || (model.openai_api_mode || 'chat_completions') === group.mode))
|
|
2063
|
+
const route = group.route
|
|
1878
2064
|
if (protocolModels.length === 0) {
|
|
1879
2065
|
ops.push({ op: 'unset', path: ['providers', route] })
|
|
1880
2066
|
continue
|
|
1881
2067
|
}
|
|
1882
2068
|
ops.push({ op: 'set', path: ['providers', route], value: {
|
|
1883
|
-
displayName: protocol === '
|
|
2069
|
+
displayName: group.protocol === 'anthropic' ? 'ANT 团队 · Anthropic' : `ANT 团队 · OpenAI · ${group.mode === 'responses' ? 'Responses' : 'Chat Completions'}`,
|
|
1884
2070
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
1885
|
-
api:
|
|
1886
|
-
baseURL: protocol === 'openai'
|
|
2071
|
+
api: group.api,
|
|
2072
|
+
baseURL: group.protocol === 'openai'
|
|
1887
2073
|
? new URL('/model-gateway/openai/v1', normalized).toString()
|
|
1888
2074
|
: new URL('/model-gateway/anthropic', normalized).toString(),
|
|
1889
2075
|
models: protocolModels.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
@@ -1891,9 +2077,56 @@ window.__ModuleLoader__.load({
|
|
|
1891
2077
|
}
|
|
1892
2078
|
const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
1893
2079
|
if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
|
|
2080
|
+
const defaultMatch = rawDefaultModel && models.find((model) => model.protocol === rawDefaultModel.protocol
|
|
2081
|
+
&& model.model_id === rawDefaultModel.model_id)
|
|
2082
|
+
const serverDefault = defaultMatch || null
|
|
2083
|
+
const initialDefault = defaultMatch || (rawDefaultModel == null ? models[0] : null)
|
|
2084
|
+
setServerDefaultModel(serverDefault)
|
|
2085
|
+
const preference = readDefaultPreference(normalized)
|
|
2086
|
+
let statusMessage = ''
|
|
2087
|
+
let selection
|
|
2088
|
+
if (!preference?.initialized && initialDefault) {
|
|
2089
|
+
const previousDefault = await readDshDefaultSelection()
|
|
2090
|
+
selection = await writeDshDefaultModel(initialDefault)
|
|
2091
|
+
saveDefaultPreference(normalized, { initialized: true, serverDefault: modelSelectionKey(serverDefault || initialDefault), previousDefault })
|
|
2092
|
+
statusMessage = serverDefault
|
|
2093
|
+
? '已按团队默认模型设置 DSH 全局默认。该 Profile 的新会话会使用此模型;你之后可在此更改,刷新不会覆盖选择。'
|
|
2094
|
+
: '当前无法使用团队默认模型;已用首个获准模型初始化 DSH 全局默认。请管理员检查默认设置和成员授权。'
|
|
2095
|
+
} else {
|
|
2096
|
+
selection = await readDshDefaultSelection()
|
|
2097
|
+
const currentAuthorized = modelForProviderSelection(selection, models)
|
|
2098
|
+
const baseline = models.find((model) => modelSelectionKey(model) === preference?.serverDefault)
|
|
2099
|
+
const stillUsingBaseline = baseline && currentAuthorized && modelSelectionKey(currentAuthorized) === modelSelectionKey(baseline)
|
|
2100
|
+
if (currentAuthorized && selection?.provider !== modelProvider(currentAuthorized)) {
|
|
2101
|
+
selection = await writeDshDefaultModel(currentAuthorized)
|
|
2102
|
+
}
|
|
2103
|
+
if (serverDefault && stillUsingBaseline && modelSelectionKey(serverDefault) !== preference.serverDefault) {
|
|
2104
|
+
selection = await writeDshDefaultModel(serverDefault)
|
|
2105
|
+
saveDefaultPreference(normalized, { ...preference, initialized: true, serverDefault: modelSelectionKey(serverDefault) })
|
|
2106
|
+
statusMessage = '团队默认模型已更新;你尚未设置个人覆盖,因此已同步到 DSH 全局默认。'
|
|
2107
|
+
} else if (selection?.provider?.startsWith('ant-team-') && !currentAuthorized && models.length) {
|
|
2108
|
+
const fallback = serverDefault || models[0]
|
|
2109
|
+
selection = await writeDshDefaultModel(fallback)
|
|
2110
|
+
saveDefaultPreference(normalized, { ...preference, initialized: true,
|
|
2111
|
+
serverDefault: modelSelectionKey(serverDefault || fallback) })
|
|
2112
|
+
statusMessage = serverDefault
|
|
2113
|
+
? '当前默认模型已失去授权,已恢复为团队默认模型。'
|
|
2114
|
+
: '当前默认模型已失去授权,已改用首个获准模型;请管理员设置团队默认模型。'
|
|
2115
|
+
}
|
|
2116
|
+
const effectiveAuthorized = modelForProviderSelection(selection, models)
|
|
2117
|
+
setDshDefaultSelection(selection)
|
|
2118
|
+
setDefaultModelDraft(effectiveAuthorized ? modelSelectionKey(effectiveAuthorized) : '')
|
|
2119
|
+
if (!serverDefault && !statusMessage) statusMessage = rawDefaultModel
|
|
2120
|
+
? '团队默认模型不在这台设备的获准范围内;请联系管理员调整模型权限。'
|
|
2121
|
+
: '团队默认模型尚未设置;请联系管理员设置,或在下方选择本机可用的默认模型。'
|
|
2122
|
+
else if (!statusMessage) statusMessage = effectiveAuthorized
|
|
2123
|
+
? '已保留你选择的 DSH 全局默认模型。'
|
|
2124
|
+
: '当前 DSH 默认不属于 ANT 获准模型;ANT 对话和任务会使用服务端默认模型。'
|
|
2125
|
+
}
|
|
2126
|
+
setDefaultModelMessage(statusMessage)
|
|
1894
2127
|
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized) } catch {}
|
|
1895
2128
|
onServiceUrlChange(normalized)
|
|
1896
|
-
return models.length
|
|
2129
|
+
return { count: models.length, defaultSelection: selection }
|
|
1897
2130
|
}
|
|
1898
2131
|
|
|
1899
2132
|
const refreshModels = async () => {
|
|
@@ -1911,13 +2144,13 @@ window.__ModuleLoader__.load({
|
|
|
1911
2144
|
if (response.status === 401) setNeedsReconnect(true)
|
|
1912
2145
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
1913
2146
|
}
|
|
1914
|
-
const
|
|
2147
|
+
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model)
|
|
1915
2148
|
setAuthorizedModels(result.models)
|
|
1916
2149
|
setLastModelSync(new Date())
|
|
1917
2150
|
setCredentialConfigured(true)
|
|
1918
2151
|
setNeedsReconnect(false)
|
|
1919
2152
|
setReauthenticate(false)
|
|
1920
|
-
setModelMessage(`模型目录已刷新,共 ${count} 个获准模型。`)
|
|
2153
|
+
setModelMessage(`模型目录已刷新,共 ${sync.count} 个获准模型。`)
|
|
1921
2154
|
} catch (error) {
|
|
1922
2155
|
setModelMessage(error instanceof Error ? error.message : '刷新模型失败,请稍后重试。')
|
|
1923
2156
|
} finally {
|
|
@@ -2071,7 +2304,32 @@ window.__ModuleLoader__.load({
|
|
|
2071
2304
|
|
|
2072
2305
|
const clearLocalConnection = async () => {
|
|
2073
2306
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('DSH 未提供模型设置接口。')
|
|
2307
|
+
const selection = await readDshDefaultSelection()
|
|
2308
|
+
if (selection?.provider?.startsWith('ant-team-')) {
|
|
2309
|
+
const previous = readDefaultPreference(serviceUrl)?.previousDefault
|
|
2310
|
+
const restore = previous && typeof previous.provider === 'string' && typeof previous.model === 'string'
|
|
2311
|
+
&& !previous.provider.startsWith('ant-team-')
|
|
2312
|
+
const operations = restore
|
|
2313
|
+
? [
|
|
2314
|
+
{ op: 'set', path: ['provider'], value: previous.provider },
|
|
2315
|
+
{ op: 'set', path: ['model'], value: previous.model },
|
|
2316
|
+
previous.reasoningEffort
|
|
2317
|
+
? { op: 'set', path: ['reasoningEffort'], value: previous.reasoningEffort }
|
|
2318
|
+
: { op: 'unset', path: ['reasoningEffort'] },
|
|
2319
|
+
]
|
|
2320
|
+
: [
|
|
2321
|
+
{ op: 'unset', path: ['provider'] },
|
|
2322
|
+
{ op: 'unset', path: ['model'] },
|
|
2323
|
+
{ op: 'unset', path: ['reasoningEffort'] },
|
|
2324
|
+
]
|
|
2325
|
+
const reset = await remote.settings.mutate('agent-default-model', [
|
|
2326
|
+
...operations,
|
|
2327
|
+
], undefined)
|
|
2328
|
+
if (!reset.ok) throw new Error(reset.error?.message || 'DSH 全局默认模型未能恢复。')
|
|
2329
|
+
}
|
|
2074
2330
|
const settingsResult = await remote.settings.mutate('llm-pi-ai', [
|
|
2331
|
+
{ op: 'unset', path: ['providers', 'ant-team-openai-chat-completions'] },
|
|
2332
|
+
{ op: 'unset', path: ['providers', 'ant-team-openai-responses'] },
|
|
2075
2333
|
{ op: 'unset', path: ['providers', 'ant-team-openai'] },
|
|
2076
2334
|
{ op: 'unset', path: ['providers', 'ant-team-anthropic'] },
|
|
2077
2335
|
], undefined)
|
|
@@ -2080,6 +2338,8 @@ window.__ModuleLoader__.load({
|
|
|
2080
2338
|
const credentialResult = await remote.credentials.unset('ANT_MODEL_API_KEY')
|
|
2081
2339
|
if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
|
|
2082
2340
|
setCredentialConfigured(false)
|
|
2341
|
+
setAuthorizedModels([]); setServerDefaultModel(null); setDshDefaultSelection(null); setDefaultModelDraft('')
|
|
2342
|
+
try { localStorage.removeItem(`${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(serviceUrl)}:${savedDeviceId() || ''}`) } catch {}
|
|
2083
2343
|
onConnectionChange?.(null)
|
|
2084
2344
|
setNeedsReconnect(false)
|
|
2085
2345
|
setReauthenticate(false)
|
|
@@ -2259,7 +2519,7 @@ window.__ModuleLoader__.load({
|
|
|
2259
2519
|
const credentialWrite = await remote.credentials.set('ANT_MODEL_API_KEY', result.token)
|
|
2260
2520
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
2261
2521
|
credentialSaved = true
|
|
2262
|
-
await applyAuthorizedModels(result.models, normalized)
|
|
2522
|
+
await applyAuthorizedModels(result.models, normalized, result.default_model)
|
|
2263
2523
|
setAuthorizedModels(result.models)
|
|
2264
2524
|
setLastModelSync(new Date())
|
|
2265
2525
|
setCode('')
|
|
@@ -2290,13 +2550,14 @@ window.__ModuleLoader__.load({
|
|
|
2290
2550
|
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
2291
2551
|
const updateGuideUrl = pinnedServiceUrl
|
|
2292
2552
|
? new URL('/ant-update', pinnedServiceUrl).href : null
|
|
2553
|
+
const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels)
|
|
2293
2554
|
const modelRows = authorizedModels.map((model) => h('div', { key: `${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
2294
2555
|
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
|
|
2295
2556
|
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2296
2557
|
h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, model.display_name || model.model_id),
|
|
2297
2558
|
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
2298
2559
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
2299
|
-
|
|
2560
|
+
model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${(model.openai_api_mode || 'chat_completions') === 'responses' ? 'Responses' : 'Chat Completions'}`),
|
|
2300
2561
|
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, '已同步到 DSH')))
|
|
2301
2562
|
const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
2302
2563
|
h('div', null,
|
|
@@ -2360,6 +2621,29 @@ window.__ModuleLoader__.load({
|
|
|
2360
2621
|
backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
|
|
2361
2622
|
fontSize: 12,
|
|
2362
2623
|
} }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
|
|
2624
|
+
h('section', { style: { ...infoNoticeStyle, gap: 8 } },
|
|
2625
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap', alignItems: 'center' } },
|
|
2626
|
+
h('strong', null, 'ANT 默认模型'),
|
|
2627
|
+
h('span', { style: mutedTextStyle }, serverDefaultModel
|
|
2628
|
+
? `团队默认:${serverDefaultModel.display_name || serverDefaultModel.model_id}`
|
|
2629
|
+
: '团队默认尚未设置,或当前成员无权使用')),
|
|
2630
|
+
h('p', { style: mutedTextStyle }, '首次同步时,ANT 会用团队默认初始化 DSH 全局默认模型。它会影响该 Profile 的新会话以及 ANT 对话和任务;之后刷新保留你的个人覆盖。'),
|
|
2631
|
+
h('label', { style: { display: 'grid', gap: 5, maxWidth: 720 } }, 'DSH 全局默认模型',
|
|
2632
|
+
h('select', { 'aria-label': 'DSH 全局默认模型', value: defaultModelDraft,
|
|
2633
|
+
disabled: credentialConfigured !== true || !authorizedModels.length || defaultModelBusy || busy,
|
|
2634
|
+
onChange: (event) => setDefaultModelDraft(event.target.value), style: inputStyle },
|
|
2635
|
+
h('option', { value: '' }, '选择获准模型'),
|
|
2636
|
+
...authorizedModels.map((model) => h('option', { key: modelSelectionKey(model), value: modelSelectionKey(model) },
|
|
2637
|
+
`${model.display_name || model.model_id} · ${model.protocol === 'anthropic' ? 'Anthropic' : (model.openai_api_mode || 'chat_completions') === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'}`)))),
|
|
2638
|
+
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
2639
|
+
h('button', { type: 'button', disabled: defaultModelBusy || !defaultModelDraft,
|
|
2640
|
+
onClick: () => { const chosen = authorizedModels.find((model) => modelSelectionKey(model) === defaultModelDraft); void applyUserDefaultModel(chosen) },
|
|
2641
|
+
style: primaryButtonStyle }, defaultModelBusy ? '保存中…' : '保存为 DSH 全局默认'),
|
|
2642
|
+
h('button', { type: 'button', disabled: defaultModelBusy || !serverDefaultModel
|
|
2643
|
+
|| modelSelectionKey(currentDshDefaultModel || {}) === modelSelectionKey(serverDefaultModel),
|
|
2644
|
+
onClick: () => void applyUserDefaultModel(serverDefaultModel), style: secondaryButtonStyle }, '恢复团队默认'),
|
|
2645
|
+
currentDshDefaultModel ? h('span', { style: mutedTextStyle }, `当前 DSH 默认:${currentDshDefaultModel.display_name || currentDshDefaultModel.model_id}`) : null),
|
|
2646
|
+
defaultModelMessage ? h('p', { role: 'status', style: mutedTextStyle }, defaultModelMessage) : null),
|
|
2363
2647
|
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2364
2648
|
modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
|
|
2365
2649
|
h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
|
|
@@ -2368,7 +2652,7 @@ window.__ModuleLoader__.load({
|
|
|
2368
2652
|
credentialConfigured !== true ? h('button', { type: 'button', onClick: () => onSelectTab('connection'),
|
|
2369
2653
|
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
2370
2654
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
2371
|
-
h('button', { type: 'button', disabled: busy || !bindingReady || credentialConfigured !== true,
|
|
2655
|
+
h('button', { type: 'button', disabled: busy || defaultModelBusy || !bindingReady || credentialConfigured !== true,
|
|
2372
2656
|
onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2373
2657
|
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString('zh-CN')}`) : null),
|
|
2374
2658
|
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|