@moguiyu/dsh-tavily 0.1.4 → 0.1.6

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/lib/client.js CHANGED
@@ -64,16 +64,15 @@ window.__ModuleLoader__.load({
64
64
  }, react.createElement('span', { style: { position: 'absolute', top: 2, left: checked === true ? 18 : 2, width: 14, height: 14, borderRadius: 7, background: '#fff', transition: 'left .12s ease' } }))
65
65
  }
66
66
 
67
- function UsageCircle({ percent, size, label }) {
67
+ function UsageCircle({ percent, size, label, onClick }) {
68
68
  const radius = 15
69
69
  const circumference = 2 * Math.PI * radius
70
70
  const pct = percent != null ? Math.min(100, Math.max(0, percent)) : 0
71
- const warn = pct > 85
72
71
  const dim = size || 44
73
- return react.createElement('div', { style: { position: 'relative', width: dim, height: dim, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } },
72
+ return react.createElement('button', { type: 'button', title: 'Reload usage', 'aria-label': 'Reload usage', onClick: onClick, style: { position: 'relative', width: dim, height: dim, border: 'none', background: 'transparent', padding: 0, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } },
74
73
  react.createElement('svg', { width: dim, height: dim, viewBox: '0 0 40 40' },
75
74
  react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: 'var(--dsw-alias-border-l2)', strokeWidth: 4 }),
76
- react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: warn ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-brand-primary)', strokeWidth: 4, strokeLinecap: 'round', strokeDasharray: circumference, strokeDashoffset: circumference - (pct / 100) * circumference, transform: 'rotate(-90 20 20)' })
75
+ react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: 'var(--dsw-alias-state-success-primary)', strokeWidth: 4, strokeLinecap: 'round', strokeDasharray: circumference, strokeDashoffset: circumference - (pct / 100) * circumference, transform: 'rotate(-90 20 20)' })
77
76
  ),
78
77
  react.createElement('span', { style: { position: 'absolute', fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } }, label != null ? label : (percent != null ? percent + '%' : '—'))
79
78
  )
@@ -144,10 +143,6 @@ window.__ModuleLoader__.load({
144
143
  }
145
144
  }
146
145
 
147
- const hasPending = Object.values(removing).some(Boolean)
148
- || Object.values(replacing).some(Boolean)
149
- || adds.some((item) => item.value.trim().length > 0)
150
-
151
146
  const saveStrategy = async (next) => {
152
147
  setBusy(true)
153
148
  setNotice(null)
@@ -191,29 +186,46 @@ window.__ModuleLoader__.load({
191
186
  }
192
187
  }
193
188
 
194
- const save = async () => {
189
+ const saveRemove = async (masked) => {
195
190
  setBusy(true)
196
191
  setNotice(null)
197
192
  try {
198
- const addValues = adds.map((item) => item.value.trim()).filter((value) => value.length > 0)
199
- const removeMasked = [
200
- ...Object.keys(removing).filter((masked) => removing[masked] === true),
201
- ...Object.keys(replacing).filter((masked) => replacing[masked] === true)
202
- ]
203
193
  const response = await fetch('/api/tavily-manager', {
204
194
  method: 'POST',
205
195
  headers: { 'content-type': 'application/json' },
206
- body: JSON.stringify({ add: addValues, remove: removeMasked, strategy })
196
+ body: JSON.stringify({ add: [], remove: [masked], strategy })
207
197
  })
208
198
  const data = await response.json()
209
- if (!data.ok) { setNotice({ error: data.error || 'Save failed' }); return }
210
- setNotice({ ok: 'Saved — effective on the next search.' })
211
- setRemoving({})
212
- setReplacing({})
213
- setReplaceDrafts({})
214
- setAdds([])
215
- setRevealed({})
216
- setConfirm({})
199
+ if (!data.ok) { setNotice({ error: data.error || 'Delete failed' }); return }
200
+ setNotice({ ok: 'Key removed.' })
201
+ setConfirm((current) => Object.assign({}, current, { [masked]: false }))
202
+ setRemoving((current) => Object.assign({}, current, { [masked]: false }))
203
+ setRevealed((current) => Object.assign({}, current, { [masked]: false }))
204
+ await refresh()
205
+ } catch (error) {
206
+ setNotice({ error: String(error && error.message ? error.message : error) })
207
+ } finally {
208
+ setBusy(false)
209
+ }
210
+ }
211
+
212
+ const saveReplace = async (masked) => {
213
+ const value = typeof replaceDrafts[masked] === 'string' ? replaceDrafts[masked].trim() : ''
214
+ if (!value) return
215
+ setBusy(true)
216
+ setNotice(null)
217
+ try {
218
+ const response = await fetch('/api/tavily-manager', {
219
+ method: 'POST',
220
+ headers: { 'content-type': 'application/json' },
221
+ body: JSON.stringify({ add: [value], remove: [masked], strategy })
222
+ })
223
+ const data = await response.json()
224
+ if (!data.ok) { setNotice({ error: data.error || 'Update failed' }); return }
225
+ setNotice({ ok: 'Key updated.' })
226
+ setReplacing((current) => Object.assign({}, current, { [masked]: false }))
227
+ setReplaceDrafts((current) => Object.assign({}, current, { [masked]: '' }))
228
+ setRevealed((current) => Object.assign({}, current, { [masked]: false }))
217
229
  await refresh()
218
230
  } catch (error) {
219
231
  setNotice({ error: String(error && error.message ? error.message : error) })
@@ -249,6 +261,7 @@ window.__ModuleLoader__.load({
249
261
  setConfirm((current) => Object.assign({}, current, { [masked]: false }))
250
262
  setRemoving((current) => Object.assign({}, current, { [masked]: true }))
251
263
  setRevealed((current) => Object.assign({}, current, { [masked]: false }))
264
+ saveRemove(masked)
252
265
  }
253
266
 
254
267
  const restore = (masked) => {
@@ -314,16 +327,21 @@ window.__ModuleLoader__.load({
314
327
  react.createElement('thead', null,
315
328
  react.createElement('tr', null,
316
329
  react.createElement('th', { style: headStyle }, 'Key'),
330
+ react.createElement('th', { style: headStyle }, 'Usage'),
317
331
  react.createElement('th', { style: headStyle }, 'Saved'),
318
332
  react.createElement('th', { style: headStyle }, 'Actions')
319
333
  )
320
334
  ),
321
335
  react.createElement('tbody', null,
322
- (server !== null ? server.keys : []).map((key) => {
336
+ (server !== null ? server.keys : []).map((key, index) => {
323
337
  const masked = key.masked
324
338
  const isRemoved = removing[masked] === true
325
339
  const isReplacing = replacing[masked] === true
326
340
  const isRevealed = typeof revealed[masked] === 'string'
341
+ const usageRow = usage !== null && usage.ok === true ? usage.perKey[index] : null
342
+ const pct = usageRow && usageRow.ok && usageRow.planLimit != null && usageRow.planLimit > 0 && usageRow.planUsage != null
343
+ ? Math.min(100, Math.round((usageRow.planUsage / usageRow.planLimit) * 100))
344
+ : null
327
345
  return react.createElement('tr', { key: masked, style: { borderBottom: '1px solid var(--dsw-alias-border-l2)', opacity: isRemoved ? 0.45 : 1 } },
328
346
  react.createElement('td', { style: Object.assign({}, cellStyle, { fontFamily: 'var(--ds-font-family-code, monospace)', fontSize: 12, wordBreak: 'break-all' }) },
329
347
  isReplacing
@@ -342,11 +360,14 @@ window.__ModuleLoader__.load({
342
360
  })
343
361
  )
344
362
  ),
363
+ react.createElement('td', { style: cellStyle },
364
+ react.createElement(UsageCircle, { percent: pct, label: pct != null ? pct + '%' : (usageRow && usageRow.ok && usageRow.planUsage != null ? String(usageRow.planUsage) : '—'), onClick: refresh })
365
+ ),
345
366
  react.createElement('td', { style: Object.assign({}, cellStyle, { color: 'var(--dsw-alias-label-tertiary)', whiteSpace: 'nowrap' }) }, formatDate(key.savedAt)),
346
367
  react.createElement('td', { style: cellStyle },
347
368
  isReplacing
348
369
  ? react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
349
- react.createElement(IconButton, { icon: 'check', title: 'Keep', onClick: () => { setReplacing((current) => Object.assign({}, current, { [masked]: false })) }, disabled: busy }),
370
+ react.createElement(IconButton, { icon: 'check', title: 'Save key', onClick: () => saveReplace(masked), disabled: busy || (typeof replaceDrafts[masked] === 'string' ? replaceDrafts[masked].trim().length === 0 : true) }),
350
371
  react.createElement(IconButton, { icon: 'close', title: 'Cancel', onClick: () => cancelReplace(masked), disabled: busy })
351
372
  )
352
373
  : react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
@@ -376,6 +397,7 @@ window.__ModuleLoader__.load({
376
397
  })
377
398
  ),
378
399
  react.createElement('td', { style: cellStyle }, '—'),
400
+ react.createElement('td', { style: cellStyle }, '—'),
379
401
  react.createElement('td', { style: cellStyle },
380
402
  react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
381
403
  react.createElement(IconButton, { icon: 'check', title: 'Save key', onClick: () => saveAdd(item), disabled: busy || item.value.trim().length === 0 }),
@@ -385,63 +407,26 @@ window.__ModuleLoader__.load({
385
407
  ))
386
408
  )
387
409
  ),
388
- react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
389
- react.createElement('button', { type: 'button', style: btn, onClick: () => setAdds((current) => [...current, { id: 'add-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), value: '' }]), disabled: busy }, '+ Add key')
410
+ react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
411
+ react.createElement('button', { type: 'button', style: btn, onClick: () => setAdds((current) => [...current, { id: 'add-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), value: '' }]), disabled: busy }, '+ Add key'),
390
412
  ),
391
- react.createElement('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12, padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8 } },
392
- react.createElement('label', { style: { fontWeight: 500, fontSize: 13 } }, 'Key usage strategy'),
393
- react.createElement('select', {
394
- style: { maxWidth: 280, height: 32, border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)', padding: '0 10px', fontSize: 13 },
395
- value: strategy,
396
- onChange: (event) => saveStrategy(event.target.value),
397
- disabled: busy
398
- }, STRATEGIES.map((option) => react.createElement('option', { key: option.id, value: option.id }, option.label))),
399
- react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
400
- (STRATEGIES.find((option) => option.id === strategy) || STRATEGIES[0]).hint + ' The strategy is saved immediately when you select it; the first key then becomes primary for web_search.'
401
- )
402
- ),
403
- react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
404
- react.createElement('button', { type: 'button', style: Object.assign({}, btn, { height: 32, background: 'var(--dsw-alias-button-primary-fill)', color: 'var(--dsw-alias-label-primary-foreground)' }), onClick: save, disabled: busy || !hasPending }, 'Save key changes'),
405
- notice !== null && react.createElement('span', { style: { fontSize: 13, color: notice.ok ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)' } }, notice.ok ? notice.ok : String(notice.error))
406
- ),
407
- react.createElement('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12, padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8 } },
408
- react.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'space-between' } },
409
- react.createElement('span', { style: { fontWeight: 500, fontSize: 13 } }, 'Tavily Usage'),
410
- react.createElement('button', { type: 'button', style: btn, onClick: refresh, disabled: busy }, 'Refresh')
413
+ react.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
414
+ react.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
415
+ react.createElement('label', { style: { fontWeight: 500, fontSize: 13 } }, 'Key usage strategy'),
416
+ react.createElement('select', {
417
+ style: { maxWidth: 280, height: 32, border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)', padding: '0 10px', fontSize: 13 },
418
+ value: strategy,
419
+ onChange: (event) => saveStrategy(event.target.value),
420
+ disabled: busy
421
+ }, STRATEGIES.map((option) => react.createElement('option', { key: option.id, value: option.id }, option.label))),
422
+ notice !== null && react.createElement('span', { style: { fontSize: 13, color: notice.ok ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)' } }, notice.ok ? notice.ok : String(notice.error))
411
423
  ),
412
- usageError !== null && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usageError)),
413
- usage !== null && usage.ok === false && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usage.error)),
414
- usage !== null && usage.ok === true && react.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } },
415
- react.createElement('div', { style: { fontSize: 13 } },
416
- usage.totals.keys + ' key(s) (' + usage.totals.okKeys + ' readable)' + (usage.totals.planLimit !== null ? ' · plan usage ' + usage.totals.planUsage + ' / ' + usage.totals.planLimit : '')
417
- ),
418
- react.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
419
- react.createElement('thead', null,
420
- react.createElement('tr', null,
421
- react.createElement('th', { style: headStyle }, 'API Key'),
422
- react.createElement('th', { style: headStyle }, 'Usage')
423
- )
424
- ),
425
- react.createElement('tbody', null,
426
- (server !== null ? server.keys : []).map((key, index) => {
427
- const row = usage.perKey[index]
428
- const pct = row && row.ok && row.planLimit != null && row.planLimit > 0 && row.planUsage != null
429
- ? Math.min(100, Math.round((row.planUsage / row.planLimit) * 100))
430
- : null
431
- return react.createElement('tr', { key: key.masked, style: { borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
432
- react.createElement('td', { style: Object.assign({}, cellStyle, { fontFamily: 'var(--ds-font-family-code, monospace)', fontSize: 12, wordBreak: 'break-all' }) }, key.masked),
433
- react.createElement('td', { style: cellStyle },
434
- react.createElement(UsageCircle, { percent: pct, label: pct != null ? pct + '%' : (row && row.ok && row.planUsage != null ? String(row.planUsage) : '—') })
435
- )
436
- )
437
- })
438
- )
439
- )
424
+ react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
425
+ (STRATEGIES.find((option) => option.id === strategy) || STRATEGIES[0]).hint + ' Saved immediately when selected; the first key then becomes primary for web_search.'
440
426
  )
441
427
  ),
442
- react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
443
- 'Usage data comes from the Tavily /usage endpoint (fetched locally; no keys are exposed).'
444
- )
428
+ usageError !== null && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usageError)),
429
+ usage !== null && usage.ok === false && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usage.error))
445
430
  )
446
431
  )
447
432
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moguiyu/dsh-tavily",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Tavily Search for DSH: multiple API keys, rotation/failover, usage gauge, settings card, and web_search toggle in one plugin.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/client.js CHANGED
@@ -69,16 +69,15 @@ export function factory(require) {
69
69
  }, react.createElement('span', { style: { position: 'absolute', top: 2, left: checked === true ? 18 : 2, width: 14, height: 14, borderRadius: 7, background: '#fff', transition: 'left .12s ease' } }))
70
70
  }
71
71
 
72
- function UsageCircle({ percent, size, label }) {
72
+ function UsageCircle({ percent, size, label, onClick }) {
73
73
  const radius = 15
74
74
  const circumference = 2 * Math.PI * radius
75
75
  const pct = percent != null ? Math.min(100, Math.max(0, percent)) : 0
76
- const warn = pct > 85
77
76
  const dim = size || 44
78
- return react.createElement('div', { style: { position: 'relative', width: dim, height: dim, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } },
77
+ return react.createElement('button', { type: 'button', title: 'Reload usage', 'aria-label': 'Reload usage', onClick: onClick, style: { position: 'relative', width: dim, height: dim, border: 'none', background: 'transparent', padding: 0, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' } },
79
78
  react.createElement('svg', { width: dim, height: dim, viewBox: '0 0 40 40' },
80
79
  react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: 'var(--dsw-alias-border-l2)', strokeWidth: 4 }),
81
- react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: warn ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-brand-primary)', strokeWidth: 4, strokeLinecap: 'round', strokeDasharray: circumference, strokeDashoffset: circumference - (pct / 100) * circumference, transform: 'rotate(-90 20 20)' })
80
+ react.createElement('circle', { cx: 20, cy: 20, r: radius, fill: 'none', stroke: 'var(--dsw-alias-state-success-primary)', strokeWidth: 4, strokeLinecap: 'round', strokeDasharray: circumference, strokeDashoffset: circumference - (pct / 100) * circumference, transform: 'rotate(-90 20 20)' })
82
81
  ),
83
82
  react.createElement('span', { style: { position: 'absolute', fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-primary)' } }, label != null ? label : (percent != null ? percent + '%' : '—'))
84
83
  )
@@ -149,10 +148,6 @@ export function factory(require) {
149
148
  }
150
149
  }
151
150
 
152
- const hasPending = Object.values(removing).some(Boolean)
153
- || Object.values(replacing).some(Boolean)
154
- || adds.some((item) => item.value.trim().length > 0)
155
-
156
151
  const saveStrategy = async (next) => {
157
152
  setBusy(true)
158
153
  setNotice(null)
@@ -196,29 +191,46 @@ export function factory(require) {
196
191
  }
197
192
  }
198
193
 
199
- const save = async () => {
194
+ const saveRemove = async (masked) => {
200
195
  setBusy(true)
201
196
  setNotice(null)
202
197
  try {
203
- const addValues = adds.map((item) => item.value.trim()).filter((value) => value.length > 0)
204
- const removeMasked = [
205
- ...Object.keys(removing).filter((masked) => removing[masked] === true),
206
- ...Object.keys(replacing).filter((masked) => replacing[masked] === true)
207
- ]
208
198
  const response = await fetch('/api/tavily-manager', {
209
199
  method: 'POST',
210
200
  headers: { 'content-type': 'application/json' },
211
- body: JSON.stringify({ add: addValues, remove: removeMasked, strategy })
201
+ body: JSON.stringify({ add: [], remove: [masked], strategy })
212
202
  })
213
203
  const data = await response.json()
214
- if (!data.ok) { setNotice({ error: data.error || 'Save failed' }); return }
215
- setNotice({ ok: 'Saved — effective on the next search.' })
216
- setRemoving({})
217
- setReplacing({})
218
- setReplaceDrafts({})
219
- setAdds([])
220
- setRevealed({})
221
- setConfirm({})
204
+ if (!data.ok) { setNotice({ error: data.error || 'Delete failed' }); return }
205
+ setNotice({ ok: 'Key removed.' })
206
+ setConfirm((current) => Object.assign({}, current, { [masked]: false }))
207
+ setRemoving((current) => Object.assign({}, current, { [masked]: false }))
208
+ setRevealed((current) => Object.assign({}, current, { [masked]: false }))
209
+ await refresh()
210
+ } catch (error) {
211
+ setNotice({ error: String(error && error.message ? error.message : error) })
212
+ } finally {
213
+ setBusy(false)
214
+ }
215
+ }
216
+
217
+ const saveReplace = async (masked) => {
218
+ const value = typeof replaceDrafts[masked] === 'string' ? replaceDrafts[masked].trim() : ''
219
+ if (!value) return
220
+ setBusy(true)
221
+ setNotice(null)
222
+ try {
223
+ const response = await fetch('/api/tavily-manager', {
224
+ method: 'POST',
225
+ headers: { 'content-type': 'application/json' },
226
+ body: JSON.stringify({ add: [value], remove: [masked], strategy })
227
+ })
228
+ const data = await response.json()
229
+ if (!data.ok) { setNotice({ error: data.error || 'Update failed' }); return }
230
+ setNotice({ ok: 'Key updated.' })
231
+ setReplacing((current) => Object.assign({}, current, { [masked]: false }))
232
+ setReplaceDrafts((current) => Object.assign({}, current, { [masked]: '' }))
233
+ setRevealed((current) => Object.assign({}, current, { [masked]: false }))
222
234
  await refresh()
223
235
  } catch (error) {
224
236
  setNotice({ error: String(error && error.message ? error.message : error) })
@@ -254,6 +266,7 @@ export function factory(require) {
254
266
  setConfirm((current) => Object.assign({}, current, { [masked]: false }))
255
267
  setRemoving((current) => Object.assign({}, current, { [masked]: true }))
256
268
  setRevealed((current) => Object.assign({}, current, { [masked]: false }))
269
+ saveRemove(masked)
257
270
  }
258
271
 
259
272
  const restore = (masked) => {
@@ -319,16 +332,21 @@ export function factory(require) {
319
332
  react.createElement('thead', null,
320
333
  react.createElement('tr', null,
321
334
  react.createElement('th', { style: headStyle }, 'Key'),
335
+ react.createElement('th', { style: headStyle }, 'Usage'),
322
336
  react.createElement('th', { style: headStyle }, 'Saved'),
323
337
  react.createElement('th', { style: headStyle }, 'Actions')
324
338
  )
325
339
  ),
326
340
  react.createElement('tbody', null,
327
- (server !== null ? server.keys : []).map((key) => {
341
+ (server !== null ? server.keys : []).map((key, index) => {
328
342
  const masked = key.masked
329
343
  const isRemoved = removing[masked] === true
330
344
  const isReplacing = replacing[masked] === true
331
345
  const isRevealed = typeof revealed[masked] === 'string'
346
+ const usageRow = usage !== null && usage.ok === true ? usage.perKey[index] : null
347
+ const pct = usageRow && usageRow.ok && usageRow.planLimit != null && usageRow.planLimit > 0 && usageRow.planUsage != null
348
+ ? Math.min(100, Math.round((usageRow.planUsage / usageRow.planLimit) * 100))
349
+ : null
332
350
  return react.createElement('tr', { key: masked, style: { borderBottom: '1px solid var(--dsw-alias-border-l2)', opacity: isRemoved ? 0.45 : 1 } },
333
351
  react.createElement('td', { style: Object.assign({}, cellStyle, { fontFamily: 'var(--ds-font-family-code, monospace)', fontSize: 12, wordBreak: 'break-all' }) },
334
352
  isReplacing
@@ -347,11 +365,14 @@ export function factory(require) {
347
365
  })
348
366
  )
349
367
  ),
368
+ react.createElement('td', { style: cellStyle },
369
+ react.createElement(UsageCircle, { percent: pct, label: pct != null ? pct + '%' : (usageRow && usageRow.ok && usageRow.planUsage != null ? String(usageRow.planUsage) : '—'), onClick: refresh })
370
+ ),
350
371
  react.createElement('td', { style: Object.assign({}, cellStyle, { color: 'var(--dsw-alias-label-tertiary)', whiteSpace: 'nowrap' }) }, formatDate(key.savedAt)),
351
372
  react.createElement('td', { style: cellStyle },
352
373
  isReplacing
353
374
  ? react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
354
- react.createElement(IconButton, { icon: 'check', title: 'Keep', onClick: () => { setReplacing((current) => Object.assign({}, current, { [masked]: false })) }, disabled: busy }),
375
+ react.createElement(IconButton, { icon: 'check', title: 'Save key', onClick: () => saveReplace(masked), disabled: busy || (typeof replaceDrafts[masked] === 'string' ? replaceDrafts[masked].trim().length === 0 : true) }),
355
376
  react.createElement(IconButton, { icon: 'close', title: 'Cancel', onClick: () => cancelReplace(masked), disabled: busy })
356
377
  )
357
378
  : react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
@@ -381,6 +402,7 @@ export function factory(require) {
381
402
  })
382
403
  ),
383
404
  react.createElement('td', { style: cellStyle }, '—'),
405
+ react.createElement('td', { style: cellStyle }, '—'),
384
406
  react.createElement('td', { style: cellStyle },
385
407
  react.createElement('span', { style: { display: 'inline-flex', gap: 2 } },
386
408
  react.createElement(IconButton, { icon: 'check', title: 'Save key', onClick: () => saveAdd(item), disabled: busy || item.value.trim().length === 0 }),
@@ -390,63 +412,26 @@ export function factory(require) {
390
412
  ))
391
413
  )
392
414
  ),
393
- react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
394
- react.createElement('button', { type: 'button', style: btn, onClick: () => setAdds((current) => [...current, { id: 'add-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), value: '' }]), disabled: busy }, '+ Add key')
415
+ react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
416
+ react.createElement('button', { type: 'button', style: btn, onClick: () => setAdds((current) => [...current, { id: 'add-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7), value: '' }]), disabled: busy }, '+ Add key'),
395
417
  ),
396
- react.createElement('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12, padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8 } },
397
- react.createElement('label', { style: { fontWeight: 500, fontSize: 13 } }, 'Key usage strategy'),
398
- react.createElement('select', {
399
- style: { maxWidth: 280, height: 32, border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)', padding: '0 10px', fontSize: 13 },
400
- value: strategy,
401
- onChange: (event) => saveStrategy(event.target.value),
402
- disabled: busy
403
- }, STRATEGIES.map((option) => react.createElement('option', { key: option.id, value: option.id }, option.label))),
404
- react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
405
- (STRATEGIES.find((option) => option.id === strategy) || STRATEGIES[0]).hint + ' The strategy is saved immediately when you select it; the first key then becomes primary for web_search.'
406
- )
407
- ),
408
- react.createElement('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
409
- react.createElement('button', { type: 'button', style: Object.assign({}, btn, { height: 32, background: 'var(--dsw-alias-button-primary-fill)', color: 'var(--dsw-alias-label-primary-foreground)' }), onClick: save, disabled: busy || !hasPending }, 'Save key changes'),
410
- notice !== null && react.createElement('span', { style: { fontSize: 13, color: notice.ok ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)' } }, notice.ok ? notice.ok : String(notice.error))
411
- ),
412
- react.createElement('div', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12, padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8 } },
413
- react.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'space-between' } },
414
- react.createElement('span', { style: { fontWeight: 500, fontSize: 13 } }, 'Tavily Usage'),
415
- react.createElement('button', { type: 'button', style: btn, onClick: refresh, disabled: busy }, 'Refresh')
418
+ react.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
419
+ react.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
420
+ react.createElement('label', { style: { fontWeight: 500, fontSize: 13 } }, 'Key usage strategy'),
421
+ react.createElement('select', {
422
+ style: { maxWidth: 280, height: 32, border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)', padding: '0 10px', fontSize: 13 },
423
+ value: strategy,
424
+ onChange: (event) => saveStrategy(event.target.value),
425
+ disabled: busy
426
+ }, STRATEGIES.map((option) => react.createElement('option', { key: option.id, value: option.id }, option.label))),
427
+ notice !== null && react.createElement('span', { style: { fontSize: 13, color: notice.ok ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-state-error-primary)' } }, notice.ok ? notice.ok : String(notice.error))
416
428
  ),
417
- usageError !== null && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usageError)),
418
- usage !== null && usage.ok === false && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usage.error)),
419
- usage !== null && usage.ok === true && react.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } },
420
- react.createElement('div', { style: { fontSize: 13 } },
421
- usage.totals.keys + ' key(s) (' + usage.totals.okKeys + ' readable)' + (usage.totals.planLimit !== null ? ' · plan usage ' + usage.totals.planUsage + ' / ' + usage.totals.planLimit : '')
422
- ),
423
- react.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
424
- react.createElement('thead', null,
425
- react.createElement('tr', null,
426
- react.createElement('th', { style: headStyle }, 'API Key'),
427
- react.createElement('th', { style: headStyle }, 'Usage')
428
- )
429
- ),
430
- react.createElement('tbody', null,
431
- (server !== null ? server.keys : []).map((key, index) => {
432
- const row = usage.perKey[index]
433
- const pct = row && row.ok && row.planLimit != null && row.planLimit > 0 && row.planUsage != null
434
- ? Math.min(100, Math.round((row.planUsage / row.planLimit) * 100))
435
- : null
436
- return react.createElement('tr', { key: key.masked, style: { borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
437
- react.createElement('td', { style: Object.assign({}, cellStyle, { fontFamily: 'var(--ds-font-family-code, monospace)', fontSize: 12, wordBreak: 'break-all' }) }, key.masked),
438
- react.createElement('td', { style: cellStyle },
439
- react.createElement(UsageCircle, { percent: pct, label: pct != null ? pct + '%' : (row && row.ok && row.planUsage != null ? String(row.planUsage) : '—') })
440
- )
441
- )
442
- })
443
- )
444
- )
429
+ react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
430
+ (STRATEGIES.find((option) => option.id === strategy) || STRATEGIES[0]).hint + ' Saved immediately when selected; the first key then becomes primary for web_search.'
445
431
  )
446
432
  ),
447
- react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-label-tertiary)' } },
448
- 'Usage data comes from the Tavily /usage endpoint (fetched locally; no keys are exposed).'
449
- )
433
+ usageError !== null && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usageError)),
434
+ usage !== null && usage.ok === false && react.createElement('p', { style: { margin: 0, fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, String(usage.error))
450
435
  )
451
436
  )
452
437
  )