@ai-wendao/ant 0.5.0 → 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 +6 -0
- package/client.js +170 -0
- package/index.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,12 @@ ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to its team's
|
|
|
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
|
## 协作机器人与任务卡
|
package/client.js
CHANGED
|
@@ -12,6 +12,8 @@ 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'
|
|
16
18
|
const PROVIDER_SYNC_EXCLUSIONS_KEY = 'ant-team-provider-sync-exclusions'
|
|
17
19
|
const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
|
|
@@ -3065,7 +3067,175 @@ window.__ModuleLoader__.load({
|
|
|
3065
3067
|
includePlugins: tab === 'all', ...navigation })
|
|
3066
3068
|
}
|
|
3067
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
|
+
|
|
3068
3237
|
function apply(ctx) {
|
|
3238
|
+
ctx.effect(() => installEnterpriseBranding(ctx), 'ANT enterprise branding synchronization')
|
|
3069
3239
|
const remote = {
|
|
3070
3240
|
credentials: ctx.remote.credentials,
|
|
3071
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'],
|