@ai-wendao/ant 0.4.15 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/client.js +406 -81
- package/index.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,16 @@ 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
|
|
|
11
|
+
## 企业品牌
|
|
12
|
+
|
|
13
|
+
团队管理员可在 ANT 管理台的“企业品牌”页设置 DSH 侧栏 Logo、品牌名称、品牌徽标和新会话标题。Logo 接受 PNG、JPEG 或 WebP,最大 350 KB;图片和文字保存在当前团队的 ANT 服务端。成员 Bundle 启动时立即同步,DSH 保持打开时每 5 分钟通过 ETag 检查版本;网络暂时不可用时继续使用本机最近一次成功配置,不阻止 DSH 启动。
|
|
14
|
+
|
|
15
|
+
Logo 与侧栏品牌使用 DSH 正式 UI 插槽。新会话标题目前没有官方文本插槽,Bundle 只在页面中精确匹配 DSH 当前标题原文后替换;上游更改该文案时可能需要更新 ANT Bundle。此项不影响会话和工具功能,也不控制 DSH 原生欢迎/登录窗口、菜单或应用图标。
|
|
16
|
+
|
|
11
17
|
飞书和企业微信的机器人连接、消息收发由 DSH-IM 负责;ANT Bundle 不保存机器人密钥或直连 IM 平台。ANT 继续负责团队成员身份和模型授权,并可在成员显式开启时接管一个专用飞书机器人的文本收件,按成员授权范围处理协作请求。
|
|
12
18
|
|
|
13
19
|
## 协作机器人与任务卡
|
|
@@ -26,7 +32,7 @@ PR 快照任务使用已核验的 PR Head SHA 创建独立工作树。Codex 使
|
|
|
26
32
|
|
|
27
33
|
## 企业精选技能
|
|
28
34
|
|
|
29
|
-
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md`
|
|
35
|
+
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md` 导入技能,核对来源、文件和依赖后发布,并为企业技能和插件指定主要用途类别。成员从 DSH 左侧“企业扩展”进入目录,可组合筛选办公文档、编程开发、设计与多媒体、数据分析、协作沟通、写作与营销、自动化与效率、运维与安全或其他类别,同时按来源范围、技能/插件类型和安装状态筛选或搜索。类别只用于发现内容;“本企业”“全市场精选”和“DSH 官方”是独立来源范围。ANT 插件设置页只处理连接、模型和更新。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
|
|
30
36
|
|
|
31
37
|
安装不会自动运行技能脚本或安装 Python、Node 等系统依赖。ANT 只更新或卸载自己管理且本机内容未被修改的技能目录;发现同名个人技能或本机改动时会停止覆盖。
|
|
32
38
|
|
package/client.js
CHANGED
|
@@ -12,7 +12,10 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
return value.replace(suffix, '').trim() || 'OpenAI'
|
|
13
13
|
}
|
|
14
14
|
const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
|
|
15
|
+
const BRANDING_CACHE_PREFIX = 'ant-team-branding:'
|
|
16
|
+
const BRANDING_SYNC_PATH = '/api/ant-teams/branding'
|
|
15
17
|
const PROVIDER_ROUTES_STORAGE_KEY = 'ant-team-provider-routes'
|
|
18
|
+
const PROVIDER_SYNC_EXCLUSIONS_KEY = 'ant-team-provider-sync-exclusions'
|
|
16
19
|
const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
|
|
17
20
|
const DEFAULT_MODEL_INITIALIZED_KEY = 'ant-team-default-model-initialized'
|
|
18
21
|
const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
|
|
@@ -31,32 +34,41 @@ window.__ModuleLoader__.load({
|
|
|
31
34
|
} catch { return 'connection' }
|
|
32
35
|
}
|
|
33
36
|
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-
|
|
37
|
+
const extensionCategories = [
|
|
38
|
+
['office', '办公文档'], ['development', '编程开发'], ['design', '设计与多媒体'],
|
|
39
|
+
['data', '数据分析'], ['collaboration', '协作沟通'], ['content', '写作与营销'],
|
|
40
|
+
['automation', '自动化与效率'], ['operations', '运维与安全'], ['other', '其他'],
|
|
41
|
+
]
|
|
42
|
+
const extensionCategoryLabels = Object.fromEntries(extensionCategories)
|
|
43
|
+
const legacyMarketSkillCategories = {
|
|
44
|
+
'ant-ppt-master': 'office', 'ant-frontend-slides': 'design', 'ant-slidev': 'development',
|
|
45
|
+
'ant-word-docx': 'office', 'ant-excel-xlsx': 'office', 'ant-pdf': 'office',
|
|
46
|
+
'ant-internal-comms': 'collaboration', 'ant-formatted-report-writer': 'office',
|
|
47
|
+
'ant-agent-browser': 'automation', 'ant-vercel-react-best-practices': 'development',
|
|
48
|
+
'ant-vercel-composition-patterns': 'development', 'ant-supabase-postgres-best-practices': 'data',
|
|
49
|
+
'ant-diagnosing-bugs': 'development', 'ant-improve-codebase-architecture': 'development',
|
|
50
|
+
'ant-code-review': 'development', 'ant-systematic-debugging': 'development',
|
|
51
|
+
'ant-test-driven-development': 'development', 'ant-verification-before-completion': 'development',
|
|
52
|
+
'ant-writing-plans': 'development', 'ant-to-spec': 'development', 'ant-skill-creator': 'automation',
|
|
53
|
+
'ant-frontend-design': 'design', 'ant-web-design-guidelines': 'design',
|
|
54
|
+
'ant-ui-ux-pro-max': 'design', 'ant-emil-design-eng': 'design',
|
|
55
|
+
'ant-review-animations': 'design', 'ant-shadcn': 'design',
|
|
56
|
+
'ant-theme-factory': 'design', 'ant-brand-guidelines': 'design',
|
|
57
|
+
'ant-copywriting': 'content', 'ant-seo-audit': 'content', 'ant-brainstorming': 'content',
|
|
49
58
|
}
|
|
59
|
+
const categoryOf = (entry) => extensionCategoryLabels[entry?.category]
|
|
60
|
+
? entry.category : legacyMarketSkillCategories[entry?.dsh_name] || 'other'
|
|
61
|
+
const categoryLabel = (category) => extensionCategoryLabels[category] || extensionCategoryLabels.other
|
|
50
62
|
const officialOfficeGuide = 'https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/packages/skill/skill-office/README.zh.md'
|
|
51
63
|
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
64
|
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: '
|
|
65
|
+
{ name: 'office', title: 'Office 办公入口', category: 'office', since: '0.2.1-rc.2', description: '根据任务选择 Word、PPT、Excel 或文件处理能力。', prompt: '帮我完成这份办公文件' },
|
|
66
|
+
{ name: 'office-docx', title: 'Word 文档', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 DOCX 文档。', prompt: '帮我写一份 Word 报告' },
|
|
67
|
+
{ name: 'office-pptx', title: 'PPT 演示', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 PPTX 演示文稿。', prompt: '帮我做一份项目汇报 PPT' },
|
|
68
|
+
{ name: 'office-xlsx', title: 'Excel 表格', category: 'office', since: '0.2.0-rc.2', description: '创建、修改和检查 XLSX 工作簿。', prompt: '帮我整理这份 Excel 表格' },
|
|
69
|
+
{ name: 'office-render', title: '文件预览', category: 'office', since: '0.2.1-rc.2', description: '把 Office 文件渲染成图片,检查实际外观。', prompt: '请预览这份文档并检查排版' },
|
|
70
|
+
{ name: 'office-recalculate', title: '公式重算', category: 'office', since: '0.2.1-rc.2', description: '重新计算表格公式并核对结果。', prompt: '请重新计算这份表格的公式' },
|
|
71
|
+
{ name: 'office-export-pdf', title: '导出 PDF', category: 'office', since: '0.2.1-rc.2', description: '将 Office 文件导出为 PDF。', prompt: '把这份 Word 文档导出成 PDF' },
|
|
60
72
|
]
|
|
61
73
|
const curatedSkillIntroductions = {
|
|
62
74
|
"ant-ppt-master": {"ref": "c350bc31e6d43b7bcf5234183d650ba8f9f4c9d7", "description": "制作和编辑 PowerPoint 演示文稿。"},
|
|
@@ -115,7 +127,7 @@ window.__ModuleLoader__.load({
|
|
|
115
127
|
}
|
|
116
128
|
return null
|
|
117
129
|
}
|
|
118
|
-
const skillCategory = (skill) =>
|
|
130
|
+
const skillCategory = (skill) => categoryOf(skill)
|
|
119
131
|
const panelStyle = {
|
|
120
132
|
display: 'grid', alignContent: 'start', gap: 14, padding: 18,
|
|
121
133
|
border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 14,
|
|
@@ -128,6 +140,9 @@ window.__ModuleLoader__.load({
|
|
|
128
140
|
borderLeft: '3px solid var(--dsw-alias-state-business-primary)',
|
|
129
141
|
}
|
|
130
142
|
const mutedTextStyle = { margin: 0, color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6 }
|
|
143
|
+
const feedbackNoticeStyle = { ...mutedTextStyle, position: 'sticky', top: 0, zIndex: 8,
|
|
144
|
+
padding: '9px 12px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
145
|
+
backgroundColor: 'var(--dsw-alias-bg-base)', boxShadow: '0 4px 14px #173d3510', overflowWrap: 'anywhere' }
|
|
131
146
|
const sectionTitleStyle = { margin: 0, fontSize: 16, fontWeight: 650, lineHeight: 1.4 }
|
|
132
147
|
const inputStyle = {
|
|
133
148
|
width: '100%', boxSizing: 'border-box', padding: '10px 12px',
|
|
@@ -393,9 +408,8 @@ window.__ModuleLoader__.load({
|
|
|
393
408
|
binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
|
|
394
409
|
binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
|
|
395
410
|
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))
|
|
411
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
412
|
+
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)
|
|
399
413
|
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
400
414
|
h('div', { style: { display: 'grid', gap: 4 } },
|
|
401
415
|
h('h2', { style: { margin: 0, fontSize: 20, fontWeight: 650, lineHeight: 1.35 } }, '团队与协作'),
|
|
@@ -1220,7 +1234,7 @@ window.__ModuleLoader__.load({
|
|
|
1220
1234
|
return h('section', { style: { ...settingsPanelStyle,
|
|
1221
1235
|
display: ['collaboration', 'local'].includes(activeTab) ? 'grid' : 'none' },
|
|
1222
1236
|
'aria-label': '群聊协作设置' },
|
|
1223
|
-
message ? h('p', { role: 'status', style:
|
|
1237
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1224
1238
|
h('div', { id: 'ant-chat-entry', role: 'region', 'aria-label': '聊天入口和机器人状态', tabIndex: -1,
|
|
1225
1239
|
style: { display: activeTab === 'collaboration' ? 'grid' : 'none', gap: 12 } },
|
|
1226
1240
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
@@ -1588,8 +1602,6 @@ window.__ModuleLoader__.load({
|
|
|
1588
1602
|
&& (scopeFilter === 'all' || scopeFilter === 'official')
|
|
1589
1603
|
&& (categoryFilter === 'all' || skill.category === categoryFilter)
|
|
1590
1604
|
})
|
|
1591
|
-
const categories = [...new Set([...skills.map(skillCategory), ...officialOfficeSkills.map((skill) => skill.category)])]
|
|
1592
|
-
.sort((a, b) => a.localeCompare(b, 'zh-CN'))
|
|
1593
1605
|
const statusLabel = { installed: '已放入本机目录', 'update-available': '可更新', 'not-installed': '未安装', conflict: '本机冲突' }
|
|
1594
1606
|
if (originalView) return h('section', { 'aria-label': '技能原文', style: {
|
|
1595
1607
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
@@ -1638,19 +1650,20 @@ window.__ModuleLoader__.load({
|
|
|
1638
1650
|
h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
|
|
1639
1651
|
style: { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
|
|
1640
1652
|
includePlugins ? null : h('span', { style: { ...mutedTextStyle, marginLeft: 'auto' } }, `显示 ${visible.length + visibleOfficial.length} / ${skills.length + officialOfficeSkills.length} 个技能`)),
|
|
1641
|
-
|
|
1653
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
|
|
1642
1654
|
h('span', { style: mutedTextStyle }, '来源'),
|
|
1643
1655
|
...[['all', '全部'], ['official', 'DSH 官方'], ['market', '全市场精选'], ['team', '本企业']].map(([value, label]) =>
|
|
1644
1656
|
h('button', { key: value, type: 'button', onClick: () => setScopeFilter(value), 'aria-pressed': scopeFilter === value,
|
|
1645
1657
|
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),
|
|
1658
|
+
h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '用途类别',
|
|
1659
|
+
h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value),
|
|
1660
|
+
'aria-label': '筛选扩展用途类别', style: { ...inputStyle, width: 'auto', minHeight: 34 } },
|
|
1648
1661
|
h('option', { value: 'all' }, '全部用途'),
|
|
1649
|
-
...
|
|
1650
|
-
),
|
|
1662
|
+
...extensionCategories.map(([category, label]) => h('option', { key: category, value: category }, label)))),
|
|
1663
|
+
)),
|
|
1651
1664
|
includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, loading ? '技能 · 读取中…' : `技能 · ${visible.length + visibleOfficial.length}`) : null,
|
|
1652
1665
|
message && (!includePlugins || loadError || skills.length || (!loading && !query.trim()))
|
|
1653
|
-
? h('p', { role: loadError ? 'alert' : 'status', style:
|
|
1666
|
+
? h('p', { role: loadError ? 'alert' : 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1654
1667
|
visible.length && !includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, '全市场与本企业技能') : null,
|
|
1655
1668
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
1656
1669
|
...visible.map((skill) => {
|
|
@@ -1677,7 +1690,7 @@ window.__ModuleLoader__.load({
|
|
|
1677
1690
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
1678
1691
|
h('h2', { style: { margin: 0, fontSize: 19 } }, skill.title),
|
|
1679
1692
|
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))),
|
|
1693
|
+
h('span', { style: mutedTextStyle }, categoryLabel(skillCategory(skill)))),
|
|
1681
1694
|
h('span', { style: mutedTextStyle }, marketPending
|
|
1682
1695
|
? skill.team_connected ? '待企业启用' : '连接企业后查看启用状态'
|
|
1683
1696
|
: statusLabel[skill.local_status] || '状态未知')),
|
|
@@ -1742,20 +1755,24 @@ window.__ModuleLoader__.load({
|
|
|
1742
1755
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 } },
|
|
1743
1756
|
h('h3', { style: { margin: 0, fontSize: 17 } }, skill.title),
|
|
1744
1757
|
h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 6,
|
|
1745
|
-
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, 'DSH 自带 · 无需在 ANT 安装')
|
|
1758
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, 'DSH 自带 · 无需在 ANT 安装'),
|
|
1759
|
+
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
1760
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(skill.category))),
|
|
1746
1761
|
h('p', { style: mutedTextStyle }, skill.description),
|
|
1747
1762
|
h('p', { style: mutedTextStyle }, `在新会话中说:“${skill.prompt}”。也可输入 /${skill.name}。`),
|
|
1748
1763
|
h('p', { style: mutedTextStyle }, `DSH ${skill.since} 起提供;如果无法使用,请在 DSH 检查更新。`),
|
|
1749
1764
|
h('a', { href: officialOfficeEnglish(skill), target: '_blank', rel: 'noopener noreferrer',
|
|
1750
1765
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13, lineHeight: 1.6, padding: '4px 0' } }, '英文原文 ↗'))))) : null,
|
|
1751
1766
|
includePlugins ? h(PluginCenterPage, { remote, onBack, onAll, onSkills, openBundle,
|
|
1752
|
-
embedded: true, query, onQueryChange, externalFilter: filter,
|
|
1767
|
+
embedded: true, query, onQueryChange, externalFilter: filter, externalCategoryFilter: categoryFilter,
|
|
1768
|
+
externalScopeFilter: scopeFilter,
|
|
1753
1769
|
hasSkillMatches: visible.length + visibleOfficial.length > 0, skillCatalogReady: !loading && !loadError,
|
|
1754
1770
|
refreshToken }) : null))
|
|
1755
1771
|
}
|
|
1756
1772
|
|
|
1757
1773
|
function PluginCenterPage({ remote, onBack, onAll, onSkills, openBundle,
|
|
1758
1774
|
embedded = false, query, onQueryChange, externalFilter = 'all',
|
|
1775
|
+
externalCategoryFilter = 'all', externalScopeFilter = 'all',
|
|
1759
1776
|
hasSkillMatches = false, skillCatalogReady = false, refreshToken }) {
|
|
1760
1777
|
const [plugins, setPlugins] = useState([])
|
|
1761
1778
|
const [bundles, setBundles] = useState([])
|
|
@@ -1766,6 +1783,7 @@ window.__ModuleLoader__.load({
|
|
|
1766
1783
|
const [completedId, setCompletedId] = useState('')
|
|
1767
1784
|
const [message, setMessage] = useState('')
|
|
1768
1785
|
const [filter, setFilter] = useState('all')
|
|
1786
|
+
const [categoryFilter, setCategoryFilter] = useState('all')
|
|
1769
1787
|
const refreshGeneration = useRef(0)
|
|
1770
1788
|
const refresh = async () => {
|
|
1771
1789
|
const generation = ++refreshGeneration.current
|
|
@@ -1889,21 +1907,46 @@ window.__ModuleLoader__.load({
|
|
|
1889
1907
|
} finally { setWorkingId('') }
|
|
1890
1908
|
}
|
|
1891
1909
|
const activeFilter = embedded ? externalFilter : filter
|
|
1910
|
+
const activeCategoryFilter = embedded ? externalCategoryFilter : categoryFilter
|
|
1911
|
+
const activeScopeFilter = embedded ? externalScopeFilter : 'team'
|
|
1912
|
+
const currentPluginByPackage = new Map()
|
|
1913
|
+
const publishedPluginPackages = new Set()
|
|
1914
|
+
for (const plugin of plugins) {
|
|
1915
|
+
if (!currentPluginByPackage.has(plugin.package_name)
|
|
1916
|
+
|| plugin.status === 'published' && !publishedPluginPackages.has(plugin.package_name)) {
|
|
1917
|
+
currentPluginByPackage.set(plugin.package_name, plugin.id)
|
|
1918
|
+
}
|
|
1919
|
+
if (plugin.status === 'published') publishedPluginPackages.add(plugin.package_name)
|
|
1920
|
+
}
|
|
1921
|
+
if (activeFilter === 'installed') {
|
|
1922
|
+
for (const plugin of plugins) {
|
|
1923
|
+
if (publishedPluginPackages.has(plugin.package_name)) continue
|
|
1924
|
+
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1925
|
+
if (!installed?.version || installed.version !== plugin.version) continue
|
|
1926
|
+
currentPluginByPackage.set(plugin.package_name, plugin.id)
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1892
1929
|
const visible = plugins.filter((plugin) => {
|
|
1930
|
+
const isCurrentVersion = currentPluginByPackage.get(plugin.package_name) === plugin.id
|
|
1893
1931
|
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
1894
1932
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
1895
1933
|
const installSpec = pluginInstallSpec(plugin)
|
|
1896
1934
|
const localAhead = installed?.version && newerVersion(installed.version, plugin.version)
|
|
1897
1935
|
const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
|
|
1898
|
-
return matches && (
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1936
|
+
return matches && (activeScopeFilter === 'all' || activeScopeFilter === 'team')
|
|
1937
|
+
&& (activeCategoryFilter === 'all' || categoryOf(plugin) === activeCategoryFilter)
|
|
1938
|
+
&& (activeFilter === 'retired'
|
|
1939
|
+
? plugin.status !== 'published'
|
|
1940
|
+
: isCurrentVersion && (activeFilter === 'all'
|
|
1941
|
+
|| activeFilter === 'installed' && Boolean(installed)
|
|
1942
|
+
|| activeFilter === 'available' && plugin.status === 'published' && installSpec && !localAhead && (!installed || (installed.version && installed.version !== plugin.version))
|
|
1943
|
+
|| activeFilter === 'updates' && plugin.status === 'published' && installSpec && installed && installed.version && installed.version !== plugin.version && !localAhead)
|
|
1944
|
+
)
|
|
1902
1945
|
})
|
|
1903
1946
|
const noMatches = embedded
|
|
1904
1947
|
? skillCatalogReady ? '没有找到符合条件的企业扩展。' : '插件中没有符合条件的结果。'
|
|
1905
1948
|
: activeFilter === 'available'
|
|
1906
|
-
? '
|
|
1949
|
+
? '当前没有可一键安装的插件。可在“目录概览”查看审核目录,或联系企业管理员。'
|
|
1907
1950
|
: '没有符合筛选条件的插件。'
|
|
1908
1951
|
return h('section', { 'aria-label': '企业精选插件', style: embedded
|
|
1909
1952
|
? { display: 'grid', gap: 16 }
|
|
@@ -1926,15 +1969,20 @@ window.__ModuleLoader__.load({
|
|
|
1926
1969
|
: h('section', { role: 'status', style: panelStyle },
|
|
1927
1970
|
h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
|
|
1928
1971
|
h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
|
|
1929
|
-
!loading && loadError ? h('p', { role: 'alert', style:
|
|
1972
|
+
!loading && loadError ? h('p', { role: 'alert', style: feedbackNoticeStyle }, message) : null,
|
|
1930
1973
|
plugins.length && !embedded ? h(ExtensionRequestNotice) : null,
|
|
1931
1974
|
plugins.length && !embedded ? h('div', { style: panelStyle },
|
|
1932
1975
|
h('input', { type: 'search', value: query, onChange: (event) => onQueryChange(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
|
|
1933
1976
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
1934
|
-
...[['all', '
|
|
1977
|
+
...[['all', '目录概览'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
|
|
1935
1978
|
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
|
-
|
|
1979
|
+
style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
|
|
1980
|
+
h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '用途类别',
|
|
1981
|
+
h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value),
|
|
1982
|
+
'aria-label': '筛选插件用途类别', style: { ...inputStyle, width: 'auto', minHeight: 34 } },
|
|
1983
|
+
h('option', { value: 'all' }, '全部用途'),
|
|
1984
|
+
...extensionCategories.map(([category, label]) => h('option', { key: category, value: category }, label)))))) : null,
|
|
1985
|
+
plugins.length && message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
1938
1986
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
1939
1987
|
...visible.map((plugin) => {
|
|
1940
1988
|
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
@@ -1947,7 +1995,7 @@ window.__ModuleLoader__.load({
|
|
|
1947
1995
|
const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
|
|
1948
1996
|
const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
|
|
1949
1997
|
const status = security ? '已废弃(安全风险)'
|
|
1950
|
-
: plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' : localAhead
|
|
1998
|
+
: plugin.status === 'deprecated' ? local?.version ? `已废弃 · 本机 ${local.version}` : '已废弃' : local?.error ? '加载异常' : localAhead
|
|
1951
1999
|
? '本机版本高于团队目录版本' : sameVersion
|
|
1952
2000
|
? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
|
|
1953
2001
|
: sourceKnown ? '本机同版,来源不同' : recordedInstall
|
|
@@ -1959,7 +2007,9 @@ window.__ModuleLoader__.load({
|
|
|
1959
2007
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
1960
2008
|
h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
|
|
1961
2009
|
h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
|
|
1962
|
-
embedded ? h('span', { style: mutedTextStyle }, '插件') : null
|
|
2010
|
+
embedded ? h('span', { style: mutedTextStyle }, '插件') : null,
|
|
2011
|
+
h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6,
|
|
2012
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, categoryLabel(categoryOf(plugin)))),
|
|
1963
2013
|
h('span', { style: security
|
|
1964
2014
|
? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)', fontWeight: 650 } : mutedTextStyle }, status)),
|
|
1965
2015
|
h('p', { style: mutedTextStyle }, plugin.description),
|
|
@@ -2010,6 +2060,9 @@ window.__ModuleLoader__.load({
|
|
|
2010
2060
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
2011
2061
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
2012
2062
|
const [authorizedModels, setAuthorizedModels] = useState([])
|
|
2063
|
+
const [providerSyncGroups, setProviderSyncGroups] = useState([])
|
|
2064
|
+
const [appliedProviderSyncExclusions, setAppliedProviderSyncExclusions] = useState([])
|
|
2065
|
+
const [providerSyncDraft, setProviderSyncDraft] = useState([])
|
|
2013
2066
|
const [serverDefaultModel, setServerDefaultModel] = useState(null)
|
|
2014
2067
|
const [dshDefaultSelection, setDshDefaultSelection] = useState(null)
|
|
2015
2068
|
const [defaultModelDraft, setDefaultModelDraft] = useState('')
|
|
@@ -2095,17 +2148,27 @@ window.__ModuleLoader__.load({
|
|
|
2095
2148
|
: `ant-team-openai-${model.provider_id}`
|
|
2096
2149
|
const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
|
|
2097
2150
|
const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2151
|
+
const providerSyncPreferenceKey = (rawServiceUrl) => `${PROVIDER_SYNC_EXCLUSIONS_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
|
|
2098
2152
|
const readDefaultPreference = (rawServiceUrl) => {
|
|
2099
2153
|
try { return JSON.parse(localStorage.getItem(defaultPreferenceKey(rawServiceUrl)) || 'null') } catch { return null }
|
|
2100
2154
|
}
|
|
2101
2155
|
const saveDefaultPreference = (rawServiceUrl, value) => {
|
|
2102
2156
|
try { localStorage.setItem(defaultPreferenceKey(rawServiceUrl), JSON.stringify(value)) } catch {}
|
|
2103
2157
|
}
|
|
2104
|
-
const modelForProviderSelection = (selection, models) => {
|
|
2158
|
+
const modelForProviderSelection = (selection, models, preferredModel = null) => {
|
|
2105
2159
|
if (!selection) return null
|
|
2106
2160
|
const exact = models.find((model) => model.model_id === selection.model && modelProvider(model) === selection.provider)
|
|
2107
2161
|
if (exact) return exact
|
|
2108
|
-
|
|
2162
|
+
if (['ant-team-openai', 'ant-team-openai-chat-completions'].includes(selection.provider)) {
|
|
2163
|
+
const chatCandidates = models.filter((model) => model.protocol === 'openai'
|
|
2164
|
+
&& model.openai_api_mode !== 'responses' && model.model_id === selection.model)
|
|
2165
|
+
const preferredChat = preferredModel && chatCandidates.find((model) =>
|
|
2166
|
+
modelSelectionKey(model) === modelSelectionKey(preferredModel))
|
|
2167
|
+
if (preferredChat) return preferredChat
|
|
2168
|
+
const canonicalChat = chatCandidates.find((model) => model.provider_id === 'openai-default')
|
|
2169
|
+
return canonicalChat || (chatCandidates.length === 1 ? chatCandidates[0] : null)
|
|
2170
|
+
}
|
|
2171
|
+
const legacyProvider = selection.provider === 'ant-team-openai-responses'
|
|
2109
2172
|
? 'openai-default' : selection.provider === 'ant-team-anthropic' ? 'anthropic-default' : null
|
|
2110
2173
|
return legacyProvider ? models.find((model) => model.provider_id === legacyProvider && model.model_id === selection.model) : null
|
|
2111
2174
|
}
|
|
@@ -2131,8 +2194,8 @@ window.__ModuleLoader__.load({
|
|
|
2131
2194
|
return selection
|
|
2132
2195
|
}
|
|
2133
2196
|
const applyUserDefaultModel = async (model) => {
|
|
2134
|
-
if (!model || !
|
|
2135
|
-
setDefaultModelMessage('
|
|
2197
|
+
if (!model || !syncedModels.some((item) => modelSelectionKey(item) === modelSelectionKey(model))) {
|
|
2198
|
+
setDefaultModelMessage('请选择当前已同步到 DSH 的模型。')
|
|
2136
2199
|
return
|
|
2137
2200
|
}
|
|
2138
2201
|
setDefaultModelBusy(true)
|
|
@@ -2149,7 +2212,7 @@ window.__ModuleLoader__.load({
|
|
|
2149
2212
|
setDefaultModelMessage(error instanceof Error ? error.message : '保存本机默认模型失败。')
|
|
2150
2213
|
} finally { setDefaultModelBusy(false) }
|
|
2151
2214
|
}
|
|
2152
|
-
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null) => {
|
|
2215
|
+
const applyAuthorizedModels = async (models, rawServiceUrl, rawDefaultModel = null, options = {}) => {
|
|
2153
2216
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
2154
2217
|
if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
|
|
2155
2218
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 版本未提供模型设置接口。')
|
|
@@ -2183,6 +2246,8 @@ window.__ModuleLoader__.load({
|
|
|
2183
2246
|
: provider.providerId === 'anthropic-default'
|
|
2184
2247
|
? 'ant-team-anthropic' : `ant-team-${provider.protocol}-${provider.providerId}`
|
|
2185
2248
|
const routeConfigurations = new Map()
|
|
2249
|
+
const compatibilityRoutes = []
|
|
2250
|
+
const providerGroups = []
|
|
2186
2251
|
for (const provider of providers) {
|
|
2187
2252
|
const route = routeForProvider(provider)
|
|
2188
2253
|
const api = provider.protocol === 'openai'
|
|
@@ -2200,9 +2265,17 @@ window.__ModuleLoader__.load({
|
|
|
2200
2265
|
baseURL,
|
|
2201
2266
|
models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
|
|
2202
2267
|
})
|
|
2268
|
+
providerGroups.push({
|
|
2269
|
+
key: route,
|
|
2270
|
+
label: `${provider.providerName} · ${provider.protocol === 'anthropic' ? 'Anthropic Messages' : provider.apiMode === 'responses' ? 'OpenAI Responses' : 'OpenAI Chat Completions'}`,
|
|
2271
|
+
routes: [route],
|
|
2272
|
+
modelCount: provider.models.length,
|
|
2273
|
+
legacy: false,
|
|
2274
|
+
})
|
|
2203
2275
|
if (provider.protocol === 'openai' && provider.providerId === 'openai-default') {
|
|
2204
2276
|
const aliases = provider.apiMode === 'responses'
|
|
2205
2277
|
? ['ant-team-openai-chat-completions', 'ant-team-openai'] : ['ant-team-openai']
|
|
2278
|
+
compatibilityRoutes.push(...aliases)
|
|
2206
2279
|
const legacyChat = {
|
|
2207
2280
|
displayName: `${provider.providerName} · Chat(旧会话兼容)`,
|
|
2208
2281
|
apiKeyEnv: 'ANT_MODEL_API_KEY',
|
|
@@ -2213,13 +2286,56 @@ window.__ModuleLoader__.load({
|
|
|
2213
2286
|
for (const alias of aliases) routeConfigurations.set(alias, legacyChat)
|
|
2214
2287
|
}
|
|
2215
2288
|
}
|
|
2216
|
-
const routes = [...routeConfigurations.keys()]
|
|
2217
2289
|
const ops = []
|
|
2218
2290
|
let previousRoutes = []
|
|
2219
2291
|
try {
|
|
2220
2292
|
const parsed = JSON.parse(localStorage.getItem(PROVIDER_ROUTES_STORAGE_KEY) || '[]')
|
|
2221
2293
|
if (Array.isArray(parsed)) previousRoutes = parsed.filter((route) => typeof route === 'string')
|
|
2222
2294
|
} catch {}
|
|
2295
|
+
const compatibilityGroup = compatibilityRoutes.length ? [{
|
|
2296
|
+
key: 'legacy-openai-chat', label: 'ANT OpenAI · Chat(旧会话兼容)', routes: compatibilityRoutes,
|
|
2297
|
+
modelCount: providers.find((provider) => provider.providerId === 'openai-default')?.models.length || 0,
|
|
2298
|
+
legacy: true,
|
|
2299
|
+
}] : []
|
|
2300
|
+
const allGroups = [...providerGroups, ...compatibilityGroup]
|
|
2301
|
+
let excludedGroups = options.excludedGroups
|
|
2302
|
+
if (!Array.isArray(excludedGroups)) {
|
|
2303
|
+
try {
|
|
2304
|
+
const saved = JSON.parse(localStorage.getItem(providerSyncPreferenceKey(normalized)) || 'null')
|
|
2305
|
+
if (Array.isArray(saved)) excludedGroups = saved
|
|
2306
|
+
} catch {}
|
|
2307
|
+
}
|
|
2308
|
+
if (!Array.isArray(excludedGroups)) {
|
|
2309
|
+
// Existing installs keep their current legacy alias group; first-time installs get all routes.
|
|
2310
|
+
excludedGroups = previousRoutes.length && compatibilityRoutes.length
|
|
2311
|
+
&& !compatibilityRoutes.some((route) => previousRoutes.includes(route)) ? ['legacy-openai-chat'] : []
|
|
2312
|
+
}
|
|
2313
|
+
excludedGroups = [...new Set(excludedGroups.filter((key) => allGroups.some((group) => group.key === key)))]
|
|
2314
|
+
const excludedGroupSet = new Set(excludedGroups)
|
|
2315
|
+
const excluded = new Set(allGroups.filter((group) => excludedGroupSet.has(group.key)).flatMap((group) => group.routes))
|
|
2316
|
+
const routes = [...routeConfigurations.keys()].filter((route) => !excluded.has(route))
|
|
2317
|
+
const syncedModels = models.filter((model) => !excluded.has(modelProvider(model)))
|
|
2318
|
+
if (models.length && !syncedModels.length) throw new Error('请至少保留一个常规模型 provider 同步到本机 DSH。')
|
|
2319
|
+
const defaultMatch = rawDefaultModel && models.find((model) =>
|
|
2320
|
+
(rawDefaultModel.provider_id ? model.provider_id === rawDefaultModel.provider_id : model.protocol === rawDefaultModel.protocol)
|
|
2321
|
+
&& model.protocol === rawDefaultModel.protocol && model.model_id === rawDefaultModel.model_id)
|
|
2322
|
+
const syncedDefaultMatch = defaultMatch && syncedModels.some((model) => modelSelectionKey(model) === modelSelectionKey(defaultMatch))
|
|
2323
|
+
? defaultMatch : null
|
|
2324
|
+
const serverDefault = syncedDefaultMatch || null
|
|
2325
|
+
const initialDefault = syncedDefaultMatch || (rawDefaultModel == null ? syncedModels[0] : null)
|
|
2326
|
+
if (Array.isArray(options.excludedGroups)) {
|
|
2327
|
+
const intentionallyRemovedRoutes = [...excluded].filter((route) => previousRoutes.includes(route))
|
|
2328
|
+
if (intentionallyRemovedRoutes.length && !window.confirm(
|
|
2329
|
+
'关闭这些 provider 会从本机 DSH 移除对应模型路由。保存了这些路由的历史会话可能暂时无法继续;重新开启同步可以恢复。仍要应用吗?')) {
|
|
2330
|
+
throw new Error('已取消同步范围更改;本机 DSH 设置未修改。')
|
|
2331
|
+
}
|
|
2332
|
+
const currentSelection = await readDshDefaultSelection()
|
|
2333
|
+
if (currentSelection?.provider && excluded.has(currentSelection.provider)) {
|
|
2334
|
+
const replacement = modelForProviderSelection(currentSelection, syncedModels, serverDefault) || serverDefault || syncedModels[0]
|
|
2335
|
+
if (!replacement) throw new Error('请先选择一个保留同步的模型,再移除当前 DSH 默认 provider。')
|
|
2336
|
+
await writeDshDefaultModel(replacement)
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2223
2339
|
const knownRoutes = new Set([...previousRoutes, 'ant-team-openai', 'ant-team-openai-chat-completions',
|
|
2224
2340
|
'ant-team-openai-responses', 'ant-team-anthropic'])
|
|
2225
2341
|
for (const route of knownRoutes) {
|
|
@@ -2231,11 +2347,10 @@ window.__ModuleLoader__.load({
|
|
|
2231
2347
|
const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
|
|
2232
2348
|
if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
|
|
2233
2349
|
try { localStorage.setItem(PROVIDER_ROUTES_STORAGE_KEY, JSON.stringify(routes)) } catch {}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
const initialDefault = defaultMatch || (rawDefaultModel == null ? models[0] : null)
|
|
2350
|
+
try { localStorage.setItem(providerSyncPreferenceKey(normalized), JSON.stringify(excludedGroups)) } catch {}
|
|
2351
|
+
setProviderSyncGroups(allGroups)
|
|
2352
|
+
setAppliedProviderSyncExclusions(excludedGroups)
|
|
2353
|
+
setProviderSyncDraft(excludedGroups)
|
|
2239
2354
|
setServerDefaultModel(serverDefault)
|
|
2240
2355
|
const preference = readDefaultPreference(normalized)
|
|
2241
2356
|
let statusMessage = ''
|
|
@@ -2249,8 +2364,8 @@ window.__ModuleLoader__.load({
|
|
|
2249
2364
|
: '当前无法使用团队默认模型;已用首个获准模型初始化 DSH 全局默认。请管理员检查默认设置和成员授权。'
|
|
2250
2365
|
} else {
|
|
2251
2366
|
selection = await readDshDefaultSelection()
|
|
2252
|
-
const currentAuthorized = modelForProviderSelection(selection,
|
|
2253
|
-
const baseline =
|
|
2367
|
+
const currentAuthorized = modelForProviderSelection(selection, syncedModels, serverDefault)
|
|
2368
|
+
const baseline = syncedModels.find((model) => modelSelectionKey(model) === preference?.serverDefault)
|
|
2254
2369
|
const stillUsingBaseline = baseline && currentAuthorized && modelSelectionKey(currentAuthorized) === modelSelectionKey(baseline)
|
|
2255
2370
|
if (currentAuthorized && selection?.provider !== modelProvider(currentAuthorized)) {
|
|
2256
2371
|
selection = await writeDshDefaultModel(currentAuthorized)
|
|
@@ -2259,8 +2374,8 @@ window.__ModuleLoader__.load({
|
|
|
2259
2374
|
selection = await writeDshDefaultModel(serverDefault)
|
|
2260
2375
|
saveDefaultPreference(normalized, { ...preference, initialized: true, serverDefault: modelSelectionKey(serverDefault) })
|
|
2261
2376
|
statusMessage = '团队默认模型已更新;你尚未设置个人覆盖,因此已同步到 DSH 全局默认。'
|
|
2262
|
-
} else if (selection?.provider?.startsWith('ant-team-') && !currentAuthorized &&
|
|
2263
|
-
const fallback = serverDefault ||
|
|
2377
|
+
} else if (selection?.provider?.startsWith('ant-team-') && !currentAuthorized && syncedModels.length) {
|
|
2378
|
+
const fallback = serverDefault || syncedModels[0]
|
|
2264
2379
|
selection = await writeDshDefaultModel(fallback)
|
|
2265
2380
|
saveDefaultPreference(normalized, { ...preference, initialized: true,
|
|
2266
2381
|
serverDefault: modelSelectionKey(serverDefault || fallback) })
|
|
@@ -2268,11 +2383,11 @@ window.__ModuleLoader__.load({
|
|
|
2268
2383
|
? '当前默认模型已失去授权,已恢复为团队默认模型。'
|
|
2269
2384
|
: '当前默认模型已失去授权,已改用首个获准模型;请管理员设置团队默认模型。'
|
|
2270
2385
|
}
|
|
2271
|
-
const effectiveAuthorized = modelForProviderSelection(selection,
|
|
2386
|
+
const effectiveAuthorized = modelForProviderSelection(selection, syncedModels)
|
|
2272
2387
|
setDshDefaultSelection(selection)
|
|
2273
2388
|
setDefaultModelDraft(effectiveAuthorized ? modelSelectionKey(effectiveAuthorized) : '')
|
|
2274
2389
|
if (!serverDefault && !statusMessage) statusMessage = rawDefaultModel
|
|
2275
|
-
? '
|
|
2390
|
+
? '团队默认模型当前未同步到这台 DSH;请在下方选择一个已同步模型作为本机默认。'
|
|
2276
2391
|
: '团队默认模型尚未设置;请联系管理员设置,或在下方选择本机可用的默认模型。'
|
|
2277
2392
|
else if (!statusMessage) statusMessage = effectiveAuthorized
|
|
2278
2393
|
? '已保留你选择的 DSH 全局默认模型。'
|
|
@@ -2281,10 +2396,10 @@ window.__ModuleLoader__.load({
|
|
|
2281
2396
|
setDefaultModelMessage(statusMessage)
|
|
2282
2397
|
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized) } catch {}
|
|
2283
2398
|
onServiceUrlChange(normalized)
|
|
2284
|
-
return { count:
|
|
2399
|
+
return { count: syncedModels.length, defaultSelection: selection }
|
|
2285
2400
|
}
|
|
2286
2401
|
|
|
2287
|
-
const refreshModels = async () => {
|
|
2402
|
+
const refreshModels = async (applyDraft = false) => {
|
|
2288
2403
|
if (!bindingReady) {
|
|
2289
2404
|
setMessage('请先等待团队绑定检查完成,再刷新团队模型。')
|
|
2290
2405
|
return
|
|
@@ -2301,13 +2416,14 @@ window.__ModuleLoader__.load({
|
|
|
2301
2416
|
if (response.status === 401) setNeedsReconnect(true)
|
|
2302
2417
|
throw new Error(result.message || `刷新失败(${response.status})`)
|
|
2303
2418
|
}
|
|
2304
|
-
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model
|
|
2419
|
+
const sync = await applyAuthorizedModels(result.models, result.service_url, result.default_model,
|
|
2420
|
+
applyDraft ? { excludedGroups: providerSyncDraft } : {})
|
|
2305
2421
|
setAuthorizedModels(result.models)
|
|
2306
2422
|
setLastModelSync(new Date())
|
|
2307
2423
|
setCredentialConfigured(true)
|
|
2308
2424
|
setNeedsReconnect(false)
|
|
2309
2425
|
setReauthenticate(false)
|
|
2310
|
-
setModelMessage(
|
|
2426
|
+
setModelMessage(`模型目录已刷新,共同步 ${sync.count} 个获准模型。`)
|
|
2311
2427
|
} catch (error) {
|
|
2312
2428
|
setModelMessage(error instanceof Error ? error.message : '刷新模型失败,请稍后重试。')
|
|
2313
2429
|
} finally {
|
|
@@ -2509,6 +2625,7 @@ window.__ModuleLoader__.load({
|
|
|
2509
2625
|
if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
|
|
2510
2626
|
setCredentialConfigured(false)
|
|
2511
2627
|
setAuthorizedModels([]); setServerDefaultModel(null); setDshDefaultSelection(null); setDefaultModelDraft('')
|
|
2628
|
+
setProviderSyncGroups([]); setAppliedProviderSyncExclusions([]); setProviderSyncDraft([])
|
|
2512
2629
|
try { localStorage.removeItem(`${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(serviceUrl)}:${savedDeviceId() || ''}`) } catch {}
|
|
2513
2630
|
onConnectionChange?.(null)
|
|
2514
2631
|
setNeedsReconnect(false)
|
|
@@ -2720,11 +2837,35 @@ window.__ModuleLoader__.load({
|
|
|
2720
2837
|
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
2721
2838
|
const updateGuideUrl = pinnedServiceUrl
|
|
2722
2839
|
? new URL('/ant-update', pinnedServiceUrl).href : null
|
|
2723
|
-
const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels)
|
|
2840
|
+
const currentDshDefaultModel = modelForProviderSelection(dshDefaultSelection, authorizedModels, serverDefaultModel)
|
|
2841
|
+
const excludedGroupSet = new Set(providerSyncDraft)
|
|
2842
|
+
const excludedRouteSet = new Set(providerSyncGroups.filter((group) => excludedGroupSet.has(group.key))
|
|
2843
|
+
.flatMap((group) => group.routes))
|
|
2844
|
+
const appliedExcludedGroupSet = new Set(appliedProviderSyncExclusions)
|
|
2845
|
+
const appliedExcludedRouteSet = new Set(providerSyncGroups.filter((group) => appliedExcludedGroupSet.has(group.key))
|
|
2846
|
+
.flatMap((group) => group.routes))
|
|
2847
|
+
const syncedModels = authorizedModels.filter((model) => !appliedExcludedRouteSet.has(modelProvider(model)))
|
|
2848
|
+
const hasPendingSyncChanges = [...new Set([...providerSyncDraft, ...appliedProviderSyncExclusions])]
|
|
2849
|
+
.some((key) => excludedGroupSet.has(key) !== appliedExcludedGroupSet.has(key))
|
|
2850
|
+
const activePrimaryGroupCount = providerSyncGroups.filter((group) => !group.legacy && !excludedGroupSet.has(group.key)).length
|
|
2851
|
+
const pendingServerDefaultRemoval = hasPendingSyncChanges && serverDefaultModel
|
|
2852
|
+
? providerSyncGroups.some((group) => group.routes.includes(modelProvider(serverDefaultModel))
|
|
2853
|
+
&& !appliedExcludedGroupSet.has(group.key) && excludedGroupSet.has(group.key))
|
|
2854
|
+
: false
|
|
2855
|
+
const selectedDefaultDraft = syncedModels.find((model) => modelSelectionKey(model) === defaultModelDraft)
|
|
2856
|
+
const toggleProviderSyncGroup = (group, checked) => {
|
|
2857
|
+
setProviderSyncDraft((current) => {
|
|
2858
|
+
const next = new Set(current)
|
|
2859
|
+
if (checked) next.delete(group.key)
|
|
2860
|
+
else next.add(group.key)
|
|
2861
|
+
return [...next]
|
|
2862
|
+
})
|
|
2863
|
+
}
|
|
2724
2864
|
const modelRows = authorizedModels.map((model) => {
|
|
2725
2865
|
const providerName = model.protocol === 'openai'
|
|
2726
2866
|
? compactOpenaiProviderName(model.provider_name)
|
|
2727
2867
|
: model.provider_name
|
|
2868
|
+
const isSynced = !excludedRouteSet.has(modelProvider(model))
|
|
2728
2869
|
return h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
|
|
2729
2870
|
style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
|
|
2730
2871
|
gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
|
|
@@ -2732,7 +2873,9 @@ window.__ModuleLoader__.load({
|
|
|
2732
2873
|
h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
|
|
2733
2874
|
backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
|
|
2734
2875
|
model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
|
|
2735
|
-
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } },
|
|
2876
|
+
h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, hasPendingSyncChanges
|
|
2877
|
+
? isSynced ? '刷新后同步到 DSH' : '刷新后从 DSH 移除'
|
|
2878
|
+
: isSynced ? '已同步到 DSH' : '未同步到 DSH'))
|
|
2736
2879
|
})
|
|
2737
2880
|
const connectionPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'connection' ? 'grid' : 'none' } },
|
|
2738
2881
|
h('div', null,
|
|
@@ -2784,7 +2927,7 @@ window.__ModuleLoader__.load({
|
|
|
2784
2927
|
setMessage('设备编号已复制,请交给原团队管理员。')
|
|
2785
2928
|
} catch { setMessage(savedDeviceId() ? `请复制设备编号:${savedDeviceId()}` : '本机无法确认旧设备编号,请联系原团队管理员核查。') }
|
|
2786
2929
|
}, style: secondaryButtonStyle }, '复制设备编号'))) : null,
|
|
2787
|
-
message ? h('p', { role: 'status', style:
|
|
2930
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null)
|
|
2788
2931
|
|
|
2789
2932
|
const modelPanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'models' ? 'grid' : 'none' } },
|
|
2790
2933
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
@@ -2806,10 +2949,10 @@ window.__ModuleLoader__.load({
|
|
|
2806
2949
|
h('p', { style: mutedTextStyle }, '首次同步时,ANT 会用团队默认初始化 DSH 全局默认模型。它会影响该 Profile 的新会话以及 ANT 对话和任务;之后刷新保留你的个人覆盖。'),
|
|
2807
2950
|
h('label', { style: { display: 'grid', gap: 5, maxWidth: 720 } }, 'DSH 全局默认模型',
|
|
2808
2951
|
h('select', { 'aria-label': 'DSH 全局默认模型', value: defaultModelDraft,
|
|
2809
|
-
disabled: credentialConfigured !== true || !
|
|
2952
|
+
disabled: credentialConfigured !== true || !syncedModels.length || defaultModelBusy || busy || hasPendingSyncChanges,
|
|
2810
2953
|
onChange: (event) => setDefaultModelDraft(event.target.value), style: inputStyle },
|
|
2811
2954
|
h('option', { value: '' }, '选择获准模型'),
|
|
2812
|
-
...
|
|
2955
|
+
...syncedModels.map((model) => {
|
|
2813
2956
|
const providerName = model.protocol === 'openai'
|
|
2814
2957
|
? compactOpenaiProviderName(model.provider_name)
|
|
2815
2958
|
: model.provider_name
|
|
@@ -2818,14 +2961,28 @@ window.__ModuleLoader__.load({
|
|
|
2818
2961
|
`${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id} · ${protocolName}`)
|
|
2819
2962
|
}))),
|
|
2820
2963
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
|
|
2821
|
-
h('button', { type: 'button', disabled: defaultModelBusy || !
|
|
2822
|
-
onClick: () =>
|
|
2964
|
+
h('button', { type: 'button', disabled: defaultModelBusy || !selectedDefaultDraft || hasPendingSyncChanges,
|
|
2965
|
+
onClick: () => void applyUserDefaultModel(selectedDefaultDraft),
|
|
2823
2966
|
style: primaryButtonStyle }, defaultModelBusy ? '保存中…' : '保存为 DSH 全局默认'),
|
|
2824
|
-
h('button', { type: 'button', disabled: defaultModelBusy || !serverDefaultModel
|
|
2967
|
+
h('button', { type: 'button', disabled: busy || defaultModelBusy || pendingServerDefaultRemoval || !serverDefaultModel
|
|
2825
2968
|
|| modelSelectionKey(currentDshDefaultModel || {}) === modelSelectionKey(serverDefaultModel),
|
|
2826
2969
|
onClick: () => void applyUserDefaultModel(serverDefaultModel), style: secondaryButtonStyle }, '恢复团队默认'),
|
|
2827
2970
|
currentDshDefaultModel ? h('span', { style: mutedTextStyle }, `当前 DSH 默认:${currentDshDefaultModel.display_name || currentDshDefaultModel.model_id}`) : null),
|
|
2971
|
+
pendingServerDefaultRemoval ? h('p', { role: 'status', style: mutedTextStyle },
|
|
2972
|
+
'团队默认 provider 正准备移除;先刷新应用同步范围,或重新勾选该 provider。') : null,
|
|
2828
2973
|
defaultModelMessage ? h('p', { role: 'status', style: mutedTextStyle }, defaultModelMessage) : null),
|
|
2974
|
+
providerSyncGroups.length ? h('section', { style: { ...infoNoticeStyle, gap: 8 } },
|
|
2975
|
+
h('strong', null, '同步到本机 DSH 的 provider'),
|
|
2976
|
+
h('p', { style: mutedTextStyle }, '首次升级会保留当前已同步的旧会话兼容路由。取消勾选并刷新后,该 provider 会从本机 DSH 移除;使用它的历史会话可能暂时无法继续,重新勾选并刷新可恢复。'),
|
|
2977
|
+
h('p', { style: mutedTextStyle }, '至少保留一个常规模型 provider;旧会话兼容组只供已有会话使用。'),
|
|
2978
|
+
hasPendingSyncChanges ? h('p', { role: 'status', style: mutedTextStyle }, '同步范围尚未应用;点击“刷新团队模型”后生效。') : null,
|
|
2979
|
+
...providerSyncGroups.map((group) => h('label', { key: group.key,
|
|
2980
|
+
style: { display: 'flex', alignItems: 'center', gap: 9, padding: '5px 0', cursor: busy ? 'default' : 'pointer' } },
|
|
2981
|
+
h('input', { type: 'checkbox', 'aria-label': group.label,
|
|
2982
|
+
checked: !excludedGroupSet.has(group.key), disabled: busy
|
|
2983
|
+
|| (!group.legacy && !excludedGroupSet.has(group.key) && activePrimaryGroupCount <= 1),
|
|
2984
|
+
onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
|
|
2985
|
+
h('span', null, `${group.label} · ${group.modelCount} 个模型`)))) : null,
|
|
2829
2986
|
h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
2830
2987
|
modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
|
|
2831
2988
|
h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
|
|
@@ -2835,7 +2992,7 @@ window.__ModuleLoader__.load({
|
|
|
2835
2992
|
style: secondaryButtonStyle }, '前往团队连接') : null)),
|
|
2836
2993
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 10 } },
|
|
2837
2994
|
h('button', { type: 'button', disabled: busy || defaultModelBusy || !bindingReady || credentialConfigured !== true,
|
|
2838
|
-
onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2995
|
+
onClick: () => void refreshModels(true), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
2839
2996
|
lastModelSync ? h('span', { style: mutedTextStyle }, `最近同步:${lastModelSync.toLocaleString('zh-CN')}`) : null),
|
|
2840
2997
|
modelMessage && !modelMessage.startsWith('正在') ? h('p', { role: 'status', style: mutedTextStyle }, modelMessage) : null)
|
|
2841
2998
|
|
|
@@ -2869,12 +3026,12 @@ window.__ModuleLoader__.load({
|
|
|
2869
3026
|
h('strong', { style: { fontSize: 14, lineHeight: 1.4 } }, '本版更新'),
|
|
2870
3027
|
h('p', { style: mutedTextStyle }, bundleRelease.release_notes)) : null,
|
|
2871
3028
|
['incompatible', 'unknown'].includes(serviceCompatibility.state) ? h('p', { role: 'status', style: {
|
|
2872
|
-
...
|
|
3029
|
+
...feedbackNoticeStyle,
|
|
2873
3030
|
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)'
|
|
2874
3031
|
: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))',
|
|
2875
3032
|
} }, `服务端兼容性:${serviceCompatibility.message}`) : null,
|
|
2876
3033
|
bundleUpdate.message && updateStatusNeedsDetails.includes(bundleUpdate.state) ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
2877
|
-
? { ...
|
|
3034
|
+
? { ...feedbackNoticeStyle, color: 'var(--dsw-alias-state-error-primary)' } : feedbackNoticeStyle }, bundleUpdate.message) : null,
|
|
2878
3035
|
!canCheckUpdate && updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer',
|
|
2879
3036
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开更新指引 ↗') : null,
|
|
2880
3037
|
!canCheckUpdate && !updateGuideUrl ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|
|
@@ -2910,7 +3067,175 @@ window.__ModuleLoader__.load({
|
|
|
2910
3067
|
includePlugins: tab === 'all', ...navigation })
|
|
2911
3068
|
}
|
|
2912
3069
|
|
|
3070
|
+
function brandingCacheKey() {
|
|
3071
|
+
const serviceUrl = loadServiceUrl()
|
|
3072
|
+
return serviceUrl ? `${BRANDING_CACHE_PREFIX}${serviceUrl}` : null
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
function readBrandingCache() {
|
|
3076
|
+
const key = brandingCacheKey()
|
|
3077
|
+
if (!key) return null
|
|
3078
|
+
try {
|
|
3079
|
+
const value = JSON.parse(localStorage.getItem(key) || 'null')
|
|
3080
|
+
if (!value || !Number.isSafeInteger(value.revision) || value.revision < 0) return null
|
|
3081
|
+
return value
|
|
3082
|
+
} catch { return null }
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function normalizeBranding(value) {
|
|
3086
|
+
if (!value || !Number.isSafeInteger(value.revision) || value.revision < 0) return null
|
|
3087
|
+
const fields = ['wordmark', 'badge', 'headline']
|
|
3088
|
+
if (fields.some((field) => value[field] != null && typeof value[field] !== 'string')) return null
|
|
3089
|
+
if (value.logo_data_url != null && (typeof value.logo_data_url !== 'string'
|
|
3090
|
+
|| !/^data:image\/(?:png|jpeg|webp);base64,[A-Za-z0-9+/]+=*$/u.test(value.logo_data_url)
|
|
3091
|
+
|| value.logo_data_url.length > 512 * 1024)) return null
|
|
3092
|
+
return { revision: value.revision, logo_data_url: value.logo_data_url || null,
|
|
3093
|
+
wordmark: value.wordmark || null, badge: value.badge || null, headline: value.headline || null }
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
function installEnterpriseBranding(ctx) {
|
|
3097
|
+
const slotDisposers = new Map()
|
|
3098
|
+
const slotValues = new Map()
|
|
3099
|
+
const originalHeadlines = new Map()
|
|
3100
|
+
let headlineObserver = null
|
|
3101
|
+
let applied = null
|
|
3102
|
+
const isHeroHeadlineNode = (node) => {
|
|
3103
|
+
let container = node?.parentElement
|
|
3104
|
+
const compact = (value) => value?.replace(/\s+/gu, '') || ''
|
|
3105
|
+
const aliases = [node?.textContent?.trim(), originalHeadlines.get(node)?.original,
|
|
3106
|
+
originalHeadlines.get(node)?.current, '探索未至之境', 'Into the Unknown'].filter(Boolean).map(compact)
|
|
3107
|
+
for (let depth = 0; container && depth < 6; depth += 1, container = container.parentElement) {
|
|
3108
|
+
if (!container.querySelector) continue
|
|
3109
|
+
const badges = [...container.querySelectorAll('span, small, label')]
|
|
3110
|
+
.filter((element) => ['预览版', 'Preview'].includes(element.textContent?.trim()))
|
|
3111
|
+
const marks = container.querySelectorAll('svg, img')
|
|
3112
|
+
const wholeRow = compact(container.textContent)
|
|
3113
|
+
if (badges.length === 1 && marks.length === 1
|
|
3114
|
+
&& aliases.some((title) => ['预览版', 'Preview'].some((badge) =>
|
|
3115
|
+
wholeRow === `${title}${compact(badge)}` || wholeRow === `${compact(badge)}${title}`))) return true
|
|
3116
|
+
}
|
|
3117
|
+
return false
|
|
3118
|
+
}
|
|
3119
|
+
const replaceSlot = (name, value, component) => {
|
|
3120
|
+
if (slotValues.get(name) === value) return
|
|
3121
|
+
slotDisposers.get(name)?.()
|
|
3122
|
+
slotDisposers.delete(name); slotValues.delete(name)
|
|
3123
|
+
if (!value) return
|
|
3124
|
+
const dispose = ctx.slots.inject(name, () => ctx.slots.register({ name }, component))
|
|
3125
|
+
slotDisposers.set(name, dispose); slotValues.set(name, value)
|
|
3126
|
+
}
|
|
3127
|
+
const replaceHeadlineNode = (node) => {
|
|
3128
|
+
if (node?.nodeType !== Node.TEXT_NODE) return
|
|
3129
|
+
const text = node.textContent?.trim()
|
|
3130
|
+
const prior = originalHeadlines.get(node)
|
|
3131
|
+
const matchesHeadline = text === '探索未至之境' || text === 'Into the Unknown'
|
|
3132
|
+
|| text === prior?.original || text === prior?.current
|
|
3133
|
+
if (applied?.headline && text !== applied.headline && matchesHeadline && isHeroHeadlineNode(node)) {
|
|
3134
|
+
originalHeadlines.set(node, { original: prior?.original || text, current: applied.headline })
|
|
3135
|
+
node.textContent = applied.headline
|
|
3136
|
+
} else if (!applied?.headline && prior && text === prior.current) {
|
|
3137
|
+
node.textContent = prior.original
|
|
3138
|
+
originalHeadlines.delete(node)
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
const replaceHeadlineSubtree = (root) => {
|
|
3142
|
+
if (!root) return
|
|
3143
|
+
if (root.nodeType === Node.TEXT_NODE) { replaceHeadlineNode(root); return }
|
|
3144
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT)
|
|
3145
|
+
let node
|
|
3146
|
+
while ((node = walker.nextNode())) replaceHeadlineNode(node)
|
|
3147
|
+
}
|
|
3148
|
+
const updateHeadline = (records = null) => {
|
|
3149
|
+
if (typeof document === 'undefined' || !document.body) return
|
|
3150
|
+
if (!records) { replaceHeadlineSubtree(document.body); return }
|
|
3151
|
+
for (const record of records) {
|
|
3152
|
+
if (record.type === 'characterData') replaceHeadlineNode(record.target)
|
|
3153
|
+
else for (const node of record.addedNodes) replaceHeadlineSubtree(node)
|
|
3154
|
+
}
|
|
3155
|
+
for (const node of originalHeadlines.keys()) if (!node.isConnected) originalHeadlines.delete(node)
|
|
3156
|
+
}
|
|
3157
|
+
const apply = (value) => {
|
|
3158
|
+
const branding = normalizeBranding(value)
|
|
3159
|
+
const previousHeadline = applied?.headline
|
|
3160
|
+
applied = branding ? { ...branding } : null
|
|
3161
|
+
const logo = branding?.logo_data_url || null
|
|
3162
|
+
const nameValue = branding && (branding.wordmark || branding.badge)
|
|
3163
|
+
? `${branding.wordmark || ''}\u0000${branding.badge || ''}` : null
|
|
3164
|
+
const mark = ({ size = 24, className }) => logo
|
|
3165
|
+
? h('img', { src: logo, alt: '', className, draggable: false,
|
|
3166
|
+
style: { display: 'block', width: size, height: size, objectFit: 'contain' } }) : null
|
|
3167
|
+
const brandName = () => h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' } },
|
|
3168
|
+
h('span', { style: { color: 'var(--dsw-alias-brand-primary)', fontWeight: 650 } }, branding?.wordmark || 'DEEPSEEK'),
|
|
3169
|
+
h('span', { style: { padding: '2px 5px', borderRadius: 4, color: 'var(--dsw-alias-label-primary-inverted)',
|
|
3170
|
+
background: 'var(--dsw-alias-brand-primary)', fontSize: 10, fontWeight: 650, letterSpacing: '.04em' } }, branding?.badge || 'HARNESS'))
|
|
3171
|
+
replaceSlot('sidebar.brand.mark', logo, mark)
|
|
3172
|
+
replaceSlot('conversation.hero.brand.mark', logo, mark)
|
|
3173
|
+
replaceSlot('sidebar.brand.name', nameValue, brandName)
|
|
3174
|
+
if (branding?.headline && !headlineObserver && typeof document !== 'undefined' && document.body) {
|
|
3175
|
+
headlineObserver = new MutationObserver(updateHeadline)
|
|
3176
|
+
headlineObserver.observe(document.body, { childList: true, subtree: true, characterData: true })
|
|
3177
|
+
}
|
|
3178
|
+
updateHeadline()
|
|
3179
|
+
}
|
|
3180
|
+
apply(readBrandingCache())
|
|
3181
|
+
let etag = applied?.revision ? `"${applied.revision}"` : null
|
|
3182
|
+
let retryCount = 0
|
|
3183
|
+
let retryTimer = null
|
|
3184
|
+
let disposed = false
|
|
3185
|
+
const refresh = async () => {
|
|
3186
|
+
if (disposed) return
|
|
3187
|
+
let retryable = true
|
|
3188
|
+
try {
|
|
3189
|
+
if (!loadServiceUrl()) {
|
|
3190
|
+
const originResponse = await fetch('/api/ant-teams/connection-origin', { cache: 'no-store' })
|
|
3191
|
+
if (disposed) return
|
|
3192
|
+
if (!originResponse.ok) return
|
|
3193
|
+
const originInfo = await readLocalJsonResponse(originResponse)
|
|
3194
|
+
if (disposed) return
|
|
3195
|
+
const origin = normalizeServiceUrl(originInfo?.service_url || '')
|
|
3196
|
+
if (!origin) return
|
|
3197
|
+
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, origin) } catch {}
|
|
3198
|
+
}
|
|
3199
|
+
const response = await fetch(BRANDING_SYNC_PATH, { cache: 'no-store',
|
|
3200
|
+
headers: etag ? { 'if-none-match': etag } : {} })
|
|
3201
|
+
if (disposed) return
|
|
3202
|
+
if (response.status === 304) { retryCount = 0; return }
|
|
3203
|
+
if (!response.ok) {
|
|
3204
|
+
retryable = [404, 426, 502, 503].includes(response.status)
|
|
3205
|
+
throw new Error('branding sync unavailable')
|
|
3206
|
+
}
|
|
3207
|
+
const branding = normalizeBranding(await readLocalJsonResponse(response))
|
|
3208
|
+
if (disposed || !branding) return
|
|
3209
|
+
etag = response.headers.get('etag') || `"${branding.revision}"`
|
|
3210
|
+
const key = brandingCacheKey()
|
|
3211
|
+
if (key) {
|
|
3212
|
+
try { localStorage.setItem(key, JSON.stringify(branding)) } catch {}
|
|
3213
|
+
}
|
|
3214
|
+
apply(branding)
|
|
3215
|
+
retryCount = 0
|
|
3216
|
+
} catch {
|
|
3217
|
+
if (!disposed && retryable && retryCount < 3) {
|
|
3218
|
+
const delay = [3_000, 10_000, 30_000][retryCount]
|
|
3219
|
+
retryCount += 1
|
|
3220
|
+
clearTimeout(retryTimer)
|
|
3221
|
+
retryTimer = setTimeout(() => void refresh(), delay)
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
void refresh()
|
|
3226
|
+
const timer = setInterval(() => void refresh(), 5 * 60 * 1000)
|
|
3227
|
+
return () => {
|
|
3228
|
+
disposed = true
|
|
3229
|
+
clearInterval(timer); clearTimeout(retryTimer); headlineObserver?.disconnect()
|
|
3230
|
+
for (const [node, prior] of originalHeadlines) {
|
|
3231
|
+
if (node.textContent?.trim() === prior.current) node.textContent = prior.original
|
|
3232
|
+
}
|
|
3233
|
+
for (const dispose of slotDisposers.values()) dispose?.()
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
|
|
2913
3237
|
function apply(ctx) {
|
|
3238
|
+
ctx.effect(() => installEnterpriseBranding(ctx), 'ANT enterprise branding synchronization')
|
|
2914
3239
|
const remote = {
|
|
2915
3240
|
credentials: ctx.remote.credentials,
|
|
2916
3241
|
settings: ctx.remote.settings,
|
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { readLocalDelegationSettings, recentDelegationStatus, startDelegationHos
|
|
|
10
10
|
export const inject = []
|
|
11
11
|
|
|
12
12
|
const MODEL_SYNC_PATH = '/api/ant-teams/models'
|
|
13
|
+
const BRANDING_PATH = '/api/ant-teams/branding'
|
|
13
14
|
const SERVICE_INFO_PATH = '/api/ant-teams/service-info'
|
|
14
15
|
const BUNDLE_RELEASE_PATH = '/api/ant-teams/bundle-release'
|
|
15
16
|
const CONNECTION_ORIGIN_PATH = '/api/ant-teams/connection-origin'
|
|
@@ -842,6 +843,38 @@ async function fetchMemberPlugins(request, credentials, settings) {
|
|
|
842
843
|
}
|
|
843
844
|
}
|
|
844
845
|
|
|
846
|
+
async function fetchMemberBranding(request, credentials, settings) {
|
|
847
|
+
if (request.method !== 'GET') return jsonResponse(405, { message: '只支持 GET 请求。' })
|
|
848
|
+
try {
|
|
849
|
+
const connection = await readMemberConnection(credentials, settings)
|
|
850
|
+
if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
|
|
851
|
+
const etag = request.headers.get('if-none-match')
|
|
852
|
+
const upstream = await fetch(new URL('/api/me/branding', connection.serviceUrl), {
|
|
853
|
+
headers: { authorization: `Bearer ${connection.token}`, accept: 'application/json',
|
|
854
|
+
...(etag ? { 'if-none-match': etag } : {}) },
|
|
855
|
+
redirect: 'manual', signal: AbortSignal.timeout(10_000),
|
|
856
|
+
})
|
|
857
|
+
if (upstream.status === 304) {
|
|
858
|
+
return new Response(null, { status: 304, headers: { etag: upstream.headers.get('etag') || etag || '' } })
|
|
859
|
+
}
|
|
860
|
+
if (!upstream.ok) return jsonResponse(upstream.status === 401 || upstream.status === 403 ? 401 : 502,
|
|
861
|
+
{ message: '暂时无法读取企业品牌设置。' })
|
|
862
|
+
const text = await readLimitedBody(upstream, 600 * 1024)
|
|
863
|
+
if (text === null) return jsonResponse(502, { message: '企业品牌设置响应过大。' })
|
|
864
|
+
const branding = JSON.parse(text)
|
|
865
|
+
if (!Number.isSafeInteger(branding?.revision) || branding.revision < 0
|
|
866
|
+
|| ![null, undefined].includes(branding.logo_data_url) && typeof branding.logo_data_url !== 'string'
|
|
867
|
+
|| ![null, undefined].includes(branding.wordmark) && typeof branding.wordmark !== 'string'
|
|
868
|
+
|| ![null, undefined].includes(branding.badge) && typeof branding.badge !== 'string'
|
|
869
|
+
|| ![null, undefined].includes(branding.headline) && typeof branding.headline !== 'string') {
|
|
870
|
+
return jsonResponse(502, { message: '企业品牌设置格式无效。' })
|
|
871
|
+
}
|
|
872
|
+
return new Response(text, { status: 200,
|
|
873
|
+
headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store',
|
|
874
|
+
...(upstream.headers.get('etag') ? { etag: upstream.headers.get('etag') } : {}) } })
|
|
875
|
+
} catch { return jsonResponse(502, { message: '暂时无法读取企业品牌设置。' }) }
|
|
876
|
+
}
|
|
877
|
+
|
|
845
878
|
async function fetchAuthRequest(request, endpoint, credentials, settings) {
|
|
846
879
|
if (request.method !== 'POST') return jsonResponse(405, { message: '只支持 POST 请求' })
|
|
847
880
|
let body
|
|
@@ -941,6 +974,10 @@ export function apply(ctx) {
|
|
|
941
974
|
requestBody: 'buffered',
|
|
942
975
|
fetch: (request) => fetchMemberModels(request, connectionCtx.credentials, connectionCtx.settings),
|
|
943
976
|
}), 'ant-teams: authenticated model catalog relay')
|
|
977
|
+
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
978
|
+
path: BRANDING_PATH, methods: ['GET'], requestBody: 'buffered',
|
|
979
|
+
fetch: (request) => fetchMemberBranding(request, connectionCtx.credentials, connectionCtx.settings),
|
|
980
|
+
}), 'ant-teams: conditional enterprise branding relay')
|
|
944
981
|
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
945
982
|
path: BUNDLE_RELEASE_PATH,
|
|
946
983
|
methods: ['GET'],
|