@ai-wendao/ant 0.3.14 → 0.3.15

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.
Files changed (2) hide show
  1. package/client.js +141 -51
  2. package/package.json +1 -1
package/client.js CHANGED
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} }
5
5
  var exports = module.exports
6
- const { createElement, useEffect, useState } = require('react')
6
+ const { createElement, useEffect, useRef, useState } = require('react')
7
7
  const h = createElement
8
8
  const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
9
9
  const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
@@ -34,14 +34,15 @@ window.__ModuleLoader__.load({
34
34
  border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 8,
35
35
  cursor: 'pointer',
36
36
  }
37
+ const categoryButtonStyle = { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', whiteSpace: 'nowrap' }
37
38
  function ExtensionRequestNotice() {
38
39
  return h('aside', { role: 'note', style: {
39
- ...panelStyle,
40
- borderColor: 'var(--dsw-alias-state-business-primary)',
41
- gap: 5,
40
+ display: 'flex', alignItems: 'baseline', gap: 8, padding: '9px 12px',
41
+ borderRadius: 8, backgroundColor: 'var(--dsw-alias-bg-layer-2)',
42
+ color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6,
42
43
  } },
43
- h('h2', { style: { margin: 0, fontSize: 16 } }, '没有找到想用的技能或插件?'),
44
- h('p', { style: mutedTextStyle }, '目录里没有想用的扩展,或插件没有安装按钮?请联系企业管理员,说明你希望完成的任务或提供扩展地址。已开放安装的技能和插件可在这里直接安装;勿自行安装未经审核的扩展。'))
44
+ h('strong', { style: { flexShrink: 0, color: 'var(--dsw-alias-label-primary)' } }, '提示'),
45
+ h('span', null, '没有找到想用的扩展,或插件没有安装按钮?联系企业管理员,说明用途或提供地址。已开放的扩展可直接安装;未经审核的版本请勿自行安装。'))
45
46
  }
46
47
  const normalizeServiceUrl = (value) => {
47
48
  let url
@@ -149,8 +150,38 @@ window.__ModuleLoader__.load({
149
150
  function TeamServiceConfig({ remote, openInstalledPlugin }) {
150
151
  const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
151
152
  const [draft, setDraft] = useState(serviceUrl)
153
+ const [binding, setBinding] = useState({ state: 'checking', url: null })
154
+ const bindingRevision = useRef(0)
152
155
  const [message, setMessage] = useState('')
153
156
  const [pluginAlerts, setPluginAlerts] = useState([])
157
+ useEffect(() => {
158
+ let disposed = false
159
+ const revision = bindingRevision.current
160
+ const current = () => !disposed && bindingRevision.current === revision
161
+ const inspectBinding = async () => {
162
+ try {
163
+ const response = await fetch('/api/ant-teams/connection-origin', { cache: 'no-store' })
164
+ const result = await readLocalJsonResponse(response)
165
+ if (!response.ok) throw new Error()
166
+ const pinned = result.service_url == null ? null : normalizeServiceUrl(result.service_url)
167
+ if (result.service_url != null && !pinned) throw new Error()
168
+ if (pinned) {
169
+ if (current()) {
170
+ setBinding({ state: 'ready', url: pinned })
171
+ setServiceUrl(pinned)
172
+ setDraft(pinned)
173
+ try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, pinned) } catch {}
174
+ }
175
+ return
176
+ }
177
+ const described = await remote?.credentials?.describe?.(['ANT_MODEL_API_KEY'])
178
+ if (!described?.ok) throw new Error()
179
+ if (current()) setBinding({ state: described.value?.ANT_MODEL_API_KEY?.configured ? 'unverified' : 'ready', url: null })
180
+ } catch { if (current()) setBinding({ state: 'error', url: null }) }
181
+ }
182
+ void inspectBinding()
183
+ return () => { disposed = true }
184
+ }, [remote])
154
185
  useEffect(() => {
155
186
  let disposed = false
156
187
  const inspect = async () => {
@@ -175,6 +206,10 @@ window.__ModuleLoader__.load({
175
206
  }, [remote])
176
207
  const save = (event) => {
177
208
  event.preventDefault()
209
+ if (binding.state !== 'ready' || binding.url) {
210
+ setMessage(binding.url ? '当前已连接团队;切换前请先在下方断开当前设备。' : '尚未核对这台 DSH 的团队绑定,不能修改地址。')
211
+ return
212
+ }
178
213
  const normalized = normalizeServiceUrl(draft)
179
214
  if (!normalized) {
180
215
  setMessage('请输入 HTTPS 团队服务地址;本机测试可使用 localhost。')
@@ -189,25 +224,39 @@ window.__ModuleLoader__.load({
189
224
  setMessage('浏览器无法保存此地址,请检查本地存储权限。')
190
225
  }
191
226
  }
192
- const link = consoleUrl(serviceUrl)
227
+ const link = binding.state === 'ready' ? consoleUrl(binding.url || serviceUrl) : null
228
+ const onConnectionChange = (url) => {
229
+ bindingRevision.current += 1
230
+ setBinding({ state: 'ready', url: url || null })
231
+ if (url) {
232
+ setServiceUrl(url)
233
+ setDraft(url)
234
+ try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, url) } catch {}
235
+ }
236
+ }
193
237
  return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
194
238
  h('p', { style: mutedTextStyle }, '连接团队服务后,即可同步获准模型并使用团队协作。'),
195
239
  h('section', { style: panelStyle },
196
240
  h('div', null,
197
241
  h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '团队服务'),
198
242
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
199
- h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
200
- h('label', { htmlFor: 'ant-team-service-url', style: { gridColumn: '1 / -1', fontSize: 13, fontWeight: 550 } }, '服务地址'),
243
+ h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: binding.url ? '1fr' : 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
244
+ h('label', { htmlFor: 'ant-team-service-url', style: { gridColumn: '1 / -1', fontSize: 13, fontWeight: 550 } }, binding.url ? '当前连接的团队服务地址' : '服务地址'),
201
245
  h('input', {
202
246
  id: 'ant-team-service-url',
203
247
  type: 'url',
204
248
  required: true,
205
- value: draft,
249
+ value: binding.url || draft,
250
+ readOnly: binding.state !== 'ready' || Boolean(binding.url),
206
251
  placeholder: 'https://ant.example.com',
207
- onChange: (event) => setDraft(event.target.value),
252
+ onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) },
208
253
  style: inputStyle,
209
254
  }),
210
- h('button', { type: 'submit', style: primaryButtonStyle }, '保存地址')),
255
+ binding.url ? null : h('button', { type: 'submit', disabled: binding.state !== 'ready', style: primaryButtonStyle }, '保存地址')),
256
+ binding.url ? h('p', { style: mutedTextStyle }, '若要切换团队,请先在下方断开当前设备,再填写新地址并验证邮箱。') : null,
257
+ binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
258
+ binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
259
+ binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
211
260
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
212
261
  message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
213
262
  link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)),
@@ -217,7 +266,8 @@ window.__ModuleLoader__.load({
217
266
  `${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
218
267
  ...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
219
268
  onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
220
- h(ModelSyncFooter, { remote, serviceUrl, onServiceUrlChange: setServiceUrl }))
269
+ h(ModelSyncFooter, { remote, serviceUrl, bindingReady: binding.state === 'ready',
270
+ onServiceUrlChange: setServiceUrl, onConnectionChange }))
221
271
  }
222
272
 
223
273
  function SkillCenterPage({ onBack, onPlugins }) {
@@ -276,9 +326,11 @@ window.__ModuleLoader__.load({
276
326
  height: '100%', overflowY: 'auto', boxSizing: 'border-box',
277
327
  padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
278
328
  } }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
279
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
280
- h('button', { type: 'button', onClick: onBack, style: secondaryButtonStyle }, '← 返回 ANT 插件设置'),
281
- h('button', { type: 'button', onClick: onPlugins, style: secondaryButtonStyle }, '查看企业精选插件 →')),
329
+ h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
330
+ h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
331
+ h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '技能'),
332
+ h('button', { type: 'button', onClick: onPlugins, style: categoryButtonStyle }, '插件')),
333
+ h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
282
334
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'end', justifyContent: 'space-between', gap: 16 } },
283
335
  h('div', null,
284
336
  h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选技能'),
@@ -309,9 +361,9 @@ window.__ModuleLoader__.load({
309
361
  skill.local_status === 'conflict'
310
362
  ? h('p', { role: 'status', style: mutedTextStyle }, '本机同名技能有未受 ANT 管理的内容、文件已被修改或备份损坏;请联系管理员处理。')
311
363
  : h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 'auto' } },
312
- h('button', { type: 'button', disabled: Boolean(workingId) || skill.local_status === 'installed',
364
+ skill.local_status === 'installed' ? null : h('button', { type: 'button', disabled: Boolean(workingId),
313
365
  onClick: () => void changeInstall(skill), style: primaryButtonStyle },
314
- skill.local_status === 'installed' ? '文件已安装' : skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
366
+ skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
315
367
  skill.local_status !== 'not-installed' ? h('button', { type: 'button', disabled: Boolean(workingId),
316
368
  onClick: () => void changeInstall(skill, true), style: secondaryButtonStyle }, '卸载') : null))
317
369
  })) : !loading && skills.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null))
@@ -320,7 +372,8 @@ window.__ModuleLoader__.load({
320
372
  function PluginCenterPage({ remote, onBack, onSkills, openBundle }) {
321
373
  const [plugins, setPlugins] = useState([])
322
374
  const [bundles, setBundles] = useState([])
323
- const [loading, setLoading] = useState(false)
375
+ const [loading, setLoading] = useState(true)
376
+ const [loadError, setLoadError] = useState(false)
324
377
  const [workingId, setWorkingId] = useState('')
325
378
  const [uncertainId, setUncertainId] = useState('')
326
379
  const [completedId, setCompletedId] = useState('')
@@ -341,10 +394,12 @@ window.__ModuleLoader__.load({
341
394
  if (!listed.ok) throw new Error(listed.error?.message || '无法读取 DSH 本机插件。')
342
395
  setPlugins(result.plugins)
343
396
  setBundles(listed.value)
397
+ setLoadError(false)
344
398
  setMessage(result.plugins.length ? '' : '团队暂未发布精选插件。')
345
399
  } catch (error) {
346
400
  setPlugins([])
347
401
  setBundles([])
402
+ setLoadError(true)
348
403
  setMessage(error instanceof Error ? error.message : '无法读取企业精选插件。')
349
404
  } finally { setLoading(false) }
350
405
  }
@@ -437,7 +492,7 @@ window.__ModuleLoader__.load({
437
492
  const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
438
493
  const artifactUrl = approvedPluginArtifactUrl(plugin)
439
494
  const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
440
- return matches && (filter === 'all' || filter === 'installed' && installed?.version === plugin.version
495
+ return matches && (filter === 'all' || filter === 'installed' && Boolean(installed)
441
496
  || filter === 'available' && artifactUrl && (!installed || (installed.version && installed.version !== plugin.version))
442
497
  || filter === 'retired' && plugin.status !== 'published')
443
498
  })
@@ -445,25 +500,28 @@ window.__ModuleLoader__.load({
445
500
  height: '100%', overflowY: 'auto', boxSizing: 'border-box',
446
501
  padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
447
502
  } }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
448
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
449
- h('button', { type: 'button', onClick: onBack, style: secondaryButtonStyle }, '← 返回 ANT 插件设置'),
450
- h('button', { type: 'button', onClick: onSkills, style: secondaryButtonStyle }, '查看企业精选技能 →')),
503
+ h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
504
+ h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
505
+ h('button', { type: 'button', onClick: onSkills, style: categoryButtonStyle }, '技能'),
506
+ h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '插件')),
507
+ h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
451
508
  h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 } },
452
509
  h('div', null,
453
510
  h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选插件'),
454
511
  h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。')),
455
512
  h('button', { type: 'button', disabled: loading, onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
456
- h('aside', { role: 'note', style: { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } },
457
- h('strong', null, '只安装团队锁定的固定版本'),
458
- h('p', { style: mutedTextStyle }, '显示“安装”按钮的版本已锁定实际安装包并由管理员发布;其他目录项仅供查看,如需使用请联系企业管理员。')),
459
- h(ExtensionRequestNotice),
460
- h('div', { style: panelStyle },
513
+ !loading && !plugins.length && !loadError ? h('section', { role: 'status', style: panelStyle },
514
+ h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
515
+ h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
516
+ !loading && loadError ? h('p', { role: 'alert', style: mutedTextStyle }, message) : null,
517
+ plugins.length ? h(ExtensionRequestNotice) : null,
518
+ plugins.length ? h('div', { style: panelStyle },
461
519
  h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
462
520
  h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
463
521
  ...[['all', '全部'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
464
522
  h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
465
- style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))),
466
- message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
523
+ style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))) : null,
524
+ plugins.length && message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
467
525
  visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
468
526
  ...visible.map((plugin) => {
469
527
  const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
@@ -515,7 +573,7 @@ window.__ModuleLoader__.load({
515
573
  : '没有符合筛选条件的插件。') : null))
516
574
  }
517
575
 
518
- function ModelSyncFooter({ remote, serviceUrl, onServiceUrlChange }) {
576
+ function ModelSyncFooter({ remote, serviceUrl, bindingReady, onServiceUrlChange, onConnectionChange }) {
519
577
  const [email, setEmail] = useState('')
520
578
  const [code, setCode] = useState('')
521
579
  const [message, setMessage] = useState('')
@@ -540,13 +598,13 @@ window.__ModuleLoader__.load({
540
598
  : `${message} 本机无法确认旧设备编号;请让原团队管理员按成员查看设备清单,勿用新编号猜测。`
541
599
  }
542
600
 
543
- const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false) => {
601
+ const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false, isCurrent = () => true) => {
544
602
  const normalized = normalizeServiceUrl(rawServiceUrl)
545
603
  if (!normalized && !useSavedConnection) {
546
- setServiceCompatibility({ state: 'idle', message: '' })
604
+ if (isCurrent()) setServiceCompatibility({ state: 'idle', message: '' })
547
605
  return null
548
606
  }
549
- setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
607
+ if (isCurrent()) setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
550
608
  try {
551
609
  const manager = remote?.pluginManager
552
610
  if (typeof manager?.listBundles !== 'function') throw new Error('当前 DSH 未提供 Bundle 版本读取接口。')
@@ -577,14 +635,14 @@ window.__ModuleLoader__.load({
577
635
  : `已识别 ANT Bundle v${result.bundle_version} 和服务端 v${result.server_version};服务端未提供协议能力,兼容性未知,仍允许继续。`
578
636
  const state = compatibility === true ? 'compatible' : compatibility === false ? 'incompatible' : 'unknown'
579
637
  const status = { state, message, ...result }
580
- setServiceCompatibility(status)
638
+ if (isCurrent()) setServiceCompatibility(status)
581
639
  return status
582
640
  } catch (error) {
583
641
  const status = {
584
642
  state: 'unknown',
585
643
  message: `无法确认 ANT 版本兼容性,仍允许继续:${error instanceof Error ? error.message : '连接检查失败。'}`,
586
644
  }
587
- setServiceCompatibility(status)
645
+ if (isCurrent()) setServiceCompatibility(status)
588
646
  return status
589
647
  }
590
648
  }
@@ -629,6 +687,10 @@ window.__ModuleLoader__.load({
629
687
  }
630
688
 
631
689
  const refreshModels = async () => {
690
+ if (!bindingReady) {
691
+ setMessage('请先等待团队绑定检查完成,再刷新团队模型。')
692
+ return
693
+ }
632
694
  setBusy(true)
633
695
  setMessage('正在从 ANT 刷新获准模型…')
634
696
  try {
@@ -806,6 +868,7 @@ window.__ModuleLoader__.load({
806
868
  const credentialResult = await remote.credentials.unset('ANT_MODEL_API_KEY')
807
869
  if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
808
870
  setCredentialConfigured(false)
871
+ onConnectionChange?.(null)
809
872
  setNeedsReconnect(false)
810
873
  setReauthenticate(false)
811
874
  setAllowLocalCleanup(false)
@@ -857,13 +920,13 @@ window.__ModuleLoader__.load({
857
920
  if (!current) return
858
921
  const configured = result.ok ? Boolean(result.value?.ANT_MODEL_API_KEY?.configured) : null
859
922
  setCredentialConfigured(configured)
860
- if (configured) {
923
+ if (configured && bindingReady) {
861
924
  void refreshModels()
862
925
  void checkBundleUpdate()
863
926
  }
864
927
  }).catch(() => { if (current) setCredentialConfigured(null) })
865
928
  return () => { current = false }
866
- }, [remote])
929
+ }, [remote, bindingReady])
867
930
 
868
931
  useEffect(() => {
869
932
  let current = true
@@ -883,11 +946,21 @@ window.__ModuleLoader__.load({
883
946
  }, [remote, credentialConfigured])
884
947
 
885
948
  useEffect(() => {
886
- void inspectServiceCompatibility(serviceUrl, credentialConfigured === true)
887
- }, [remote, serviceUrl, credentialConfigured])
949
+ if (!bindingReady) {
950
+ setServiceCompatibility({ state: 'idle', message: '' })
951
+ return
952
+ }
953
+ let current = true
954
+ void inspectServiceCompatibility(serviceUrl, credentialConfigured === true, () => current)
955
+ return () => { current = false }
956
+ }, [remote, serviceUrl, credentialConfigured, bindingReady])
888
957
 
889
958
  const requestCode = async (event) => {
890
959
  event.preventDefault()
960
+ if (!bindingReady) {
961
+ setMessage('请先等待团队绑定检查完成,再验证工作邮箱。')
962
+ return
963
+ }
891
964
  const normalized = normalizeServiceUrl(serviceUrl)
892
965
  const normalizedEmail = email.trim()
893
966
  if (!normalized) {
@@ -929,6 +1002,10 @@ window.__ModuleLoader__.load({
929
1002
 
930
1003
  const connect = async (event) => {
931
1004
  event.preventDefault()
1005
+ if (!bindingReady) {
1006
+ setMessage('请先等待团队绑定检查完成,再连接团队。')
1007
+ return
1008
+ }
932
1009
  const normalized = normalizeServiceUrl(serviceUrl)
933
1010
  const normalizedEmail = email.trim()
934
1011
  const normalizedCode = code.trim()
@@ -945,6 +1022,7 @@ window.__ModuleLoader__.load({
945
1022
  }
946
1023
  setBusy(true)
947
1024
  setMessage('正在验证邮箱并同步团队模型…')
1025
+ let credentialSaved = false
948
1026
  try {
949
1027
  await requireCompatibleService(normalized)
950
1028
  localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
@@ -968,24 +1046,34 @@ window.__ModuleLoader__.load({
968
1046
  }
969
1047
  const credentialWrite = await remote.credentials.set('ANT_MODEL_API_KEY', result.token)
970
1048
  if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
1049
+ credentialSaved = true
971
1050
  await applyAuthorizedModels(result.models, normalized)
972
1051
  setCode('')
973
1052
  onServiceUrlChange(normalized)
974
1053
  setCodeRequested(false)
975
1054
  setCredentialConfigured(true)
1055
+ onConnectionChange?.(normalized)
976
1056
  setAllowLocalCleanup(false)
977
1057
  setNeedsReconnect(false)
978
1058
  setReauthenticate(false)
979
1059
  setMessage(`已连接 ${result.email || normalizedEmail},同步 ${result.models.length} 个获准模型。它们现已出现在 DSH 模型选择器中。`)
980
1060
  } catch (error) {
981
- setMessage(error instanceof Error ? error.message : '连接失败,请稍后重试。')
1061
+ if (credentialSaved) {
1062
+ setCredentialConfigured(true)
1063
+ setCode('')
1064
+ setCodeRequested(false)
1065
+ onConnectionChange?.(normalized)
1066
+ setMessage(`ANT 凭证已保存,但团队模型未能同步:${error instanceof Error ? error.message : '请稍后重试。'} 请点击“刷新团队模型”重试。`)
1067
+ } else {
1068
+ setMessage(error instanceof Error ? error.message : '连接失败,请稍后重试。')
1069
+ }
982
1070
  } finally {
983
1071
  setBusy(false)
984
1072
  }
985
1073
  }
986
1074
 
987
1075
  const showConnectForm = credentialConfigured !== true || reauthenticate || needsReconnect
988
- const canCheckUpdate = credentialConfigured === true && !needsReconnect
1076
+ const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
989
1077
  const updateGuideUrl = pinnedServiceUrl
990
1078
  ? new URL('/ant-update', pinnedServiceUrl).href : null
991
1079
  return h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 400px), 1fr))', alignItems: 'start', gap: 16 } },
@@ -996,11 +1084,13 @@ window.__ModuleLoader__.load({
996
1084
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '使用上方团队服务地址验证工作邮箱并同步授权模型。连接状态变化时可随时刷新。')),
997
1085
  h('span', { style: {
998
1086
  flexShrink: 0, padding: '4px 9px', borderRadius: 999,
999
- color: credentialConfigured === true ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
1000
- backgroundColor: credentialConfigured === true ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
1087
+ color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
1088
+ backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
1001
1089
  fontSize: 12,
1002
- } }, credentialConfigured === true ? '已连接' : credentialConfigured === false ? '未连接' : '检查中')),
1003
- h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true
1090
+ } }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
1091
+ h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true && !bindingReady
1092
+ ? '本机有 ANT 凭证,但尚未确认它绑定的团队;请先完成上方的绑定检查。'
1093
+ : credentialConfigured === true
1004
1094
  ? 'DSH 已安全保存此设备的 ANT 团队凭证。'
1005
1095
  : credentialConfigured === false ? '验证工作邮箱后即可连接团队模型。' : '正在检查 ANT 连接状态…'),
1006
1096
  serviceCompatibility.state !== 'idle' ? h('p', {
@@ -1011,10 +1101,10 @@ window.__ModuleLoader__.load({
1011
1101
  },
1012
1102
  }, serviceCompatibility.message) : null,
1013
1103
  credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
1014
- h('button', { type: 'button', disabled: busy, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
1015
- h('button', { type: 'button', disabled: busy, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
1104
+ h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
1105
+ h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
1016
1106
  h('button', { type: 'button', disabled: busy, onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() }, style: secondaryButtonStyle }, '断开当前设备')) : null,
1017
- h('section', { style: { ...panelStyle, padding: 14, gap: 10 } },
1107
+ h('section', { style: { display: 'grid', gap: 8, paddingTop: 12, borderTop: '0.5px solid var(--dsw-alias-border-l2)' } },
1018
1108
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },
1019
1109
  h('h4', { style: { margin: 0, fontSize: 14, fontWeight: 650 } }, 'ANT 插件更新'),
1020
1110
  h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
@@ -1034,7 +1124,7 @@ window.__ModuleLoader__.load({
1034
1124
  id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
1035
1125
  onChange: (event) => setEmail(event.target.value), style: inputStyle,
1036
1126
  }),
1037
- h('button', { type: 'submit', disabled: busy, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
1127
+ h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
1038
1128
  credentialConfigured === true && showConnectForm ? h('button', {
1039
1129
  type: 'button', disabled: busy,
1040
1130
  onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
@@ -1047,7 +1137,7 @@ window.__ModuleLoader__.load({
1047
1137
  required: true, maxLength: 8, value: code, placeholder: '8 位验证码',
1048
1138
  onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle,
1049
1139
  }),
1050
- h('button', { type: 'submit', disabled: busy || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
1140
+ h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
1051
1141
  allowLocalCleanup ? h('section', { style: { ...panelStyle, padding: 14, gap: 9 } },
1052
1142
  h('p', { style: mutedTextStyle }, savedDeviceId()
1053
1143
  ? `已保存的设备编号:${savedDeviceId()}。请交给原团队管理员核对。`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "description": "ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to team identity and approved models.",
5
5
  "type": "module",
6
6
  "scripts": {