@ai-wendao/ant 0.4.14 → 0.5.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 +265 -94
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to its team's
|
|
|
4
4
|
|
|
5
5
|
## 团队模型默认值
|
|
6
6
|
|
|
7
|
-
团队管理员在 ANT 控制台设置团队默认模型,并授权成员可用模型。成员第一次在 ANT 插件中同步模型时,团队默认会写入 DSH
|
|
7
|
+
团队管理员在 ANT 控制台设置团队默认模型,并授权成员可用模型。成员第一次在 ANT 插件中同步模型时,团队默认会写入 DSH 的全局默认模型;成员之后可在“团队模型”页选另一项已获准模型。成员也可按 provider 选择同步到本机 DSH 的模型路由;首次升级保留已有路由,关闭某项并刷新后会从本机 Profile 移除,保存了该路由的历史会话可能暂时无法调用,重新启用并刷新可恢复。普通同步保留成员默认选择;若模型被撤销,插件会回到仍获准的团队默认。该默认写入 DSH profile,因此也影响同一 profile 新建的普通 DSH 会话。ANT 对话和任务会额外校验当前用户的获准模型;Codex 与 Claude Code 使用各自执行器的默认模型。
|
|
8
8
|
|
|
9
9
|
每个 OpenAI 兼容模型可配置 Chat Completions 或 Responses API。旧模型记录在升级后按 Chat Completions 解释;ANT 服务端继续使用原配置的 `/chat/completions` URL,并从相同 API 根路径派生 `/responses` 上游路径。
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to its team's
|
|
|
14
14
|
|
|
15
15
|
飞书机器人连接和消息收发由 DSH-IM 负责;ANT Bundle 不保存机器人密钥,也不直连 IM 平台。成员在 ANT 插件的“协作”页选择机器人、启用本机协作,并设置私聊和各群的普通聊天开关。群第一次 @ 机器人后会出现在群列表。群聊天开关只控制普通聊天;任务卡还要单独指定私聊或群、请求人范围和有效期。
|
|
16
16
|
|
|
17
|
-
开放任务时,成员用自然语言描述什么时候执行、要看哪些资料、怎样处理和返回结果。描述中的 Gitea PR 链接、仓库和编号也用自然语言表达。点击“识别规则”会调用一次当前获准的 DSH
|
|
17
|
+
开放任务时,成员用自然语言描述什么时候执行、要看哪些资料、怎样处理和返回结果。描述中的 Gitea PR 链接、仓库和编号也用自然语言表达。点击“识别规则”会调用一次当前获准的 DSH 团队模型,只生成草稿,不保存任务;草稿包含明确触发短语,由机器人主人检查后再开放。机器人主人指在本人 DSH 中连接并管理该 DSH-IM 机器人的 ANT 成员,不代表群管理员身份;群内谁能请求任务仍由规则中的群范围和请求人范围决定。任务只在请求消息以已配置短语开头时执行;检查摘要不合意时,修改描述并重新识别。任务名称、触发条件、PR 信息和参数由识别结果转成内部规则,界面不要求逐项填写。执行方、使用范围、请求人和有效期由通用控件控制。
|
|
18
18
|
|
|
19
19
|
群任务和群聊天设置只在 ANT 协作页配置。当前版本不设群管理员身份绑定,也不支持在群里开放任务、确认授权或开关群聊。群成员可以普通聊天,或按已开放任务的群范围、请求人范围、触发条件和有效期发起任务;未开放的任务不会因为群成员的消息而执行。关闭群普通聊天不会撤销已开放任务。服务端升级时会停用没有明确触发方式的旧任务卡;要继续使用这类任务,需在新界面重新描述规则并开放。
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ PR 快照任务使用已核验的 PR Head SHA 创建独立工作树。Codex 使
|
|
|
26
26
|
|
|
27
27
|
## 企业精选技能
|
|
28
28
|
|
|
29
|
-
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md`
|
|
29
|
+
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md` 导入技能,核对来源、文件和依赖后发布,并为企业技能和插件指定主要用途类别。成员从 DSH 左侧“企业扩展”进入目录,可组合筛选办公文档、编程开发、设计与多媒体、数据分析、协作沟通、写作与营销、自动化与效率、运维与安全或其他类别,同时按来源范围、技能/插件类型和安装状态筛选或搜索。类别只用于发现内容;“本企业”“全市场精选”和“DSH 官方”是独立来源范围。ANT 插件设置页只处理连接、模型和更新。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
|
|
30
30
|
|
|
31
31
|
安装不会自动运行技能脚本或安装 Python、Node 等系统依赖。ANT 只更新或卸载自己管理且本机内容未被修改的技能目录;发现同名个人技能或本机改动时会停止覆盖。
|
|
32
32
|
|
package/client.js
CHANGED
|
@@ -13,6 +13,7 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
}
|
|
14
14
|
const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
|
|
15
15
|
const PROVIDER_ROUTES_STORAGE_KEY = 'ant-team-provider-routes'
|
|
16
|
+
const PROVIDER_SYNC_EXCLUSIONS_KEY = 'ant-team-provider-sync-exclusions'
|
|
16
17
|
const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
|
|
17
18
|
const DEFAULT_MODEL_INITIALIZED_KEY = 'ant-team-default-model-initialized'
|
|
18
19
|
const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
|
|
@@ -31,32 +32,41 @@ window.__ModuleLoader__.load({
|
|
|
31
32
|
} catch { return 'connection' }
|
|
32
33
|
}
|
|
33
34
|
const fileCountFormat = new Intl.NumberFormat('zh-CN')
|
|
34
|
-
const
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'ant-
|
|
42
|
-
'ant-
|
|
43
|
-
'ant-
|
|
44
|
-
'ant-
|
|
45
|
-
'ant-
|
|
46
|
-
'ant-
|
|
47
|
-
'ant-
|
|
48
|
-
'ant-
|
|
35
|
+
const extensionCategories = [
|
|
36
|
+
['office', '办公文档'], ['development', '编程开发'], ['design', '设计与多媒体'],
|
|
37
|
+
['data', '数据分析'], ['collaboration', '协作沟通'], ['content', '写作与营销'],
|
|
38
|
+
['automation', '自动化与效率'], ['operations', '运维与安全'], ['other', '其他'],
|
|
39
|
+
]
|
|
40
|
+
const extensionCategoryLabels = Object.fromEntries(extensionCategories)
|
|
41
|
+
const legacyMarketSkillCategories = {
|
|
42
|
+
'ant-ppt-master': 'office', 'ant-frontend-slides': 'design', 'ant-slidev': 'development',
|
|
43
|
+
'ant-word-docx': 'office', 'ant-excel-xlsx': 'office', 'ant-pdf': 'office',
|
|
44
|
+
'ant-internal-comms': 'collaboration', 'ant-formatted-report-writer': 'office',
|
|
45
|
+
'ant-agent-browser': 'automation', 'ant-vercel-react-best-practices': 'development',
|
|
46
|
+
'ant-vercel-composition-patterns': 'development', 'ant-supabase-postgres-best-practices': 'data',
|
|
47
|
+
'ant-diagnosing-bugs': 'development', 'ant-improve-codebase-architecture': 'development',
|
|
48
|
+
'ant-code-review': 'development', 'ant-systematic-debugging': 'development',
|
|
49
|
+
'ant-test-driven-development': 'development', 'ant-verification-before-completion': 'development',
|
|
50
|
+
'ant-writing-plans': 'development', 'ant-to-spec': 'development', 'ant-skill-creator': 'automation',
|
|
51
|
+
'ant-frontend-design': 'design', 'ant-web-design-guidelines': 'design',
|
|
52
|
+
'ant-ui-ux-pro-max': 'design', 'ant-emil-design-eng': 'design',
|
|
53
|
+
'ant-review-animations': 'design', 'ant-shadcn': 'design',
|
|
54
|
+
'ant-theme-factory': 'design', 'ant-brand-guidelines': 'design',
|
|
55
|
+
'ant-copywriting': 'content', 'ant-seo-audit': 'content', 'ant-brainstorming': 'content',
|
|
49
56
|
}
|
|
57
|
+
const categoryOf = (entry) => extensionCategoryLabels[entry?.category]
|
|
58
|
+
? entry.category : legacyMarketSkillCategories[entry?.dsh_name] || 'other'
|
|
59
|
+
const categoryLabel = (category) => extensionCategoryLabels[category] || extensionCategoryLabels.other
|
|
50
60
|
const officialOfficeGuide = 'https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/packages/skill/skill-office/README.zh.md'
|
|
51
61
|
const officialOfficeEnglish = (skill) => `https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v${skill.since}/packages/skill/skill-office/assets/${skill.name}/SKILL.md`
|
|
52
62
|
const officialOfficeSkills = [
|
|
53
|
-
{ name: 'office', title: 'Office 办公入口', category: '
|
|
54
|
-
{ name: 'office-docx', title: 'Word 文档', category: '
|
|
55
|
-
{ name: 'office-pptx', title: 'PPT 演示', category: '
|
|
56
|
-
{ name: 'office-xlsx', title: 'Excel 表格', category: '
|
|
57
|
-
{ name: 'office-render', title: '文件预览', category: '
|
|
58
|
-
{ name: 'office-recalculate', title: '公式重算', category: '
|
|
59
|
-
{ name: 'office-export-pdf', title: '导出 PDF', category: '
|
|
63
|
+
{ name: 'office', title: 'Office 办公入口', category: 'office', since: '0.2.1-rc.2', description: '根据任务选择 Word、PPT、Excel 或文件处理能力。', prompt: '帮我完成这份办公文件' },
|
|
64
|
+
{ name: 'office-docx', title: 'Word 文档', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 DOCX 文档。', prompt: '帮我写一份 Word 报告' },
|
|
65
|
+
{ name: 'office-pptx', title: 'PPT 演示', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 PPTX 演示文稿。', prompt: '帮我做一份项目汇报 PPT' },
|
|
66
|
+
{ name: 'office-xlsx', title: 'Excel 表格', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 XLSX 工作簿。', prompt: '帮我整理这份 Excel 表格' },
|
|
67
|
+
{ name: 'office-render', title: '文件预览', category: 'office', since: '0.2.1-rc.2', description: '把 Office 文件渲染成图片,检查实际外观。', prompt: '请预览这份文档并检查排版' },
|
|
68
|
+
{ name: 'office-recalculate', title: '公式重算', category: 'office', since: '0.2.1-rc.2', description: '重新计算表格公式并核对结果。', prompt: '请重新计算这份表格的公式' },
|
|
69
|
+
{ name: 'office-export-pdf', title: '导出 PDF', category: 'office', since: '0.2.1-rc.2', description: '将 Office 文件导出为 PDF。', prompt: '把这份 Word 文档导出成 PDF' },
|
|
60
70
|
]
|
|
61
71
|
const curatedSkillIntroductions = {
|
|
62
72
|
"ant-ppt-master": {"ref": "c350bc31e6d43b7bcf5234183d650ba8f9f4c9d7", "description": "制作和编辑 PowerPoint 演示文稿。"},
|
|
@@ -115,7 +125,7 @@ window.__ModuleLoader__.load({
|
|
|
115
125
|
}
|
|
116
126
|
return null
|
|
117
127
|
}
|
|
118
|
-
const skillCategory = (skill) =>
|
|
128
|
+
const skillCategory = (skill) => categoryOf(skill)
|
|
119
129
|
const panelStyle = {
|
|
120
130
|
display: 'grid', alignContent: 'start', gap: 14, padding: 18,
|
|
121
131
|
border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 14,
|
|
@@ -128,6 +138,9 @@ window.__ModuleLoader__.load({
|
|
|
128
138
|
borderLeft: '3px solid var(--dsw-alias-state-business-primary)',
|
|
129
139
|
}
|
|
130
140
|
const mutedTextStyle = { margin: 0, color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6 }
|
|
141
|
+
const feedbackNoticeStyle = { ...mutedTextStyle, position: 'sticky', top: 0, zIndex: 8,
|
|
142
|
+
padding: '9px 12px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
143
|
+
backgroundColor: 'var(--dsw-alias-bg-base)', boxShadow: '0 4px 14px #173d3510', overflowWrap: 'anywhere' }
|
|
131
144
|
const sectionTitleStyle = { margin: 0, fontSize: 16, fontWeight: 650, lineHeight: 1.4 }
|
|
132
145
|
const inputStyle = {
|
|
133
146
|
width: '100%', boxSizing: 'border-box', padding: '10px 12px',
|
|
@@ -393,9 +406,8 @@ window.__ModuleLoader__.load({
|
|
|
393
406
|
binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
|
|
394
407
|
binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
|
|
395
408
|
binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
|
|
396
|
-
h('
|
|
397
|
-
|
|
398
|
-
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null))
|
|
409
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
410
|
+
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)
|
|
399
411
|
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
400
412
|
h('div', { style: { display: 'grid', gap: 4 } },
|
|
401
413
|
h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650, lineHeight: 1.35 } }, '团队与协作'),
|
|
@@ -1220,7 +1232,7 @@ window.__ModuleLoader__.load({
|
|
|
1220
1232
|
return h('section', { style: { ...settingsPanelStyle,
|
|
1221
1233
|
display: ['collaboration', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
1222
1234
|
'aria-label': '群聊协作设置' },
|
|
1223
|
-
message ? h('p', { role: 'status', style:
|
|
1235
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1224
1236
|
h('div', { id: 'ant-chat-entry', role: 'region', 'aria-label': '聊天入口和机器人状态', tabIndex: -1,
|
|
1225
1237
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1226
1238
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
@@ -1588,8 +1600,6 @@ window.__ModuleLoader__.load({
|
|
|
1588
1600
|
&& (scopeFilter === 'all' || scopeFilter === 'official')
|
|
1589
1601
|
&& (categoryFilter === 'all' || skill.category === categoryFilter)
|
|
1590
1602
|
})
|
|
1591
|
-
const categories = [...new Set([...skills.map(skillCategory), ...officialOfficeSkills.map((skill) => skill.category)])]
|
|
1592
|
-
.sort((a, b) => a.localeCompare(b, 'zh-CN'))
|
|
1593
1603
|
const statusLabel = { installed: '已放入本机目录', 'update-available': '可更新', 'not-installed': '未安装', conflict: '本机冲突' }
|
|
1594
1604
|
if (originalView) return h('section', { 'aria-label': '技能原文', style: {
|
|
1595
1605
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
@@ -1638,19 +1648,20 @@ window.__ModuleLoader__.load({
|
|
|
1638
1648
|
h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
|
|
1639
1649
|
style: { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
|
|
1640
1650
|
includePlugins ? null : h('span', { style: { ...mutedTextStyle, marginLeft: 'auto' } }, `显示 ${visible.length + visibleOfficial.length} / ${skills.length + officialOfficeSkills.length} 个技能`)),
|
|
1641
|
-
|
|
1651
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
|
|
1642
1652
|
h('span', { style: mutedTextStyle }, '来源'),
|
|
1643
1653
|
...[['all', '全部'], ['official', 'DSH 官方'], ['market', '全市场精选'], ['team', '本企业']].map(([value, label]) =>
|
|
1644
1654
|
h('button', { key: value, type: 'button', onClick: () => setScopeFilter(value), 'aria-pressed': scopeFilter === value,
|
|
1645
1655
|
style: { ...categoryButtonStyle, backgroundColor: scopeFilter === value ? 'var(--dsw-alias-bg-layer-2)' : categoryButtonStyle.backgroundColor } }, label)),
|
|
1646
|
-
h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '
|
|
1647
|
-
h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value),
|
|
1656
|
+
h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '用途类别',
|
|
1657
|
+
h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value),
|
|
1658
|
+
'aria-label': '筛选扩展用途类别', style: { ...inputStyle, width: 'auto', minHeight: 34 } },
|
|
1648
1659
|
h('option', { value: 'all' }, '全部用途'),
|
|
1649
|
-
...
|
|
1650
|
-
),
|
|
1660
|
+
...extensionCategories.map(([category, label]) => h('option', { key: category, value: category }, label)))),
|
|
1661
|
+
)),
|
|
1651
1662
|
includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, loading ? '技能 · 读取中…' : `技能 · ${visible.length + visibleOfficial.length}`) : null,
|
|
1652
1663
|
message && (!includePlugins || loadError || skills.length || (!loading && !query.trim()))
|
|
1653
|
-
? h('p', { role: loadError ? 'alert' : 'status', style:
|
|
1664
|
+
? h('p', { role: loadError ? 'alert' : 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1654
1665
|
visible.length && !includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, '全市场与本企业技能') : null,
|
|
1655
1666
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
1656
1667
|
...visible.map((skill) => {
|
|
@@ -1677,7 +1688,7 @@ window.__ModuleLoader__.load({
|
|
|
1677
1688
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
1678
1689
|
h('h2', { style: { margin: 0, fontSize: 19 } }, skill.title),
|
|
1679
1690
|
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, skill.scope === 'market' ? '全市场精选' : '本企业'),
|
|
1680
|
-
h('span', { style: mutedTextStyle }, skillCategory(skill))),
|
|
1691
|
+
h('span', { style: mutedTextStyle }, categoryLabel(skillCategory(skill)))),
|
|
1681
1692
|
h('span', { style: mutedTextStyle }, marketPending
|
|
1682
1693
|
? skill.team_connected ? '待企业启用' : '连接企业后查看启用状态'
|
|
1683
1694
|
: statusLabel[skill.local_status] || '状态未知')),
|
|
@@ -1742,20 +1753,24 @@ window.__ModuleLoader__.load({
|
|
|
1742
1753
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 } },
|
|
1743
1754
|
h('h3', { style: { margin: 0, fontSize: 17 } }, skill.title),
|
|
1744
1755
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 6,
|
|
1745
|
-
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, 'DSH 自带 · 无需在 ANT 安装')
|
|
1756
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, 'DSH 自带 · 无需在 ANT 安装'),
|
|
1757
|
+
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
1758
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(skill.category))),
|
|
1746
1759
|
h('p', { style: mutedTextStyle }, skill.description),
|
|
1747
1760
|
h('p', { style: mutedTextStyle }, `在新会话中说:“${skill.prompt}”。也可输入 /${skill.name}。`),
|
|
1748
1761
|
h('p', { style: mutedTextStyle }, `DSH ${skill.since} 起提供;如果无法使用,请在 DSH 检查更新。`),
|
|
1749
1762
|
h('a', { href: officialOfficeEnglish(skill), target: '_blank', rel: 'noopener noreferrer',
|
|
1750
1763
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13, lineHeight: 1.6, padding: '4px 0' } }, '英文原文 ↗'))))) : null,
|
|
1751
1764
|
includePlugins ? h(PluginCenterPage, { remote, onBack, onAll, onSkills, openBundle,
|
|
1752
|
-
embedded: true, query, onQueryChange, externalFilter: filter,
|
|
1765
|
+
embedded: true, query, onQueryChange, externalFilter: filter, externalCategoryFilter: categoryFilter,
|
|
1766
|
+
externalScopeFilter: scopeFilter,
|
|
1753
1767
|
hasSkillMatches: visible.length + visibleOfficial.length > 0, skillCatalogReady: !loading && !loadError,
|
|
1754
1768
|
refreshToken }) : null))
|
|
1755
1769
|
}
|
|
1756
1770
|
|
|
1757
1771
|
function PluginCenterPage({ remote, onBack, onAll, onSkills, openBundle,
|
|
1758
1772
|
embedded = false, query, onQueryChange, externalFilter = 'all',
|
|
1773
|
+
externalCategoryFilter = 'all', externalScopeFilter = 'all',
|
|
1759
1774
|
hasSkillMatches = false, skillCatalogReady = false, refreshToken }) {
|
|
1760
1775
|
const [plugins, setPlugins] = useState([])
|
|
1761
1776
|
const [bundles, setBundles] = useState([])
|
|
@@ -1766,6 +1781,7 @@ window.__ModuleLoader__.load({
|
|
|
1766
1781
|
const [completedId, setCompletedId] = useState('')
|
|
1767
1782
|
const [message, setMessage] = useState('')
|
|
1768
1783
|
const [filter, setFilter] = useState('all')
|
|
1784
|
+
const [categoryFilter, setCategoryFilter] = useState('all')
|
|
1769
1785
|
const refreshGeneration = useRef(0)
|
|
1770
1786
|
const refresh = async () => {
|
|
1771
1787
|
const generation = ++refreshGeneration.current
|
|
@@ -1889,21 +1905,46 @@ window.__ModuleLoader__.load({
|
|
|
1889
1905
|
} finally { setWorkingId('') }
|
|
1890
1906
|
}
|
|
1891
1907
|
const activeFilter = embedded ? externalFilter : filter
|
|
1908
|
+
const activeCategoryFilter = embedded ? externalCategoryFilter : categoryFilter
|
|
1909
|
+
const activeScopeFilter = embedded ? externalScopeFilter : 'team'
|
|
1910
|
+
const currentPluginByPackage = new Map()
|
|
1911
|
+
const publishedPluginPackages = new Set()
|
|
1912
|
+
for (const plugin of plugins) {
|
|
1913
|
+
if (!currentPluginByPackage.has(plugin.package_name)
|
|
1914
|
+
|| plugin.status === 'published' && !publishedPluginPackages.has(plugin.package_name)) {
|
|
1915
|
+
currentPluginByPackage.set(plugin.package_name, plugin.id)
|
|
1916
|
+
}
|
|
1917
|
+
if (plugin.status === 'published') publishedPluginPackages.add(plugin.package_name)
|
|
1918
|
+
}
|
|
1919
|
+
if (activeFilter === 'installed') {
|
|
1920
|
+
for (const plugin of plugins) {
|
|
1921
|
+
if (publishedPluginPackages.has(plugin.package_name)) continue
|
|
1922
|
+
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1923
|
+
if (!installed?.version || installed.version !== plugin.version) continue
|
|
1924
|
+
currentPluginByPackage.set(plugin.package_name, plugin.id)
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1892
1927
|
const visible = plugins.filter((plugin) => {
|
|
1928
|
+
const isCurrentVersion = currentPluginByPackage.get(plugin.package_name) === plugin.id
|
|
1893
1929
|
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1894
1930
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
1895
1931
|
const installSpec = pluginInstallSpec(plugin)
|
|
1896
1932
|
const localAhead = installed?.version && newerVersion(installed.version, plugin.version)
|
|
1897
1933
|
const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
|
|
1898
|
-
return matches && (
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1934
|
+
return matches && (activeScopeFilter === 'all' || activeScopeFilter === 'team')
|
|
1935
|
+
&& (activeCategoryFilter === 'all' || categoryOf(plugin) === activeCategoryFilter)
|
|
1936
|
+
&& (activeFilter === 'retired'
|
|
1937
|
+
? plugin.status !== 'published'
|
|
1938
|
+
: isCurrentVersion && (activeFilter === 'all'
|
|
1939
|
+
|| activeFilter === 'installed' && Boolean(installed)
|
|
1940
|
+
|| activeFilter === 'available' && plugin.status === 'published' && installSpec && !localAhead && (!installed || (installed.version && installed.version !== plugin.version))
|
|
1941
|
+
|| activeFilter === 'updates' && plugin.status === 'published' && installSpec && installed && installed.version && installed.version !== plugin.version && !localAhead)
|
|
1942
|
+
)
|
|
1902
1943
|
})
|
|
1903
1944
|
const noMatches = embedded
|
|
1904
1945
|
? skillCatalogReady ? '没有找到符合条件的企业扩展。' : '插件中没有符合条件的结果。'
|
|
1905
1946
|
: activeFilter === 'available'
|
|
1906
|
-
? '
|
|
1947
|
+
? '当前没有可一键安装的插件。可在“目录概览”查看审核目录,或联系企业管理员。'
|
|
1907
1948
|
: '没有符合筛选条件的插件。'
|
|
1908
1949
|
return h('section', { 'aria-label': '企业精选插件', style: embedded
|
|
1909
1950
|
? { display: 'grid', gap: 16 }
|
|
@@ -1926,15 +1967,20 @@ window.__ModuleLoader__.load({
|
|
|
1926
1967
|
: h('section', { role: 'status', style: panelStyle },
|
|
1927
1968
|
h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
|
|
1928
1969
|
h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
|
|
1929
|
-
!loading && loadError ? h('p', { role: 'alert', style:
|
|
1970
|
+
!loading && loadError ? h('p', { role: 'alert', style: feedbackNoticeStyle }, message) : null,
|
|
1930
1971
|
plugins.length && !embedded ? h(ExtensionRequestNotice) : null,
|
|
1931
1972
|
plugins.length && !embedded ? h('div', { style: panelStyle },
|
|
1932
1973
|
h('input', { type: 'search', value: query, onChange: (event) => onQueryChange(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
|
|
1933
1974
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
1934
|
-
...[['all', '
|
|
1975
|
+
...[['all', '目录概览'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
|
|
1935
1976
|
h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
|
|
1936
|
-
style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label))
|
|
1937
|
-
|
|
1977
|
+
style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
|
|
1978
|
+
h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '用途类别',
|
|
1979
|
+
h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value),
|
|
1980
|
+
'aria-label': '筛选插件用途类别', style: { ...inputStyle, width: 'auto', minHeight: 34 } },
|
|
1981
|
+
h('option', { value: 'all' }, '全部用途'),
|
|
1982
|
+
...extensionCategories.map(([category, label]) => h('option', { key: category, value: category }, label)))))) : null,
|
|
1983
|
+
plugins.length && message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1938
1984
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
1939
1985
|
...visible.map((plugin) => {
|
|
1940
1986
|
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
@@ -1947,7 +1993,7 @@ window.__ModuleLoader__.load({
|
|
|
1947
1993
|
const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
|
|
1948
1994
|
const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
|
|
1949
1995
|
const status = security ? '已废弃(安全风险)'
|
|
1950
|
-
: plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' : localAhead
|
|
1996
|
+
: plugin.status === 'deprecated' ? local?.version ? `已废弃 · 本机 ${local.version}` : '已废弃' : local?.error ? '加载异常' : localAhead
|
|
1951
1997
|
? '本机版本高于团队目录版本' : sameVersion
|
|
1952
1998
|
? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
|
|
1953
1999
|
: sourceKnown ? '本机同版,来源不同' : recordedInstall
|
|
@@ -1959,7 +2005,9 @@ window.__ModuleLoader__.load({
|
|
|
1959
2005
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
1960
2006
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
1961
2007
|
h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
|
|
1962
|
-
embedded ? h('span', { style: mutedTextStyle }, '插件') : null
|
|
2008
|
+
embedded ? h('span', { style: mutedTextStyle }, '插件') : null,
|
|
2009
|
+
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
2010
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(categoryOf(plugin)))),
|
|
1963
2011
|
h('span', { style: security
|
|
1964
2012
|
? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)', fontWeight: 650 } : mutedTextStyle }, status)),
|
|
1965
2013
|
h('p', { style: mutedTextStyle }, plugin.description),
|
|
@@ -2010,6 +2058,9 @@ window.__ModuleLoader__.load({
|
|
|
2010
2058
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
2011
2059
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
2012
2060
|
const [authorizedModels, setAuthorizedModels] = useState([])
|
|
2061
|
+
const [providerSyncGroups, setProviderSyncGroups] = useState([])
|
|
2062
|
+
const [appliedProviderSyncExclusions, setAppliedProviderSyncExclusions] = useState([])
|
|
2063
|
+
const [providerSyncDraft, setProviderSyncDraft] = useState([])
|
|
2013
2064
|
const [serverDefaultModel, setServerDefaultModel] = useState(null)
|
|
2014
2065
|
const [dshDefaultSelection, setDshDefaultSelection] = useState(null)
|
|
2015
2066
|
const [defaultModelDraft, setDefaultModelDraft] = useState('')
|
|
@@ -2095,17 +2146,27 @@ window.__ModuleLoader__.load({
|
|
|
2095
2146
|
: `ant-team-openai-${model.provider_id}`
|
|
2096
2147
|
const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
|
|
2097
2148
|
const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2149
|
+
const providerSyncPreferenceKey = (rawServiceUrl) => `${PROVIDER_SYNC_EXCLUSIONS_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2098
2150
|
const readDefaultPreference = (rawServiceUrl) => {
|
|
2099
2151
|
try { return JSON.parse(localStorage.getItem(defaultPreferenceKey(rawServiceUrl)) || 'null') } catch { return null }
|
|
2100
2152
|
}
|
|
2101
2153
|
const saveDefaultPreference = (rawServiceUrl, value) => {
|
|
2102
2154
|
try { localStorage.setItem(defaultPreferenceKey(rawServiceUrl), JSON.stringify(value)) } catch {}
|
|
2103
2155
|
}
|
|
2104
|
-
const modelForProviderSelection = (selection, models) => {
|
|
2156
|
+
const modelForProviderSelection = (selection, models, preferredModel = null) => {
|
|
2105
2157
|
if (!selection) return null
|
|
2106
2158
|
const exact = models.find((model) => model.model_id === selection.model && modelProvider(model) === selection.provider)
|
|
2107
2159
|
if (exact) return exact
|
|
2108
|
-
|
|
2160
|
+
if (['ant-team-openai', 'ant-team-openai-chat-completions'].includes(selection.provider)) {
|
|
2161
|
+
const chatCandidates = models.filter((model) => model.protocol === 'openai'
|
|
2162
|
+
&& model.openai_api_mode !== 'responses' && model.model_id === selection.model)
|
|
2163
|
+
const preferredChat = preferredModel && chatCandidates.find((model) =>
|
|
2164
|
+
modelSelectionKey(model) === modelSelectionKey(preferredModel))
|
|
2165
|
+
if (preferredChat) return preferredChat
|
|
2166
|
+
const canonicalChat = chatCandidates.find((model) => model.provider_id === 'openai-default')
|
|
2167
|
+
return canonicalChat || (chatCandidates.length === 1 ? chatCandidates[0] : null)
|
|
2168
|
+
}
|
|
2169
|
+
const legacyProvider = selection.provider === 'ant-team-openai-responses'
|
|
2109
2170
|
? 'openai-default' : selection.provider === 'ant-team-anthropic' ? 'anthropic-default' : null
|
|
2110
2171
|
return legacyProvider ? models.find((model) => model.provider_id === legacyProvider && model.model_id === selection.model) : null
|
|
2111
2172
|
}
|
|
@@ -2131,8 +2192,8 @@ window.__ModuleLoader__.load({
|
|
|
2131
2192
|
return selection
|
|
2132
2193
|
}
|
|
2133
2194
|
const applyUserDefaultModel = async (model) => {
|
|
2134
|
-
if (!model || !
|
|
2135
|
-
setDefaultModelMessage('
|
|
2195
|
+
if (!model || !syncedModels.some((item) => modelSelectionKey(item) === modelSelectionKey(model))) {
|
|
2196
|
+
setDefaultModelMessage('请选择当前已同步到 DSH 的模型。')
|
|
2136
2197
|
return
|
|
2137
2198
|
}
|
|
2138
2199
|
setDefaultModelBusy(true)
|
|
@@ -2149,7 +2210,7 @@ window.__ModuleLoader__.load({
|
|
|
2149
2210
|
setDefaultModelMessage(error instanceof Error ? error.message : '保存本机默认模型失败。')
|
|
2150
2211
|
} finally { setDefaultModelBusy(false) }
|
|
2151
2212
|
}
|
|
2152
|
-
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null) => {
|
|
2213
|
+
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null, options = {}) => {
|
|
2153
2214
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
2154
2215
|
if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
|
|
2155
2216
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 版本未提供模型设置接口。')
|
|
@@ -2182,18 +2243,9 @@ window.__ModuleLoader__.load({
|
|
|
2182
2243
|
? provider.apiMode === 'responses' ? 'ant-team-openai-responses' : 'ant-team-openai-chat-completions'
|
|
2183
2244
|
: provider.providerId === 'anthropic-default'
|
|
2184
2245
|
? 'ant-team-anthropic' : `ant-team-${provider.protocol}-${provider.providerId}`
|
|
2185
|
-
const
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
try {
|
|
2189
|
-
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2190
|
-
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2191
|
-
} catch {}
|
|
2192
|
-
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2193
|
-
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2194
|
-
for (const route of knownRoutes) {
|
|
2195
|
-
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2196
|
-
}
|
|
2246
|
+
const routeConfigurations = new Map()
|
|
2247
|
+
const compatibilityRoutes = []
|
|
2248
|
+
const providerGroups = []
|
|
2197
2249
|
for (const provider of providers) {
|
|
2198
2250
|
const route = routeForProvider(provider)
|
|
2199
2251
|
const api = provider.protocol === 'openai'
|
|
@@ -2204,22 +2256,99 @@ window.__ModuleLoader__.load({
|
|
|
2204
2256
|
: provider.protocol === 'openai'
|
|
2205
2257
|
? new URL(`/model-gateway/openai/providers/${encodeURIComponent(provider.providerId)}/v1`, normalized).toString()
|
|
2206
2258
|
: new URL(`/model-gateway/anthropic/providers/${encodeURIComponent(provider.providerId)}`, normalized).toString()
|
|
2207
|
-
|
|
2259
|
+
routeConfigurations.set(route, {
|
|
2208
2260
|
displayName: provider.providerName,
|
|
2209
2261
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
2210
2262
|
api,
|
|
2211
2263
|
baseURL,
|
|
2212
2264
|
models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
2213
|
-
}
|
|
2265
|
+
})
|
|
2266
|
+
providerGroups.push({
|
|
2267
|
+
key: route,
|
|
2268
|
+
label: `${provider.providerName} · ${provider.protocol === 'anthropic' ? 'Anthropic Messages' : provider.apiMode === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'}`,
|
|
2269
|
+
routes: [route],
|
|
2270
|
+
modelCount: provider.models.length,
|
|
2271
|
+
legacy: false,
|
|
2272
|
+
})
|
|
2273
|
+
if (provider.protocol === 'openai' && provider.providerId === 'openai-default') {
|
|
2274
|
+
const aliases = provider.apiMode === 'responses'
|
|
2275
|
+
? ['ant-team-openai-chat-completions', 'ant-team-openai'] : ['ant-team-openai']
|
|
2276
|
+
compatibilityRoutes.push(...aliases)
|
|
2277
|
+
const legacyChat = {
|
|
2278
|
+
displayName: `${provider.providerName} · Chat(旧会话兼容)`,
|
|
2279
|
+
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
2280
|
+
api: 'openai-completions',
|
|
2281
|
+
baseURL: new URL('/model-gateway/openai/v1', normalized).toString(),
|
|
2282
|
+
models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
2283
|
+
}
|
|
2284
|
+
for (const alias of aliases) routeConfigurations.set(alias, legacyChat)
|
|
2285
|
+
}
|
|
2214
2286
|
}
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
try {
|
|
2287
|
+
const ops = []
|
|
2288
|
+
let previousRoutes = []
|
|
2289
|
+
try {
|
|
2290
|
+
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2291
|
+
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2292
|
+
} catch {}
|
|
2293
|
+
const compatibilityGroup = compatibilityRoutes.length ? [{
|
|
2294
|
+
key: 'legacy-openai-chat', label: 'ANT OpenAI · Chat(旧会话兼容)', routes: compatibilityRoutes,
|
|
2295
|
+
modelCount: providers.find((provider) => provider.providerId === 'openai-default')?.models.length || 0,
|
|
2296
|
+
legacy: true,
|
|
2297
|
+
}] : []
|
|
2298
|
+
const allGroups = [...providerGroups, ...compatibilityGroup]
|
|
2299
|
+
let excludedGroups = options.excludedGroups
|
|
2300
|
+
if (!Array.isArray(excludedGroups)) {
|
|
2301
|
+
try {
|
|
2302
|
+
const saved = JSON.parse(localStorage.getItem(providerSyncPreferenceKey(normalized)) || 'null')
|
|
2303
|
+
if (Array.isArray(saved)) excludedGroups = saved
|
|
2304
|
+
} catch {}
|
|
2305
|
+
}
|
|
2306
|
+
if (!Array.isArray(excludedGroups)) {
|
|
2307
|
+
// Existing installs keep their current legacy alias group; first-time installs get all routes.
|
|
2308
|
+
excludedGroups = previousRoutes.length && compatibilityRoutes.length
|
|
2309
|
+
&& !compatibilityRoutes.some((route) => previousRoutes.includes(route)) ? ['legacy-openai-chat'] : []
|
|
2310
|
+
}
|
|
2311
|
+
excludedGroups = [...new Set(excludedGroups.filter((key) => allGroups.some((group) => group.key === key)))]
|
|
2312
|
+
const excludedGroupSet = new Set(excludedGroups)
|
|
2313
|
+
const excluded = new Set(allGroups.filter((group) => excludedGroupSet.has(group.key)).flatMap((group) => group.routes))
|
|
2314
|
+
const routes = [...routeConfigurations.keys()].filter((route) => !excluded.has(route))
|
|
2315
|
+
const syncedModels = models.filter((model) => !excluded.has(modelProvider(model)))
|
|
2316
|
+
if (models.length && !syncedModels.length) throw new Error('请至少保留一个常规模型 provider 同步到本机 DSH。')
|
|
2218
2317
|
const defaultMatch = rawDefaultModel && models.find((model) =>
|
|
2219
2318
|
(rawDefaultModel.provider_id ? model.provider_id === rawDefaultModel.provider_id : model.protocol === rawDefaultModel.protocol)
|
|
2220
2319
|
&& model.protocol === rawDefaultModel.protocol && model.model_id === rawDefaultModel.model_id)
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2320
|
+
const syncedDefaultMatch = defaultMatch && syncedModels.some((model) => modelSelectionKey(model) === modelSelectionKey(defaultMatch))
|
|
2321
|
+
? defaultMatch : null
|
|
2322
|
+
const serverDefault = syncedDefaultMatch || null
|
|
2323
|
+
const initialDefault = syncedDefaultMatch || (rawDefaultModel == null ? syncedModels[0] : null)
|
|
2324
|
+
if (Array.isArray(options.excludedGroups)) {
|
|
2325
|
+
const intentionallyRemovedRoutes = [...excluded].filter((route) => previousRoutes.includes(route))
|
|
2326
|
+
if (intentionallyRemovedRoutes.length && !window.confirm(
|
|
2327
|
+
'关闭这些 provider 会从本机 DSH 移除对应模型路由。保存了这些路由的历史会话可能暂时无法继续;重新开启同步可以恢复。仍要应用吗?')) {
|
|
2328
|
+
throw new Error('已取消同步范围更改;本机 DSH 设置未修改。')
|
|
2329
|
+
}
|
|
2330
|
+
const currentSelection = await readDshDefaultSelection()
|
|
2331
|
+
if (currentSelection?.provider && excluded.has(currentSelection.provider)) {
|
|
2332
|
+
const replacement = modelForProviderSelection(currentSelection, syncedModels, serverDefault) || serverDefault || syncedModels[0]
|
|
2333
|
+
if (!replacement) throw new Error('请先选择一个保留同步的模型,再移除当前 DSH 默认 provider。')
|
|
2334
|
+
await writeDshDefaultModel(replacement)
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2338
|
+
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2339
|
+
for (const route of knownRoutes) {
|
|
2340
|
+
if (!routes.includes(route)) ops.push({ op: 'unset', path: ['providers', route] })
|
|
2341
|
+
}
|
|
2342
|
+
for (const [route, value] of routeConfigurations) {
|
|
2343
|
+
ops.push({ op: 'set', path: ['providers', route], value })
|
|
2344
|
+
}
|
|
2345
|
+
const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
2346
|
+
if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
|
|
2347
|
+
try { localStorage.setItem(PROVIDER_ROUTES_STORAGE_KEY, JSON.stringify(routes)) } catch {}
|
|
2348
|
+
try { localStorage.setItem(providerSyncPreferenceKey(normalized), JSON.stringify(excludedGroups)) } catch {}
|
|
2349
|
+
setProviderSyncGroups(allGroups)
|
|
2350
|
+
setAppliedProviderSyncExclusions(excludedGroups)
|
|
2351
|
+
setProviderSyncDraft(excludedGroups)
|
|
2223
2352
|
setServerDefaultModel(serverDefault)
|
|
2224
2353
|
const preference = readDefaultPreference(normalized)
|
|
2225
2354
|
let statusMessage = ''
|
|
@@ -2233,8 +2362,8 @@ window.__ModuleLoader__.load({
|
|
|
2233
2362
|
: '当前无法使用团队默认模型;已用首个获准模型初始化 DSH 全局默认。请管理员检查默认设置和成员授权。'
|
|
2234
2363
|
} else {
|
|
2235
2364
|
selection = await readDshDefaultSelection()
|
|
2236
|
-
const currentAuthorized = modelForProviderSelection(selection,
|
|
2237
|
-
const baseline =
|
|
2365
|
+
const currentAuthorized = modelForProviderSelection(selection, syncedModels, serverDefault)
|
|
2366
|
+
const baseline = syncedModels.find((model) => modelSelectionKey(model) === preference?.serverDefault)
|
|
2238
2367
|
const stillUsingBaseline = baseline && currentAuthorized && modelSelectionKey(currentAuthorized) === modelSelectionKey(baseline)
|
|
2239
2368
|
if (currentAuthorized && selection?.provider !== modelProvider(currentAuthorized)) {
|
|
2240
2369
|
selection = await writeDshDefaultModel(currentAuthorized)
|
|
@@ -2243,8 +2372,8 @@ window.__ModuleLoader__.load({
|
|
|
2243
2372
|
selection = await writeDshDefaultModel(serverDefault)
|
|
2244
2373
|
saveDefaultPreference(normalized, { ...preference, initialized: true, serverDefault: modelSelectionKey(serverDefault) })
|
|
2245
2374
|
statusMessage = '团队默认模型已更新;你尚未设置个人覆盖,因此已同步到 DSH 全局默认。'
|
|
2246
|
-
} else if (selection?.provider?.startsWith('ant-team-') && !currentAuthorized &&
|
|
2247
|
-
const fallback = serverDefault ||
|
|
2375
|
+
} else if (selection?.provider?.startsWith('ant-team-') && !currentAuthorized && syncedModels.length) {
|
|
2376
|
+
const fallback = serverDefault || syncedModels[0]
|
|
2248
2377
|
selection = await writeDshDefaultModel(fallback)
|
|
2249
2378
|
saveDefaultPreference(normalized, { ...preference, initialized: true,
|
|
2250
2379
|
serverDefault: modelSelectionKey(serverDefault || fallback) })
|
|
@@ -2252,11 +2381,11 @@ window.__ModuleLoader__.load({
|
|
|
2252
2381
|
? '当前默认模型已失去授权,已恢复为团队默认模型。'
|
|
2253
2382
|
: '当前默认模型已失去授权,已改用首个获准模型;请管理员设置团队默认模型。'
|
|
2254
2383
|
}
|
|
2255
|
-
const effectiveAuthorized = modelForProviderSelection(selection,
|
|
2384
|
+
const effectiveAuthorized = modelForProviderSelection(selection, syncedModels)
|
|
2256
2385
|
setDshDefaultSelection(selection)
|
|
2257
2386
|
setDefaultModelDraft(effectiveAuthorized ? modelSelectionKey(effectiveAuthorized) : '')
|
|
2258
2387
|
if (!serverDefault && !statusMessage) statusMessage = rawDefaultModel
|
|
2259
|
-
? '
|
|
2388
|
+
? '团队默认模型当前未同步到这台 DSH;请在下方选择一个已同步模型作为本机默认。'
|
|
2260
2389
|
: '团队默认模型尚未设置;请联系管理员设置,或在下方选择本机可用的默认模型。'
|
|
2261
2390
|
else if (!statusMessage) statusMessage = effectiveAuthorized
|
|
2262
2391
|
? '已保留你选择的 DSH 全局默认模型。'
|
|
@@ -2265,10 +2394,10 @@ window.__ModuleLoader__.load({
|
|
|
2265
2394
|
setDefaultModelMessage(statusMessage)
|
|
2266
2395
|
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized) } catch {}
|
|
2267
2396
|
onServiceUrlChange(normalized)
|
|
2268
|
-
return { count:
|
|
2397
|
+
return { count: syncedModels.length, defaultSelection: selection }
|
|
2269
2398
|
}
|
|
2270
2399
|
|
|
2271
|
-
const refreshModels = async () => {
|
|
2400
|
+
const refreshModels = async (applyDraft = false) => {
|
|
2272
2401
|
if (!bindingReady) {
|
|
2273
2402
|
setMessage('请先等待团队绑定检查完成,再刷新团队模型。')
|
|
2274
2403
|
return
|
|
@@ -2285,13 +2414,14 @@ window.__ModuleLoader__.load({
|
|
|
2285
2414
|
if (response.status === 401) setNeedsReconnect(true)
|
|
2286
2415
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
2287
2416
|
}
|
|
2288
|
-
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model
|
|
2417
|
+
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model,
|
|
2418
|
+
applyDraft ? { excludedGroups: providerSyncDraft } : {})
|
|
2289
2419
|
setAuthorizedModels(result.models)
|
|
2290
2420
|
setLastModelSync(new Date())
|
|
2291
2421
|
setCredentialConfigured(true)
|
|
2292
2422
|
setNeedsReconnect(false)
|
|
2293
2423
|
setReauthenticate(false)
|
|
2294
|
-
setModelMessage(
|
|
2424
|
+
setModelMessage(`模型目录已刷新,共同步 ${sync.count} 个获准模型。`)
|
|
2295
2425
|
} catch (error) {
|
|
2296
2426
|
setModelMessage(error instanceof Error ? error.message : '刷新模型失败,请稍后重试。')
|
|
2297
2427
|
} finally {
|
|
@@ -2493,6 +2623,7 @@ window.__ModuleLoader__.load({
|
|
|
2493
2623
|
if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
|
|
2494
2624
|
setCredentialConfigured(false)
|
|
2495
2625
|
setAuthorizedModels([]); setServerDefaultModel(null); setDshDefaultSelection(null); setDefaultModelDraft('')
|
|
2626
|
+
setProviderSyncGroups([]); setAppliedProviderSyncExclusions([]); setProviderSyncDraft([])
|
|
2496
2627
|
try { localStorage.removeItem(`${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(serviceUrl)}:${savedDeviceId() || ''}`) } catch {}
|
|
2497
2628
|
onConnectionChange?.(null)
|
|
2498
2629
|
setNeedsReconnect(false)
|
|
@@ -2704,11 +2835,35 @@ window.__ModuleLoader__.load({
|
|
|
2704
2835
|
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
2705
2836
|
const updateGuideUrl = pinnedServiceUrl
|
|
2706
2837
|
? new URL('/ant-update', pinnedServiceUrl).href : null
|
|
2707
|
-
const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels)
|
|
2838
|
+
const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels, serverDefaultModel)
|
|
2839
|
+
const excludedGroupSet = new Set(providerSyncDraft)
|
|
2840
|
+
const excludedRouteSet = new Set(providerSyncGroups.filter((group) => excludedGroupSet.has(group.key))
|
|
2841
|
+
.flatMap((group) => group.routes))
|
|
2842
|
+
const appliedExcludedGroupSet = new Set(appliedProviderSyncExclusions)
|
|
2843
|
+
const appliedExcludedRouteSet = new Set(providerSyncGroups.filter((group) => appliedExcludedGroupSet.has(group.key))
|
|
2844
|
+
.flatMap((group) => group.routes))
|
|
2845
|
+
const syncedModels = authorizedModels.filter((model) => !appliedExcludedRouteSet.has(modelProvider(model)))
|
|
2846
|
+
const hasPendingSyncChanges = [...new Set([...providerSyncDraft, ...appliedProviderSyncExclusions])]
|
|
2847
|
+
.some((key) => excludedGroupSet.has(key) !== appliedExcludedGroupSet.has(key))
|
|
2848
|
+
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !group.legacy && !excludedGroupSet.has(group.key)).length
|
|
2849
|
+
const pendingServerDefaultRemoval = hasPendingSyncChanges && serverDefaultModel
|
|
2850
|
+
? providerSyncGroups.some((group) => group.routes.includes(modelProvider(serverDefaultModel))
|
|
2851
|
+
&& !appliedExcludedGroupSet.has(group.key) && excludedGroupSet.has(group.key))
|
|
2852
|
+
: false
|
|
2853
|
+
const selectedDefaultDraft = syncedModels.find((model) => modelSelectionKey(model) === defaultModelDraft)
|
|
2854
|
+
const toggleProviderSyncGroup = (group, checked) => {
|
|
2855
|
+
setProviderSyncDraft((current) => {
|
|
2856
|
+
const next = new Set(current)
|
|
2857
|
+
if (checked) next.delete(group.key)
|
|
2858
|
+
else next.add(group.key)
|
|
2859
|
+
return [...next]
|
|
2860
|
+
})
|
|
2861
|
+
}
|
|
2708
2862
|
const modelRows = authorizedModels.map((model) => {
|
|
2709
2863
|
const providerName = model.protocol === 'openai'
|
|
2710
2864
|
? compactOpenaiProviderName(model.provider_name)
|
|
2711
2865
|
: model.provider_name
|
|
2866
|
+
const isSynced = !excludedRouteSet.has(modelProvider(model))
|
|
2712
2867
|
return h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
2713
2868
|
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
|
|
2714
2869
|
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
@@ -2716,7 +2871,9 @@ window.__ModuleLoader__.load({
|
|
|
2716
2871
|
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
2717
2872
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
2718
2873
|
model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
|
|
2719
|
-
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } },
|
|
2874
|
+
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, hasPendingSyncChanges
|
|
2875
|
+
? isSynced ? '刷新后同步到 DSH' : '刷新后从 DSH 移除'
|
|
2876
|
+
: isSynced ? '已同步到 DSH' : '未同步到 DSH'))
|
|
2720
2877
|
})
|
|
2721
2878
|
const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
2722
2879
|
h('div', null,
|
|
@@ -2768,7 +2925,7 @@ window.__ModuleLoader__.load({
|
|
|
2768
2925
|
setMessage('设备编号已复制,请交给原团队管理员。')
|
|
2769
2926
|
} catch { setMessage(savedDeviceId() ? `请复制设备编号:${savedDeviceId()}` : '本机无法确认旧设备编号,请联系原团队管理员核查。') }
|
|
2770
2927
|
}, style: secondaryButtonStyle }, '复制设备编号'))) : null,
|
|
2771
|
-
message ? h('p', { role: 'status', style:
|
|
2928
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null)
|
|
2772
2929
|
|
|
2773
2930
|
const modelPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'models' ? 'grid' : 'none' } },
|
|
2774
2931
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
@@ -2790,10 +2947,10 @@ window.__ModuleLoader__.load({
|
|
|
2790
2947
|
h('p', { style: mutedTextStyle }, '首次同步时,ANT 会用团队默认初始化 DSH 全局默认模型。它会影响该 Profile 的新会话以及 ANT 对话和任务;之后刷新保留你的个人覆盖。'),
|
|
2791
2948
|
h('label', { style: { display: 'grid', gap: 5, maxWidth: 720 } }, 'DSH 全局默认模型',
|
|
2792
2949
|
h('select', { 'aria-label': 'DSH 全局默认模型', value: defaultModelDraft,
|
|
2793
|
-
disabled: credentialConfigured !== true || !
|
|
2950
|
+
disabled: credentialConfigured !== true || !syncedModels.length || defaultModelBusy || busy || hasPendingSyncChanges,
|
|
2794
2951
|
onChange: (event) => setDefaultModelDraft(event.target.value), style: inputStyle },
|
|
2795
2952
|
h('option', { value: '' }, '选择获准模型'),
|
|
2796
|
-
...
|
|
2953
|
+
...syncedModels.map((model) => {
|
|
2797
2954
|
const providerName = model.protocol === 'openai'
|
|
2798
2955
|
? compactOpenaiProviderName(model.provider_name)
|
|
2799
2956
|
: model.provider_name
|
|
@@ -2802,14 +2959,28 @@ window.__ModuleLoader__.load({
|
|
|
2802
2959
|
`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`)
|
|
2803
2960
|
}))),
|
|
2804
2961
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
2805
|
-
h('button', { type: 'button', disabled: defaultModelBusy || !
|
|
2806
|
-
onClick: () =>
|
|
2962
|
+
h('button', { type: 'button', disabled: defaultModelBusy || !selectedDefaultDraft || hasPendingSyncChanges,
|
|
2963
|
+
onClick: () => void applyUserDefaultModel(selectedDefaultDraft),
|
|
2807
2964
|
style: primaryButtonStyle }, defaultModelBusy ? '保存中…' : '保存为 DSH 全局默认'),
|
|
2808
|
-
h('button', { type: 'button', disabled: defaultModelBusy || !serverDefaultModel
|
|
2965
|
+
h('button', { type: 'button', disabled: busy || defaultModelBusy || pendingServerDefaultRemoval || !serverDefaultModel
|
|
2809
2966
|
|| modelSelectionKey(currentDshDefaultModel || {}) === modelSelectionKey(serverDefaultModel),
|
|
2810
2967
|
onClick: () => void applyUserDefaultModel(serverDefaultModel), style: secondaryButtonStyle }, '恢复团队默认'),
|
|
2811
2968
|
currentDshDefaultModel ? h('span', { style: mutedTextStyle }, `当前 DSH 默认:${currentDshDefaultModel.display_name || currentDshDefaultModel.model_id}`) : null),
|
|
2969
|
+
pendingServerDefaultRemoval ? h('p', { role: 'status', style: mutedTextStyle },
|
|
2970
|
+
'团队默认 provider 正准备移除;先刷新应用同步范围,或重新勾选该 provider。') : null,
|
|
2812
2971
|
defaultModelMessage ? h('p', { role: 'status', style: mutedTextStyle }, defaultModelMessage) : null),
|
|
2972
|
+
providerSyncGroups.length ? h('section', { style: { ...infoNoticeStyle, gap: 8 } },
|
|
2973
|
+
h('strong', null, '同步到本机 DSH 的 provider'),
|
|
2974
|
+
h('p', { style: mutedTextStyle }, '首次升级会保留当前已同步的旧会话兼容路由。取消勾选并刷新后,该 provider 会从本机 DSH 移除;使用它的历史会话可能暂时无法继续,重新勾选并刷新可恢复。'),
|
|
2975
|
+
h('p', { style: mutedTextStyle }, '至少保留一个常规模型 provider;旧会话兼容组只供已有会话使用。'),
|
|
2976
|
+
hasPendingSyncChanges ? h('p', { role: 'status', style: mutedTextStyle }, '同步范围尚未应用;点击“刷新团队模型”后生效。') : null,
|
|
2977
|
+
...providerSyncGroups.map((group) => h('label', { key: group.key,
|
|
2978
|
+
style: { display: 'flex', alignItems: 'center', gap: 9, padding: '5px 0', cursor: busy ? 'default' : 'pointer' } },
|
|
2979
|
+
h('input', { type: 'checkbox', 'aria-label': group.label,
|
|
2980
|
+
checked: !excludedGroupSet.has(group.key), disabled: busy
|
|
2981
|
+
|| (!group.legacy && !excludedGroupSet.has(group.key) && activePrimaryGroupCount <= 1),
|
|
2982
|
+
onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
|
|
2983
|
+
h('span', null, `${group.label} · ${group.modelCount} 个模型`)))) : null,
|
|
2813
2984
|
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2814
2985
|
modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
|
|
2815
2986
|
h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
|
|
@@ -2819,7 +2990,7 @@ window.__ModuleLoader__.load({
|
|
|
2819
2990
|
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
2820
2991
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
2821
2992
|
h('button', { type: 'button', disabled: busy || defaultModelBusy || !bindingReady || credentialConfigured !== true,
|
|
2822
|
-
onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2993
|
+
onClick: () => void refreshModels(true), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2823
2994
|
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString('zh-CN')}`) : null),
|
|
2824
2995
|
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
2825
2996
|
|
|
@@ -2853,12 +3024,12 @@ window.__ModuleLoader__.load({
|
|
|
2853
3024
|
h('strong', { style: { fontSize: 14, lineHeight: 1.4 } }, '本版更新'),
|
|
2854
3025
|
h('p', { style: mutedTextStyle }, bundleRelease.release_notes)) : null,
|
|
2855
3026
|
['incompatible', 'unknown'].includes(serviceCompatibility.state) ? h('p', { role: 'status', style: {
|
|
2856
|
-
...
|
|
3027
|
+
...feedbackNoticeStyle,
|
|
2857
3028
|
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)'
|
|
2858
3029
|
: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))',
|
|
2859
3030
|
} }, `服务端兼容性:${serviceCompatibility.message}`) : null,
|
|
2860
3031
|
bundleUpdate.message && updateStatusNeedsDetails.includes(bundleUpdate.state) ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
2861
|
-
? { ...
|
|
3032
|
+
? { ...feedbackNoticeStyle, color: 'var(--dsw-alias-state-error-primary)' } : feedbackNoticeStyle }, bundleUpdate.message) : null,
|
|
2862
3033
|
!canCheckUpdate && updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer',
|
|
2863
3034
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开更新指引 ↗') : null,
|
|
2864
3035
|
!canCheckUpdate && !updateGuideUrl ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|