dsh-tacit 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/client/client.js +367 -74
- package/docs/README.zh.md +2 -1
- package/lib/analyze.js +215 -24
- package/lib/fold.js +11 -5
- package/lib/index.js +1 -1
- package/lib/redact.js +53 -0
- package/lib/routes.js +1 -0
- package/lib/schema.js +98 -25
- package/lib/service.js +282 -134
- package/lib/store.js +66 -5
- package/lib/usage.js +139 -41
- package/lib/workspace.js +62 -0
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -50,15 +50,19 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
50
50
|
'status.autoOn': '自动学习已开启 · 今日 {today}/{budget}',
|
|
51
51
|
'status.autoOff': '自动学习已关闭 — 仅在你点击「分析」时学习。',
|
|
52
52
|
'status.autoHint': '混乱的轮次(重试、工具错误、压缩)以及你纠正智能体的下一条消息会被自动分析,无需点击。',
|
|
53
|
+
'status.trendCorrections': '你纠正智能体:{a} → {b}',
|
|
53
54
|
'status.trendMessy': '混乱轮次:{a} → {b}',
|
|
54
55
|
'status.trendTokens': '每轮 tokens:{a} → {b}',
|
|
55
|
-
'status.trendHint': '(最早 {n} 轮 vs 最近 {n}
|
|
56
|
+
'status.trendHint': '(最早 {n} 轮 vs 最近 {n} 轮,真实数据——这是你自己轮次的前后对比,不是对照实验)',
|
|
57
|
+
'status.trendDocs': '试用与趋势的判定方式见《How it works》:§6 Trials https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#6-trials · §10 The measured trend https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#10-the-measured-trend',
|
|
56
58
|
'turn.enrichment': '发送前补充的上下文',
|
|
57
59
|
'trigger.auto': '自动',
|
|
58
60
|
'trigger.correction': '纠正',
|
|
59
61
|
'trigger.manual': '手动',
|
|
60
62
|
'trigger.bootstrap': '引导',
|
|
61
63
|
'trigger.good': '做对了',
|
|
64
|
+
'trigger.feedback': '反馈',
|
|
65
|
+
'trigger.send': '发送',
|
|
62
66
|
'report.strengths': '这次做对了什么',
|
|
63
67
|
'report.lesson': '经验',
|
|
64
68
|
'settings.learnGood': '也从混乱之后的顺利提示词中学习',
|
|
@@ -80,6 +84,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
80
84
|
'steer.remove': '删除',
|
|
81
85
|
'steer.preview': '实际注入的文本',
|
|
82
86
|
'steer.distilled': '自动提炼',
|
|
87
|
+
'steer.queued': '等待试用',
|
|
83
88
|
'steer.trial': '试用 {n}/{total}',
|
|
84
89
|
'steer.active': '已生效',
|
|
85
90
|
'steer.retired': '已淘汰 · {reason}',
|
|
@@ -87,6 +92,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
87
92
|
'steer.workspace': '仅 {name}',
|
|
88
93
|
'steer.scope': '适用范围',
|
|
89
94
|
'steer.everywhere': '所有工作区',
|
|
95
|
+
'steer.receipt': '这条指令从哪来',
|
|
96
|
+
'steer.review': '新指令先经我确认再开始试用',
|
|
97
|
+
'steer.startTrial': '开始试用',
|
|
98
|
+
'steer.receiptLoading': '读取中…',
|
|
99
|
+
'steer.receiptCopy': '复制为 JSON',
|
|
100
|
+
'steer.receiptNever': '从未',
|
|
101
|
+
'steer.receiptNoRun': '无记账记录',
|
|
102
|
+
'steer.receiptId': '编号',
|
|
103
|
+
'steer.receiptScope': '适用范围',
|
|
104
|
+
'steer.receiptStatus': '状态',
|
|
105
|
+
'steer.receiptSource': '来源',
|
|
106
|
+
'steer.receiptCreated': '创建于',
|
|
107
|
+
'steer.receiptUpdated': '更新于',
|
|
108
|
+
'steer.receiptEvaluated': '评定于',
|
|
109
|
+
'steer.receiptVersion': '版本',
|
|
110
|
+
'steer.receiptTrial': '试用窗口',
|
|
111
|
+
'steer.receiptTrialValue': '{turns} 轮,始于 {started} · 基线:杂乱 {messy}、纠正 {corrected}',
|
|
112
|
+
'steer.receiptTriggers': '触发类别',
|
|
113
|
+
'steer.receiptEvidence': '证据',
|
|
114
|
+
'steer.receiptEvidenceValue': '{turns} 轮,来自 {conversations} 个会话',
|
|
115
|
+
'steer.receiptCost': '提炼花费',
|
|
116
|
+
'steer.receiptCostValue': '{usd} · {calls} 次调用',
|
|
117
|
+
'steer.moveTo': '移到工作区',
|
|
118
|
+
'steer.notSeen': '自 {date} 起未见',
|
|
90
119
|
'settings.auto': '自动分析混乱轮次',
|
|
91
120
|
'settings.budget': '每日自动分析上限',
|
|
92
121
|
settings: '设置',
|
|
@@ -165,10 +194,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
165
194
|
'usage.last30': '最近 30 天',
|
|
166
195
|
'usage.since': '自 {day} 起',
|
|
167
196
|
'usage.calls': '计费调用(30 天)',
|
|
197
|
+
'usage.tokens': 'Tokens(30 天)',
|
|
168
198
|
'usage.avgAnalysis': '单次分析中位成本',
|
|
169
199
|
'usage.cachedRate': '输入缓存命中率(30 天)',
|
|
170
200
|
'usage.unpriced': '无价目调用(30 天)',
|
|
171
201
|
'usage.unpricedShort': '{n} 次无价目',
|
|
202
|
+
'usage.failedRepair': '失败或修复调用花费(30 天)',
|
|
203
|
+
'usage.failedRepairSplit': '失败 {failed} · 修复 {repair}',
|
|
204
|
+
'usage.autoToday': '今日自动分析 {n} / {budget}',
|
|
172
205
|
'usage.priceUnavailable': '无价目',
|
|
173
206
|
'usage.chartLabel': '每日花费,最近 {n} 天',
|
|
174
207
|
'usage.chartSummary': '最近 {n} 天:合计 {total};最高 {day} {max}',
|
|
@@ -176,6 +209,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
176
209
|
'usage.series7': '7 天',
|
|
177
210
|
'usage.series30': '30 天',
|
|
178
211
|
'usage.breakdown': '按操作分类(最近 30 天)',
|
|
212
|
+
'usage.byRoute': '按线路分类(最近 30 天)',
|
|
213
|
+
'usage.byTrigger': '按触发方式分类(最近 30 天)',
|
|
179
214
|
'usage.filters': '筛选',
|
|
180
215
|
'usage.loading': '正在载入这次运行的调用明细…',
|
|
181
216
|
'usage.page': '第 {page} / {pages} 页',
|
|
@@ -185,6 +220,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
185
220
|
'usage.col.time': '时间',
|
|
186
221
|
'usage.col.op': '操作',
|
|
187
222
|
'usage.col.scope': '范围',
|
|
223
|
+
'usage.scopeAll': '全部会话',
|
|
188
224
|
'usage.col.model': '模型',
|
|
189
225
|
'usage.col.status': '状态',
|
|
190
226
|
'usage.col.calls': '计费调用',
|
|
@@ -236,6 +272,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
236
272
|
'privacy.warnHint': '填 0 表示关闭提醒。达到阈值的 80% 时开始提醒,超过阈值后标记为已超出。',
|
|
237
273
|
'privacy.clearUsage': '清除用量记录',
|
|
238
274
|
'privacy.apply': '应用',
|
|
275
|
+
'privacy.applyTo': '{action}:{field}',
|
|
239
276
|
'confirm.reportsTitle': '清除所有分析报告?',
|
|
240
277
|
'confirm.reportsBody': '这会删除 Tacit 为所有会话写下的全部分析报告。指令、风格规则和用量记录会保留。此操作无法撤销。',
|
|
241
278
|
'confirm.usageTitle': '清除用量记录?',
|
|
@@ -280,6 +317,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
280
317
|
'err.busy': '该轮次的分析已在运行中。',
|
|
281
318
|
'err.no-llm': '无法访问模型服务。',
|
|
282
319
|
'err.no-api-key': 'DeepSeek API Key 缺失或无效(请在 设置 → 模型 中配置)。',
|
|
320
|
+
'err.no-credit': '模型服务商余额或配额已耗尽。',
|
|
283
321
|
'err.rate-limited': '请求被限流,请稍后重试。',
|
|
284
322
|
'err.timeout': '模型调用超时,请重试。',
|
|
285
323
|
'err.empty-response': '模型没有返回内容,请重试。',
|
|
@@ -287,6 +325,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
287
325
|
'err.network': '无法连接 Tacit 服务。',
|
|
288
326
|
'err.internal': '内部错误:{detail}',
|
|
289
327
|
'err.continuation': '这是一条纯粹的「继续」类消息(没有可分析的意图)——请分析它之前的那一轮。',
|
|
328
|
+
'err.directive-policy': '指令不能改变工具权限、审批或沙箱设置,请换一种说法。',
|
|
329
|
+
'err.already-analyzed': '这一轮已有报告;点击「重新分析」可再花一次费用分析。',
|
|
330
|
+
'err.unknown-directive': '找不到这条指令。',
|
|
290
331
|
}
|
|
291
332
|
|
|
292
333
|
const DICT_EN = {
|
|
@@ -295,15 +336,19 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
295
336
|
'status.autoOn': 'Auto-learning on · {today}/{budget} today',
|
|
296
337
|
'status.autoOff': 'Auto-learning off — Tacit only learns when you click Analyze.',
|
|
297
338
|
'status.autoHint': 'Messy turns (retries, tool errors, compactions) and turns you correct in your next message are analyzed automatically — no clicks.',
|
|
339
|
+
'status.trendCorrections': 'You corrected the agent: {a} → {b}',
|
|
298
340
|
'status.trendMessy': 'Messy turns: {a} → {b}',
|
|
299
341
|
'status.trendTokens': 'Tokens/turn: {a} → {b}',
|
|
300
|
-
'status.trendHint': '(first {n} turns vs. latest {n}, measured)',
|
|
342
|
+
'status.trendHint': '(first {n} turns vs. latest {n}, measured — before/after on your own turns, not a controlled comparison)',
|
|
343
|
+
'status.trendDocs': 'How these are judged: §6 Trials https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#6-trials · §10 The measured trend https://github.com/hackernotfound/dsh-tacit/blob/main/docs/how-it-works.md#10-the-measured-trend',
|
|
301
344
|
'turn.enrichment': 'Context added before the send',
|
|
302
345
|
'trigger.auto': 'auto',
|
|
303
346
|
'trigger.correction': 'correction',
|
|
304
347
|
'trigger.manual': 'manual',
|
|
305
348
|
'trigger.bootstrap': 'bootstrap',
|
|
306
349
|
'trigger.good': 'what worked',
|
|
350
|
+
'trigger.feedback': 'feedback',
|
|
351
|
+
'trigger.send': 'send',
|
|
307
352
|
'report.strengths': 'What this prompt got right',
|
|
308
353
|
'report.lesson': 'Lesson',
|
|
309
354
|
'settings.learnGood': 'Also learn from a clean prompt right after a messy turn',
|
|
@@ -325,6 +370,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
325
370
|
'steer.remove': 'Remove',
|
|
326
371
|
'steer.preview': 'Exact text injected',
|
|
327
372
|
'steer.distilled': 'distilled',
|
|
373
|
+
'steer.queued': 'waiting for trial',
|
|
328
374
|
'steer.trial': 'trial {n}/{total}',
|
|
329
375
|
'steer.active': 'active',
|
|
330
376
|
'steer.retired': 'retired · {reason}',
|
|
@@ -332,6 +378,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
332
378
|
'steer.workspace': 'only {name}',
|
|
333
379
|
'steer.scope': 'Applies to',
|
|
334
380
|
'steer.everywhere': 'Everywhere',
|
|
381
|
+
'steer.receipt': 'Where this directive came from',
|
|
382
|
+
'steer.review': 'Review new directives before their trial',
|
|
383
|
+
'steer.startTrial': 'Start trial',
|
|
384
|
+
'steer.receiptLoading': 'Reading…',
|
|
385
|
+
'steer.receiptCopy': 'Copy as JSON',
|
|
386
|
+
'steer.receiptNever': 'never',
|
|
387
|
+
'steer.receiptNoRun': 'no ledger run',
|
|
388
|
+
'steer.receiptId': 'Id',
|
|
389
|
+
'steer.receiptScope': 'Applies to',
|
|
390
|
+
'steer.receiptStatus': 'Status',
|
|
391
|
+
'steer.receiptSource': 'Source',
|
|
392
|
+
'steer.receiptCreated': 'Created',
|
|
393
|
+
'steer.receiptUpdated': 'Updated',
|
|
394
|
+
'steer.receiptEvaluated': 'Evaluated',
|
|
395
|
+
'steer.receiptVersion': 'Version',
|
|
396
|
+
'steer.receiptTrial': 'Trial window',
|
|
397
|
+
'steer.receiptTrialValue': '{turns} turns from {started} · baseline messy {messy}, corrections {corrected}',
|
|
398
|
+
'steer.receiptTriggers': 'Trigger categories',
|
|
399
|
+
'steer.receiptEvidence': 'Evidence',
|
|
400
|
+
'steer.receiptEvidenceValue': '{turns} turns across {conversations} conversations',
|
|
401
|
+
'steer.receiptCost': 'Distillation cost',
|
|
402
|
+
'steer.receiptCostValue': '{usd} · {calls} calls',
|
|
403
|
+
'steer.moveTo': 'Move to workspace',
|
|
404
|
+
'steer.notSeen': 'not seen since {date}',
|
|
335
405
|
'settings.auto': 'Auto-analyze messy turns',
|
|
336
406
|
'settings.budget': 'Daily cap on automatic analyses',
|
|
337
407
|
settings: 'Settings',
|
|
@@ -410,10 +480,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
410
480
|
'usage.last30': 'Last 30 days',
|
|
411
481
|
'usage.since': 'Since {day}',
|
|
412
482
|
'usage.calls': 'Billed calls (30 d)',
|
|
483
|
+
'usage.tokens': 'Tokens (30 d)',
|
|
413
484
|
'usage.avgAnalysis': 'Median cost per analysis',
|
|
414
485
|
'usage.cachedRate': 'Cached input rate (30 d)',
|
|
415
486
|
'usage.unpriced': 'Unpriced calls (30 d)',
|
|
416
487
|
'usage.unpricedShort': '{n} unpriced',
|
|
488
|
+
'usage.failedRepair': 'Spent on failed or repair calls (30 d)',
|
|
489
|
+
'usage.failedRepairSplit': 'failed {failed} · repair {repair}',
|
|
490
|
+
'usage.autoToday': 'automatic analyses today {n} / {budget}',
|
|
417
491
|
'usage.priceUnavailable': 'No price',
|
|
418
492
|
'usage.chartLabel': 'Daily spend, last {n} days',
|
|
419
493
|
'usage.chartSummary': 'Last {n} days: total {total}; highest {day} {max}',
|
|
@@ -421,6 +495,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
421
495
|
'usage.series7': '7 d',
|
|
422
496
|
'usage.series30': '30 d',
|
|
423
497
|
'usage.breakdown': 'By operation (last 30 days)',
|
|
498
|
+
'usage.byRoute': 'By route (last 30 days)',
|
|
499
|
+
'usage.byTrigger': 'By trigger (last 30 days)',
|
|
424
500
|
'usage.filters': 'Filters',
|
|
425
501
|
'usage.loading': 'Loading this run\u2019s calls…',
|
|
426
502
|
'usage.page': 'Page {page} of {pages}',
|
|
@@ -430,6 +506,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
430
506
|
'usage.col.time': 'Time',
|
|
431
507
|
'usage.col.op': 'Operation',
|
|
432
508
|
'usage.col.scope': 'Scope',
|
|
509
|
+
'usage.scopeAll': 'all sessions',
|
|
433
510
|
'usage.col.model': 'Model',
|
|
434
511
|
'usage.col.status': 'Status',
|
|
435
512
|
'usage.col.calls': 'Billed calls',
|
|
@@ -481,6 +558,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
481
558
|
'privacy.warnHint': '0 turns the warning off. Tacit warns at 80 % of the amount and marks it exceeded above it.',
|
|
482
559
|
'privacy.clearUsage': 'Clear usage history',
|
|
483
560
|
'privacy.apply': 'Apply',
|
|
561
|
+
'privacy.applyTo': '{action}: {field}',
|
|
484
562
|
'confirm.reportsTitle': 'Clear all analysis reports?',
|
|
485
563
|
'confirm.reportsBody': 'This deletes every analysis report Tacit has written, for every session. Your directives, style rules and usage history are kept. This cannot be undone.',
|
|
486
564
|
'confirm.usageTitle': 'Clear usage history?',
|
|
@@ -525,6 +603,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
525
603
|
'err.busy': 'An analysis for this turn is already running.',
|
|
526
604
|
'err.no-llm': 'The model service is unavailable.',
|
|
527
605
|
'err.no-api-key': 'The DeepSeek API key is missing or invalid (configure it in Settings → Models).',
|
|
606
|
+
'err.no-credit': 'Your provider balance or quota is exhausted.',
|
|
528
607
|
'err.rate-limited': 'The request was rate-limited; try again shortly.',
|
|
529
608
|
'err.timeout': 'The model call timed out; please retry.',
|
|
530
609
|
'err.empty-response': 'The model returned no content; please retry.',
|
|
@@ -532,6 +611,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
532
611
|
'err.network': 'Could not reach the Tacit service.',
|
|
533
612
|
'err.internal': 'Internal error: {detail}',
|
|
534
613
|
'err.continuation': 'This is a bare continuation ("continue", "go ahead") with nothing to analyze — analyze the turn before it instead.',
|
|
614
|
+
'err.directive-policy': 'A directive cannot change tool permissions, approvals or the sandbox. Reword it.',
|
|
615
|
+
'err.already-analyzed': 'This turn already has a report; Re-analyze pays for a fresh one.',
|
|
616
|
+
'err.unknown-directive': 'No directive with that id.',
|
|
535
617
|
}
|
|
536
618
|
|
|
537
619
|
// ── API client (the host half's own routes on the harness origin) ──────
|
|
@@ -631,6 +713,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
631
713
|
auto: null,
|
|
632
714
|
steering: null, // {enabled, text}
|
|
633
715
|
workspaces: [], // [{cwd, label}] from /state
|
|
716
|
+
receipts: {}, // directive id → the /directive-receipt receipt, re-read on each expand
|
|
634
717
|
trend: null, // measured early-vs-recent trend
|
|
635
718
|
bootstrap: null, // {running, done, total}
|
|
636
719
|
coached: [], // cross-session coached-prompt entries
|
|
@@ -655,7 +738,6 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
655
738
|
usageFilters: { range: '30d', type: '', status: '', model: '', workspace: '', sessionId: '', page: 1, pageSize: 20 },
|
|
656
739
|
usageRuns: {}, // runId → the full run (attempts included), fetched on first expand
|
|
657
740
|
usageExpanded: new Set(), // runIds whose attempt rows are open
|
|
658
|
-
usageLoading: false,
|
|
659
741
|
usageSeries: '30', // '7' | '30' — which sparkline the strip shows
|
|
660
742
|
pricingRefreshing: false, // a /pricing-refresh call is in flight
|
|
661
743
|
initStarted: false,
|
|
@@ -775,14 +857,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
775
857
|
|
|
776
858
|
/** Read the whole cost panel in one call; a failure keeps the last envelope. */
|
|
777
859
|
async function fetchUsage() {
|
|
778
|
-
rootStore.usageLoading = true
|
|
779
860
|
try {
|
|
780
861
|
const result = await api('/usage', usageQuery())
|
|
781
862
|
if (result !== null && typeof result === 'object' && result.ok === true) rootStore.usage = result
|
|
782
863
|
} catch {
|
|
783
864
|
// A stale panel beats a blank one; the next poll tries again.
|
|
784
865
|
}
|
|
785
|
-
rootStore.usageLoading = false
|
|
786
866
|
notifyRoot()
|
|
787
867
|
}
|
|
788
868
|
|
|
@@ -1022,13 +1102,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1022
1102
|
function closeConfirm() {
|
|
1023
1103
|
rootStore.confirm = null
|
|
1024
1104
|
notifyRoot()
|
|
1105
|
+
restoreConfirmOpener()
|
|
1025
1106
|
}
|
|
1026
1107
|
|
|
1027
1108
|
/** Run the parked destructive action, whichever it is, and close the dialog. */
|
|
1028
1109
|
async function confirmAction() {
|
|
1029
1110
|
const kind = rootStore.confirm !== null && typeof rootStore.confirm === 'object' ? rootStore.confirm.kind : null
|
|
1030
|
-
|
|
1031
|
-
notifyRoot()
|
|
1111
|
+
closeConfirm()
|
|
1032
1112
|
if (kind === 'reports') await clearAllRoot()
|
|
1033
1113
|
else if (kind === 'usage') await clearUsageHistory()
|
|
1034
1114
|
}
|
|
@@ -1144,6 +1224,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1144
1224
|
notifyRoot()
|
|
1145
1225
|
}
|
|
1146
1226
|
|
|
1227
|
+
/** One directive's audit receipt. Re-read on every expand: a trial's counters move while it runs. */
|
|
1228
|
+
async function fetchReceipt(id) {
|
|
1229
|
+
const key = String(id)
|
|
1230
|
+
if (key.length === 0) return
|
|
1231
|
+
rootStore.error = null
|
|
1232
|
+
try {
|
|
1233
|
+
const result = await api('/directive-receipt', { id: key })
|
|
1234
|
+
if (result !== null && typeof result === 'object' && result.ok === true
|
|
1235
|
+
&& result.receipt !== null && typeof result.receipt === 'object') {
|
|
1236
|
+
rootStore.receipts[key] = result.receipt
|
|
1237
|
+
} else {
|
|
1238
|
+
rootStore.error = { code: result !== null && typeof result === 'object' && typeof result.code === 'string' ? result.code : 'bad-request', detail: '' }
|
|
1239
|
+
}
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
rootStore.error = errorOf(error)
|
|
1242
|
+
}
|
|
1243
|
+
notifyRoot()
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1147
1246
|
async function initStore(store) {
|
|
1148
1247
|
if (store === null || store.initStarted) return
|
|
1149
1248
|
store.initStarted = true
|
|
@@ -1174,7 +1273,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1174
1273
|
store.error = null
|
|
1175
1274
|
store.notice = null
|
|
1176
1275
|
notify(store)
|
|
1177
|
-
|
|
1276
|
+
// A turn that already has a report is only re-analyzed on purpose; the button reads "Re-analyze" then.
|
|
1277
|
+
api('/analyze', { sessionId: store.sessionId, turn, ...(store.reports[String(turn)] ? { force: true } : {}) })
|
|
1178
1278
|
.then((result) => {
|
|
1179
1279
|
if (result !== null && typeof result === 'object' && result.ok && result.report !== null && typeof result.report === 'object') {
|
|
1180
1280
|
store.reports[String(turn)] = result.report
|
|
@@ -1238,7 +1338,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1238
1338
|
// `busy` means an analysis of that turn was already running — a
|
|
1239
1339
|
// race with the auto-analyzer, not a failure of this batch.
|
|
1240
1340
|
const code = typeof entry.code === 'string' && entry.code.length > 0 ? entry.code : 'call-failed'
|
|
1241
|
-
if (failure === null && code !== 'busy') failure = { code, detail: '' }
|
|
1341
|
+
if (failure === null && code !== 'busy' && code !== 'already-analyzed') failure = { code, detail: '' }
|
|
1242
1342
|
}
|
|
1243
1343
|
if (failure !== null) store.error = failure
|
|
1244
1344
|
if (result.profile !== null && typeof result.profile === 'object') store.profile = result.profile
|
|
@@ -1568,14 +1668,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1568
1668
|
}
|
|
1569
1669
|
|
|
1570
1670
|
/**
|
|
1571
|
-
* Billed tokens in one bucket set
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1671
|
+
* Billed tokens in one bucket set, split the way they are priced:
|
|
1672
|
+
* everything the prompt cost (uncached input, cache reads, cache writes)
|
|
1673
|
+
* against what came back. `reasoningTokens` is a subset of `outputTokens`,
|
|
1674
|
+
* so adding it would count every reasoning token twice.
|
|
1574
1675
|
*/
|
|
1575
|
-
function
|
|
1576
|
-
if (buckets === null || typeof buckets !== 'object') return 0
|
|
1676
|
+
function tokensInOut(buckets) {
|
|
1677
|
+
if (buckets === null || typeof buckets !== 'object') return { input: 0, output: 0 }
|
|
1577
1678
|
const num = (value) => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0)
|
|
1578
|
-
return
|
|
1679
|
+
return {
|
|
1680
|
+
input: num(buckets.inputTokens) + num(buckets.cacheReadTokens) + num(buckets.cacheWriteTokens),
|
|
1681
|
+
output: num(buckets.outputTokens),
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/** Both halves of `tokensInOut` at once. */
|
|
1686
|
+
function tokensTotal(buckets) {
|
|
1687
|
+
const split = tokensInOut(buckets)
|
|
1688
|
+
return split.input + split.output
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/** `tokensInOut` as the one line the tiles and the run rows both show. */
|
|
1692
|
+
function fmtTokensSplit(buckets) {
|
|
1693
|
+
const split = tokensInOut(buckets)
|
|
1694
|
+
return 'in ' + fmtTokens(split.input) + ' · out ' + fmtTokens(split.output)
|
|
1579
1695
|
}
|
|
1580
1696
|
|
|
1581
1697
|
function usageNum(value) {
|
|
@@ -1593,7 +1709,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1593
1709
|
}
|
|
1594
1710
|
}
|
|
1595
1711
|
|
|
1596
|
-
/**
|
|
1712
|
+
/**
|
|
1713
|
+
* One period card, zero-filled; the two averages stay nullable.
|
|
1714
|
+
* `failedUsd` and `repairUsd` overlap on a failed repair, so they can sum
|
|
1715
|
+
* past `failedOrRepairUsd`, which counts that union once.
|
|
1716
|
+
*/
|
|
1597
1717
|
function usagePeriod(value) {
|
|
1598
1718
|
const source = value !== null && typeof value === 'object' ? value : {}
|
|
1599
1719
|
return {
|
|
@@ -1601,8 +1721,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1601
1721
|
billedCalls: usageNum(source.billedCalls),
|
|
1602
1722
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1603
1723
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1724
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1604
1725
|
tokens: usageTokens(source.tokens),
|
|
1605
1726
|
usdKnown: usageNum(source.usdKnown),
|
|
1727
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1728
|
+
repairUsd: usageNum(source.repairUsd),
|
|
1729
|
+
failedOrRepairUsd: usageNum(source.failedOrRepairUsd),
|
|
1606
1730
|
avgAnalysisUsd: typeof source.avgAnalysisUsd === 'number' && Number.isFinite(source.avgAnalysisUsd) ? source.avgAnalysisUsd : null,
|
|
1607
1731
|
cachedInputRate: typeof source.cachedInputRate === 'number' && Number.isFinite(source.cachedInputRate) ? source.cachedInputRate : null,
|
|
1608
1732
|
}
|
|
@@ -1639,8 +1763,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1639
1763
|
billedCalls: usageNum(source.billedCalls),
|
|
1640
1764
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1641
1765
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1766
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1642
1767
|
tokens: usageTokens(source.tokens),
|
|
1643
1768
|
usdKnown: usageNum(source.usdKnown),
|
|
1769
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1644
1770
|
trigger: text('trigger'),
|
|
1645
1771
|
startedAt: usageNum(source.startedAt),
|
|
1646
1772
|
endedAt: usageNum(source.endedAt),
|
|
@@ -1661,6 +1787,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1661
1787
|
if (value === null || typeof value !== 'object' || value.ok !== true) return null
|
|
1662
1788
|
const runs = value.runs !== null && typeof value.runs === 'object' ? value.runs : {}
|
|
1663
1789
|
const warnings = value.warnings !== null && typeof value.warnings === 'object' ? value.warnings : {}
|
|
1790
|
+
const auto = value.auto !== null && typeof value.auto === 'object' ? value.auto : {}
|
|
1664
1791
|
return {
|
|
1665
1792
|
trackingSince: usageNum(value.trackingSince),
|
|
1666
1793
|
pricing: value.pricing !== null && typeof value.pricing === 'object' ? value.pricing : {},
|
|
@@ -1671,9 +1798,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1671
1798
|
lifetime: usagePeriod(value.lifetime),
|
|
1672
1799
|
byType: usageBuckets(value.byType),
|
|
1673
1800
|
byModel: usageBuckets(value.byModel),
|
|
1801
|
+
byProvider: usageBuckets(value.byProvider),
|
|
1802
|
+
byTrigger: usageBuckets(value.byTrigger),
|
|
1674
1803
|
series7: usageSeries(value.series7),
|
|
1675
1804
|
series30: usageSeries(value.series30),
|
|
1676
1805
|
warnings: { daily: usageWarning(warnings.daily), monthly: usageWarning(warnings.monthly) },
|
|
1806
|
+
auto: { today: usageNum(auto.today), budget: usageNum(auto.budget) },
|
|
1677
1807
|
runs: {
|
|
1678
1808
|
items: (Array.isArray(runs.items) ? runs.items : []).map(usageRunItem),
|
|
1679
1809
|
page: usageNum(runs.page) > 0 ? Math.floor(usageNum(runs.page)) : 1,
|
|
@@ -1751,7 +1881,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1751
1881
|
h('span', { className: 'tacit-progress-count' }, autoOn ? t('status.autoOn', { today: String(today), budget: String(budget) }) : '')),
|
|
1752
1882
|
h('div', { className: 'tacit-progress-text' }, autoOn ? t('status.autoHint') : t('status.autoOff')),
|
|
1753
1883
|
hasTrend
|
|
1754
|
-
? h('div', { className: 'tacit-trend' },
|
|
1884
|
+
? h('div', { className: 'tacit-trend', title: t('status.trendDocs') },
|
|
1885
|
+
h('span', { className: 'tacit-chip' }, t('status.trendCorrections', { a: pct(trend.early.correctionRate), b: pct(trend.recent.correctionRate) })),
|
|
1755
1886
|
h('span', { className: 'tacit-chip' }, t('status.trendMessy', { a: pct(trend.early.messyRate), b: pct(trend.recent.messyRate) })),
|
|
1756
1887
|
h('span', { className: 'tacit-chip' }, t('status.trendTokens', { a: fmt(trend.early.tokensPerTurn), b: fmt(trend.recent.tokensPerTurn) })),
|
|
1757
1888
|
h('span', { className: 'tacit-progress-text' }, t('status.trendHint', { n: String(trend.window) })))
|
|
@@ -2249,42 +2380,67 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2249
2380
|
*
|
|
2250
2381
|
* It is captured by the store action that opens the dialog rather than by
|
|
2251
2382
|
* the dialog itself: React focuses the `autoFocus`ed Cancel button while it
|
|
2252
|
-
* commits, so by the time
|
|
2383
|
+
* commits, so by the time the dialog is mounted the opener is already gone.
|
|
2253
2384
|
*/
|
|
2254
2385
|
let confirmOpener = null
|
|
2255
2386
|
|
|
2387
|
+
/**
|
|
2388
|
+
* The two focus stops of the dialog, addressed by id rather than by ref:
|
|
2389
|
+
* refs would add hooks to a component whose hook count must not move.
|
|
2390
|
+
*/
|
|
2391
|
+
const CONFIRM_CANCEL_ID = 'tacit-confirm-cancel'
|
|
2392
|
+
const CONFIRM_ACCEPT_ID = 'tacit-confirm-accept'
|
|
2393
|
+
|
|
2256
2394
|
function captureConfirmOpener() {
|
|
2257
2395
|
confirmOpener = typeof document !== 'undefined' && document !== null ? document.activeElement : null
|
|
2258
2396
|
}
|
|
2259
2397
|
|
|
2398
|
+
/**
|
|
2399
|
+
* Hand focus back one macrotask after the close. The host's Settings modal
|
|
2400
|
+
* moves focus on the same Escape, so a restore that runs inside the close is
|
|
2401
|
+
* overwritten and the user lands on the host's Close button instead.
|
|
2402
|
+
*/
|
|
2403
|
+
function restoreConfirmOpener() {
|
|
2404
|
+
const opener = confirmOpener
|
|
2405
|
+
confirmOpener = null
|
|
2406
|
+
if (opener === null || typeof opener.focus !== 'function') return
|
|
2407
|
+
setTimeout(() => opener.focus(), 0)
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2260
2410
|
/**
|
|
2261
2411
|
* One small modal confirmation, rendered unconditionally by its owner (and
|
|
2262
2412
|
* `null` when closed) so React's hook count never changes between renders.
|
|
2263
|
-
* Hook-free
|
|
2264
|
-
* with markup alone.
|
|
2413
|
+
* Hook-free: focus is restored by `closeConfirm`, the one path out.
|
|
2265
2414
|
*
|
|
2266
2415
|
* Cancel comes first and takes focus: the safe answer is the one a keyboard
|
|
2267
2416
|
* or screen-reader user reaches without looking.
|
|
2268
2417
|
*/
|
|
2269
2418
|
function ConfirmDialog(kit, { open, title, body, confirmLabel, onConfirm, onCancel }) {
|
|
2270
2419
|
const { t } = kit
|
|
2271
|
-
|
|
2272
|
-
useEffect(() => {
|
|
2273
|
-
if (!isOpen || typeof document === 'undefined') return undefined
|
|
2274
|
-
const opener = confirmOpener
|
|
2275
|
-
return () => {
|
|
2276
|
-
if (opener !== null && typeof opener.focus === 'function') opener.focus()
|
|
2277
|
-
}
|
|
2278
|
-
}, [isOpen])
|
|
2279
|
-
if (!isOpen) return null
|
|
2420
|
+
if (open !== true) return null
|
|
2280
2421
|
const cancel = typeof onCancel === 'function' ? onCancel : () => {}
|
|
2281
2422
|
return h('div', {
|
|
2282
2423
|
className: 'tacit-modal-backdrop',
|
|
2283
2424
|
onClick: cancel,
|
|
2284
|
-
// Escape
|
|
2285
|
-
// bubbles
|
|
2425
|
+
// Escape and Tab are both handled here: a keydown from either button
|
|
2426
|
+
// bubbles to the backdrop. With exactly two stops in the dialog,
|
|
2427
|
+
// forward and backward tabbing both land on the other button, so
|
|
2428
|
+
// `shiftKey` needs no branch of its own.
|
|
2286
2429
|
onKeyDown: (event) => {
|
|
2287
|
-
if (event.key === 'Escape')
|
|
2430
|
+
if (event.key === 'Escape') {
|
|
2431
|
+
// Stopped here: the host's Settings modal also closes on Escape, and
|
|
2432
|
+
// cancelling a confirm must not take the whole settings page with it.
|
|
2433
|
+
if (typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
2434
|
+
cancel()
|
|
2435
|
+
return
|
|
2436
|
+
}
|
|
2437
|
+
if (event.key !== 'Tab' || typeof document === 'undefined' || document === null) return
|
|
2438
|
+
const cancelButton = document.getElementById(CONFIRM_CANCEL_ID)
|
|
2439
|
+
const acceptButton = document.getElementById(CONFIRM_ACCEPT_ID)
|
|
2440
|
+
if (cancelButton === null || acceptButton === null) return
|
|
2441
|
+
const next = document.activeElement === acceptButton ? cancelButton : acceptButton
|
|
2442
|
+
event.preventDefault()
|
|
2443
|
+
if (typeof next.focus === 'function') next.focus()
|
|
2288
2444
|
},
|
|
2289
2445
|
},
|
|
2290
2446
|
h('div', {
|
|
@@ -2298,17 +2454,76 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2298
2454
|
h('h3', { className: 'tacit-modal-title', id: 'tacit-confirm-title' }, title),
|
|
2299
2455
|
h('p', { className: 'tacit-confirm-body' }, body),
|
|
2300
2456
|
h('div', { className: 'tacit-confirm-actions' },
|
|
2301
|
-
h('button', { type: 'button', className: 'tacit-btn', autoFocus: true, onClick: cancel }, t('confirm.cancel')),
|
|
2302
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-danger', onClick: onConfirm }, confirmLabel))))
|
|
2457
|
+
h('button', { type: 'button', id: CONFIRM_CANCEL_ID, className: 'tacit-btn', autoFocus: true, onClick: cancel }, t('confirm.cancel')),
|
|
2458
|
+
h('button', { type: 'button', id: CONFIRM_ACCEPT_ID, className: 'tacit-btn tacit-btn-danger', onClick: onConfirm }, confirmLabel))))
|
|
2303
2459
|
}
|
|
2304
2460
|
|
|
2305
2461
|
// ── Global panel (Settings → Tacit section) ────────────────────────────
|
|
2306
2462
|
|
|
2307
2463
|
function DirectivesEditor(kit) {
|
|
2308
2464
|
const { t } = kit
|
|
2465
|
+
const fmtDay = (ms) => {
|
|
2466
|
+
const date = new Date(Number(ms))
|
|
2467
|
+
return !(Number(ms) > 0) || Number.isNaN(date.getTime()) ? t('steer.receiptNever') : date.toISOString().slice(0, 10)
|
|
2468
|
+
}
|
|
2469
|
+
const pct = (value) => (typeof value === 'number' && value >= 0 ? Math.round(value * 100) + '%' : t('steer.receiptNever'))
|
|
2470
|
+
const triggerName = (name) => {
|
|
2471
|
+
const key = 'trigger.' + name
|
|
2472
|
+
const label = t(key)
|
|
2473
|
+
return label === key ? name : label
|
|
2474
|
+
}
|
|
2475
|
+
const copyReceipt = (receipt) => {
|
|
2476
|
+
if (typeof navigator === 'undefined' || navigator === null || !navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') return
|
|
2477
|
+
navigator.clipboard.writeText(JSON.stringify(receipt, null, 2)).catch(() => {})
|
|
2478
|
+
}
|
|
2479
|
+
const receiptRow = (label, value) => [h('dt', { key: label + '-t' }, label), h('dd', { key: label + '-d' }, value)]
|
|
2480
|
+
/** What the receipt route returned for this directive, as definition rows; never any prompt text. */
|
|
2481
|
+
function Receipt(receipt) {
|
|
2482
|
+
if (receipt === null || typeof receipt === 'object' === false) return h('div', { className: 'tacit-panel-hint' }, t('steer.receiptLoading'))
|
|
2483
|
+
const triggers = Object.entries(receipt.triggers !== null && typeof receipt.triggers === 'object' ? receipt.triggers : {})
|
|
2484
|
+
.map(([name, count]) => triggerName(name) + ' ' + String(count)).join(' · ')
|
|
2485
|
+
const evidence = receipt.evidence !== null && typeof receipt.evidence === 'object' ? receipt.evidence : { turns: 0, conversations: 0 }
|
|
2486
|
+
const cost = receipt.cost !== null && typeof receipt.cost === 'object' ? receipt.cost : { usd: null, calls: 0 }
|
|
2487
|
+
const trial = receipt.trial !== null && typeof receipt.trial === 'object' ? receipt.trial : null
|
|
2488
|
+
return h('div', null,
|
|
2489
|
+
h('dl', { className: 'tacit-receipt' },
|
|
2490
|
+
...receiptRow(t('steer.receiptId'), String(receipt.id)),
|
|
2491
|
+
...receiptRow(t('steer.receiptScope'), typeof receipt.scope === 'string' && receipt.scope.length > 0 ? receipt.scope : t('steer.everywhere')),
|
|
2492
|
+
...receiptRow(t('steer.receiptStatus'), String(receipt.status)),
|
|
2493
|
+
...receiptRow(t('steer.receiptSource'), receipt.source === 'user' ? t('steer.user') : t('steer.distilled')),
|
|
2494
|
+
...receiptRow(t('steer.receiptCreated'), fmtDay(receipt.createdAt)),
|
|
2495
|
+
...receiptRow(t('steer.receiptUpdated'), fmtDay(receipt.updatedAt)),
|
|
2496
|
+
...receiptRow(t('steer.receiptEvaluated'), fmtDay(receipt.evaluatedAt)),
|
|
2497
|
+
...receiptRow(t('steer.receiptVersion'), String(receipt.version)),
|
|
2498
|
+
...receiptRow(t('steer.receiptTrial'), trial === null ? t('steer.receiptNever') : t('steer.receiptTrialValue', {
|
|
2499
|
+
turns: String(trial.turns), started: fmtDay(trial.startedAt), messy: pct(trial.baselineMessyRate), corrected: pct(trial.baselineCorrectionRate) })),
|
|
2500
|
+
...receiptRow(t('steer.receiptTriggers'), triggers.length > 0 ? triggers : t('steer.receiptNever')),
|
|
2501
|
+
...receiptRow(t('steer.receiptEvidence'), t('steer.receiptEvidenceValue', { turns: String(evidence.turns), conversations: String(evidence.conversations) })),
|
|
2502
|
+
...receiptRow(t('steer.receiptCost'), cost.usd === null ? t('steer.receiptNoRun') : t('steer.receiptCostValue', { usd: fmtUsd(cost.usd), calls: String(cost.calls) }))),
|
|
2503
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => copyReceipt(receipt) }, t('steer.receiptCopy')))
|
|
2504
|
+
}
|
|
2309
2505
|
return function DirectivesEditorView(props) {
|
|
2310
|
-
const { config,
|
|
2506
|
+
const { config, steering } = props
|
|
2507
|
+
const directives = (Array.isArray(props.directives) ? props.directives : []).filter((entry) => entry.status !== 'removed')
|
|
2508
|
+
const receipts = rootStore.receipts !== null && typeof rootStore.receipts === 'object' ? rootStore.receipts : {}
|
|
2509
|
+
const reviewOn = config !== null && config.reviewCandidates === true
|
|
2311
2510
|
const workspaces = Array.isArray(props.workspaces) ? props.workspaces : []
|
|
2511
|
+
const seenAt = props.seenAt !== null && typeof props.seenAt === 'object' ? props.seenAt : {}
|
|
2512
|
+
const liveScope = (scope) => workspaces.some((entry) => entry.cwd === scope || String(entry.cwd).startsWith(scope + '/'))
|
|
2513
|
+
const notSeenSince = (entry) => {
|
|
2514
|
+
const stamp = typeof seenAt[entry.workspace] === 'number' ? seenAt[entry.workspace] : entry.createdAt
|
|
2515
|
+
const date = new Date(Number(stamp))
|
|
2516
|
+
return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)
|
|
2517
|
+
}
|
|
2518
|
+
const scopeOptions = (current) => {
|
|
2519
|
+
const options = [
|
|
2520
|
+
h('option', { key: '-', value: '-' }, t('steer.moveTo')),
|
|
2521
|
+
h('option', { key: '', value: '' }, t('steer.everywhere')),
|
|
2522
|
+
]
|
|
2523
|
+
for (const entry of workspaces) options.push(h('option', { key: entry.cwd, value: entry.cwd }, entry.label))
|
|
2524
|
+
if (current.length > 0 && !workspaces.some((entry) => entry.cwd === current)) options.push(h('option', { key: current, value: current }, labelOf(current)))
|
|
2525
|
+
return options
|
|
2526
|
+
}
|
|
2312
2527
|
const [draft, setDraft] = useState('')
|
|
2313
2528
|
const [scope, setScope] = useState('')
|
|
2314
2529
|
const labelOf = (cwd) => {
|
|
@@ -2331,6 +2546,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2331
2546
|
setEnrichOn(next)
|
|
2332
2547
|
updateRootConfig({ enrichPrompts: next })
|
|
2333
2548
|
}
|
|
2549
|
+
const toggleReview = () => updateRootConfig({ reviewCandidates: !reviewOn })
|
|
2334
2550
|
const onAdd = () => {
|
|
2335
2551
|
const text = draft.trim()
|
|
2336
2552
|
if (text.length === 0) return
|
|
@@ -2346,10 +2562,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2346
2562
|
h('div', { className: 'tacit-settings-row' },
|
|
2347
2563
|
h('label', { className: 'tacit-settings-label' }, t('steer.enrich')),
|
|
2348
2564
|
h('input', { type: 'checkbox', checked: enrichOn, onChange: toggleEnrich })),
|
|
2565
|
+
h('div', { className: 'tacit-settings-row' },
|
|
2566
|
+
h('label', { className: 'tacit-settings-label' }, t('steer.review')),
|
|
2567
|
+
h('input', { type: 'checkbox', checked: reviewOn, onChange: toggleReview })),
|
|
2349
2568
|
directives.length === 0
|
|
2350
2569
|
? h('div', { className: 'tacit-empty' }, t('steer.empty'))
|
|
2351
2570
|
: h('div', { className: 'tacit-rules-list' },
|
|
2352
|
-
directives.map((entry) => h('div', { key: entry.id, className: 'tacit-
|
|
2571
|
+
directives.map((entry) => h('div', { key: entry.id, className: 'tacit-directive-block' },
|
|
2572
|
+
h('div', { className: 'tacit-rule tacit-directive' + (entry.enabled === false ? ' tacit-directive-off' : '') },
|
|
2353
2573
|
h('input', {
|
|
2354
2574
|
type: 'checkbox',
|
|
2355
2575
|
className: 'tacit-check',
|
|
@@ -2362,15 +2582,40 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2362
2582
|
typeof entry.workspace === 'string' && entry.workspace.length > 0
|
|
2363
2583
|
? h('span', { className: 'tacit-chip tacit-chip-scope', title: entry.workspace }, t('steer.workspace', { name: labelOf(entry.workspace) }))
|
|
2364
2584
|
: null,
|
|
2365
|
-
entry.
|
|
2366
|
-
? h('span', { className: 'tacit-chip tacit-chip-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
}))
|
|
2370
|
-
: entry.status === '
|
|
2371
|
-
? h('span', { className: 'tacit-chip tacit-chip-
|
|
2372
|
-
|
|
2373
|
-
|
|
2585
|
+
typeof entry.workspace === 'string' && entry.workspace.length > 0 && !liveScope(entry.workspace)
|
|
2586
|
+
? h('span', { className: 'tacit-chip tacit-chip-muted', title: entry.workspace }, t('steer.notSeen', { date: notSeenSince(entry) }))
|
|
2587
|
+
: null,
|
|
2588
|
+
entry.status === 'queued'
|
|
2589
|
+
? h('span', { className: 'tacit-chip tacit-chip-muted' }, t('steer.queued'))
|
|
2590
|
+
: entry.status === 'candidate'
|
|
2591
|
+
? h('span', { className: 'tacit-chip tacit-chip-trial' }, t('steer.trial', {
|
|
2592
|
+
n: String(entry.trial !== null && typeof entry.trial === 'object' && typeof entry.trial.turns === 'number' ? entry.trial.turns : 0),
|
|
2593
|
+
total: String(config !== null && typeof config.directiveTrialTurns === 'number' ? config.directiveTrialTurns : 10),
|
|
2594
|
+
}))
|
|
2595
|
+
: entry.status === 'retired'
|
|
2596
|
+
? h('span', { className: 'tacit-chip tacit-chip-warn' }, t('steer.retired', { reason: String(entry.retiredReason || '') }))
|
|
2597
|
+
: h('span', { className: 'tacit-chip tacit-chip-ok' }, t('steer.active')),
|
|
2598
|
+
reviewOn && entry.status === 'queued' && !(entry.approvedAt > 0)
|
|
2599
|
+
? h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => editDirectives({ action: 'start-trial', id: entry.id }) }, t('steer.startTrial'))
|
|
2600
|
+
: null,
|
|
2601
|
+
h('select', {
|
|
2602
|
+
className: 'tacit-input tacit-select tacit-select-sm',
|
|
2603
|
+
'aria-label': t('steer.moveTo'),
|
|
2604
|
+
onChange: (event) => {
|
|
2605
|
+
if (event.target.value !== '-') editDirectives({ action: 'rescope', id: entry.id, workspace: event.target.value })
|
|
2606
|
+
},
|
|
2607
|
+
}, ...scopeOptions(typeof entry.workspace === 'string' ? entry.workspace : '')),
|
|
2608
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => editDirectives({ action: 'remove', id: entry.id }) }, t('steer.remove'))),
|
|
2609
|
+
h('details', {
|
|
2610
|
+
className: 'tacit-preview',
|
|
2611
|
+
'data-testid': 'tacit-receipt',
|
|
2612
|
+
'data-directive-id': entry.id,
|
|
2613
|
+
onToggle: (event) => {
|
|
2614
|
+
if (event.target.open) fetchReceipt(entry.id)
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
h('summary', null, t('steer.receipt')),
|
|
2618
|
+
Receipt(receipts[entry.id] ?? null))))),
|
|
2374
2619
|
h('div', { className: 'tacit-settings-row' },
|
|
2375
2620
|
h('input', {
|
|
2376
2621
|
className: 'tacit-input tacit-directive-input',
|
|
@@ -2515,9 +2760,15 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2515
2760
|
const confirm = rootStore.confirm !== null && typeof rootStore.confirm === 'object' && rootStore.confirm.kind === 'usage'
|
|
2516
2761
|
? 'usage'
|
|
2517
2762
|
: (rootStore.confirm !== null && typeof rootStore.confirm === 'object' && rootStore.confirm.kind === 'reports' ? 'reports' : null)
|
|
2518
|
-
const retention = config !== null && typeof config.costHistoryDays === 'number' &&
|
|
2763
|
+
const retention = config !== null && typeof config.costHistoryDays === 'number' && Number.isFinite(config.costHistoryDays) && config.costHistoryDays > 0
|
|
2519
2764
|
? config.costHistoryDays
|
|
2520
2765
|
: 30
|
|
2766
|
+
// A value set by hand in the YAML need not be one of the offered days.
|
|
2767
|
+
// It gets an option of its own, in place, rather than being displayed
|
|
2768
|
+
// as a neighbour it is not.
|
|
2769
|
+
const retentionDays = RETENTION_DAYS.includes(retention)
|
|
2770
|
+
? RETENTION_DAYS
|
|
2771
|
+
: [...RETENTION_DAYS, retention].sort((a, b) => a - b)
|
|
2521
2772
|
/** Every card is titled by `card.<id>` and driven by `rootStore.sections`. */
|
|
2522
2773
|
const card = (id, children, extra) => SectionCard(kit, {
|
|
2523
2774
|
id,
|
|
@@ -2584,7 +2835,6 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2584
2835
|
card('pricing', [
|
|
2585
2836
|
PricingCard(kit, {
|
|
2586
2837
|
pricing: usagePricing,
|
|
2587
|
-
open: sections.pricing === true,
|
|
2588
2838
|
refreshing: rootStore.pricingRefreshing === true,
|
|
2589
2839
|
onRefresh: () => refreshPricing(t),
|
|
2590
2840
|
}),
|
|
@@ -2618,7 +2868,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2618
2868
|
]),
|
|
2619
2869
|
card('guidance', [
|
|
2620
2870
|
h(DirectivesEditorView, {
|
|
2621
|
-
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering
|
|
2871
|
+
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering,
|
|
2872
|
+
seenAt: rootStore.profile !== null && typeof rootStore.profile === 'object' ? rootStore.profile.workspaceSeenAt : null }),
|
|
2622
2873
|
]),
|
|
2623
2874
|
card('improve', [
|
|
2624
2875
|
h('div', { className: 'tacit-settings-row' },
|
|
@@ -2661,7 +2912,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2661
2912
|
value: String(retention),
|
|
2662
2913
|
onChange: (event) => updateRootConfig({ costHistoryDays: Number(event.target.value) }),
|
|
2663
2914
|
},
|
|
2664
|
-
...
|
|
2915
|
+
...retentionDays.map((days) => h('option', { key: days, value: String(days) }, String(days))))),
|
|
2665
2916
|
h('div', { className: 'tacit-settings-row' },
|
|
2666
2917
|
h('label', { className: 'tacit-settings-label', htmlFor: 'tacit-warn-daily' }, t('privacy.warnDaily')),
|
|
2667
2918
|
h('input', {
|
|
@@ -2678,7 +2929,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2678
2929
|
className: 'tacit-btn tacit-btn-sm',
|
|
2679
2930
|
// Two identically labelled buttons in one card: the accessible
|
|
2680
2931
|
// name has to say which threshold each one applies.
|
|
2681
|
-
'aria-label': t('privacy.
|
|
2932
|
+
'aria-label': t('privacy.applyTo', { action: t('privacy.apply'), field: t('privacy.warnDaily') }),
|
|
2682
2933
|
onClick: () => applyWarn('costWarnDailyUsd', dailyText, setDailyText),
|
|
2683
2934
|
}, t('privacy.apply'))),
|
|
2684
2935
|
h('div', { className: 'tacit-settings-row' },
|
|
@@ -2697,7 +2948,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2697
2948
|
className: 'tacit-btn tacit-btn-sm',
|
|
2698
2949
|
// Two identically labelled buttons in one card: the accessible
|
|
2699
2950
|
// name has to say which threshold each one applies.
|
|
2700
|
-
'aria-label': t('privacy.
|
|
2951
|
+
'aria-label': t('privacy.applyTo', { action: t('privacy.apply'), field: t('privacy.warnMonthly') }),
|
|
2701
2952
|
onClick: () => applyWarn('costWarnMonthlyUsd', monthlyText, setMonthlyText),
|
|
2702
2953
|
}, t('privacy.apply'))),
|
|
2703
2954
|
h('p', { className: 'tacit-panel-hint' }, t('privacy.warnHint')),
|
|
@@ -2744,7 +2995,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2744
2995
|
/** Every run type and op the ledger can tag, in the order the filters list them. */
|
|
2745
2996
|
const USAGE_RUN_TYPES = ['bootstrap', 'analysis', 'analysis-batch', 'improve', 'directive-distillation', 'style-distillation', 'prompt-enrichment']
|
|
2746
2997
|
const USAGE_RANGES = ['today', '7d', '30d', 'month', 'all']
|
|
2747
|
-
const USAGE_STATUSES = ['success', 'partial', 'failed']
|
|
2998
|
+
const USAGE_STATUSES = ['running', 'success', 'partial', 'failed']
|
|
2748
2999
|
const USAGE_COLUMNS = ['time', 'op', 'scope', 'model', 'status', 'calls', 'tokens', 'cost']
|
|
2749
3000
|
|
|
2750
3001
|
/** One label/value tile; `note` is the small "n unpriced" line under the value. */
|
|
@@ -2755,10 +3006,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2755
3006
|
typeof note === 'string' && note.length > 0 ? h('span', { className: 'tacit-tile-note' }, note) : null)
|
|
2756
3007
|
}
|
|
2757
3008
|
|
|
2758
|
-
/** Money tile: the period's spend
|
|
2759
|
-
function SpendTile(kit, id, label, totals) {
|
|
2760
|
-
const
|
|
2761
|
-
|
|
3009
|
+
/** Money tile: the period's spend, its unpriced-call footnote, and any second note that period carries. */
|
|
3010
|
+
function SpendTile(kit, id, label, totals, extraNote) {
|
|
3011
|
+
const notes = []
|
|
3012
|
+
if (totals.unpricedCalls > 0) notes.push(kit.t('usage.unpricedShort', { n: String(totals.unpricedCalls) }))
|
|
3013
|
+
if (typeof extraNote === 'string' && extraNote.length > 0) notes.push(extraNote)
|
|
3014
|
+
return UsageTile(id, label, usageSpend(kit, totals), notes.join(' · '))
|
|
2762
3015
|
}
|
|
2763
3016
|
|
|
2764
3017
|
/**
|
|
@@ -2821,18 +3074,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2821
3074
|
seriesButton('30', t('usage.series30'))))
|
|
2822
3075
|
}
|
|
2823
3076
|
|
|
2824
|
-
/**
|
|
2825
|
-
|
|
3077
|
+
/**
|
|
3078
|
+
* `trigger` is a free string on the wire, so a host that starts tagging
|
|
3079
|
+
* runs with something this dictionary has never heard of shows the tag
|
|
3080
|
+
* itself rather than a raw `trigger.<key>` lookup miss.
|
|
3081
|
+
*/
|
|
3082
|
+
function triggerLabel(t, trigger) {
|
|
3083
|
+
const key = 'trigger.' + trigger
|
|
3084
|
+
const label = t(key)
|
|
3085
|
+
return label === key ? trigger : label
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
/**
|
|
3089
|
+
* Spend across one bucket record, costliest first, each row ending in a
|
|
3090
|
+
* share bar. `label` turns a bucket key into its display name, which is a
|
|
3091
|
+
* dictionary lookup for the closed sets and the raw key for the open ones.
|
|
3092
|
+
*/
|
|
3093
|
+
function UsageBreakdown(kit, { buckets, title, label }) {
|
|
2826
3094
|
const { t } = kit
|
|
2827
|
-
const rows = Object.entries(
|
|
3095
|
+
const rows = Object.entries(buckets).sort((a, b) => b[1].usdKnown - a[1].usdKnown)
|
|
2828
3096
|
if (rows.length === 0) return null
|
|
2829
3097
|
const top = rows[0][1].usdKnown
|
|
2830
|
-
return h('div', { className: 'tacit-usage-breakdown' },
|
|
2831
|
-
h('div', { className: 'tacit-report-title' }, t(
|
|
3098
|
+
return h('div', { key: title, className: 'tacit-usage-breakdown' },
|
|
3099
|
+
h('div', { className: 'tacit-report-title' }, t(title)),
|
|
2832
3100
|
rows.map(([type, totals]) => {
|
|
2833
3101
|
const share = top > 0 ? Math.round((totals.usdKnown / top) * 100) : 0
|
|
2834
3102
|
return h('div', { key: type, className: 'tacit-breakdown-row' },
|
|
2835
|
-
h('span', { className: 'tacit-breakdown-name' },
|
|
3103
|
+
h('span', { className: 'tacit-breakdown-name' }, label(type)),
|
|
2836
3104
|
h('span', { className: 'tacit-breakdown-calls' }, fmtTokens(totals.billedCalls)),
|
|
2837
3105
|
h('span', { className: 'tacit-breakdown-tokens' }, fmtTokens(tokensTotal(totals.tokens))),
|
|
2838
3106
|
h('span', { className: 'tacit-breakdown-usd' }, usageSpend(kit, totals)),
|
|
@@ -2942,7 +3210,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2942
3210
|
? run.attempts.filter((attempt) => attempt !== null && typeof attempt === 'object')
|
|
2943
3211
|
: null
|
|
2944
3212
|
return h('div', { className: 'tacit-usage-attempts', id: 'tacit-run-' + runId, role: 'row' },
|
|
2945
|
-
h('div', { className: 'tacit-usage-cell tacit-usage-attempts-cell', role: 'cell' },
|
|
3213
|
+
h('div', { className: 'tacit-usage-cell tacit-usage-attempts-cell', role: 'cell', 'aria-colspan': USAGE_COLUMNS.length },
|
|
2946
3214
|
attempts === null
|
|
2947
3215
|
? h('p', { className: 'tacit-panel-hint' }, t('usage.loading'))
|
|
2948
3216
|
: attempts.map((attempt) => AttemptRow(kit, attempt))))
|
|
@@ -2951,9 +3219,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2951
3219
|
/** One run row; the first cell is the disclosure button for its attempts. */
|
|
2952
3220
|
function RunRow(kit, { item, open, onToggleRun }) {
|
|
2953
3221
|
const { t, fmtTime } = kit
|
|
3222
|
+
// A bootstrap run carries no workspace because it covers every session,
|
|
3223
|
+
// which is worth saying; any other run type missing the field is simply
|
|
3224
|
+
// a row with nothing to scope by.
|
|
3225
|
+
const unscoped = item.type === 'bootstrap' ? t('usage.scopeAll') : '—'
|
|
2954
3226
|
const scope = item.workspace.length > 0 && item.turn !== null
|
|
2955
3227
|
? item.workspace + ' · #' + String(item.turn)
|
|
2956
|
-
: (item.workspace.length > 0 ? item.workspace : (item.turn !== null ? '#' + String(item.turn) :
|
|
3228
|
+
: (item.workspace.length > 0 ? item.workspace : (item.turn !== null ? '#' + String(item.turn) : unscoped))
|
|
2957
3229
|
const cell = (key, child) => h('span', { key, className: 'tacit-usage-cell', role: 'cell' }, child)
|
|
2958
3230
|
return h('div', { className: 'tacit-usage-row', role: 'row' },
|
|
2959
3231
|
cell('time', h('button', {
|
|
@@ -2968,7 +3240,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2968
3240
|
cell('model', item.model.length > 0 ? item.model : '—'),
|
|
2969
3241
|
cell('status', h('span', { className: 'tacit-chip tacit-status-' + item.status }, t('status.' + item.status))),
|
|
2970
3242
|
cell('calls', fmtTokens(item.billedCalls)),
|
|
2971
|
-
cell('tokens',
|
|
3243
|
+
cell('tokens', h('span', { className: 'tacit-usage-tokens' },
|
|
3244
|
+
h('span', null, fmtTokens(tokensTotal(item.tokens))),
|
|
3245
|
+
h('span', { className: 'tacit-usage-split' }, fmtTokensSplit(item.tokens)))),
|
|
2972
3246
|
cell('cost', usageSpend(kit, item)))
|
|
2973
3247
|
}
|
|
2974
3248
|
|
|
@@ -3029,19 +3303,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3029
3303
|
return h('div', { className: 'tacit-usage' },
|
|
3030
3304
|
h('p', { className: 'tacit-panel-hint' }, t('usage.label')),
|
|
3031
3305
|
h('div', { className: 'tacit-tiles' },
|
|
3032
|
-
SpendTile(kit, 'today', t('usage.today'), usage.today
|
|
3306
|
+
SpendTile(kit, 'today', t('usage.today'), usage.today,
|
|
3307
|
+
t('usage.autoToday', { n: String(usage.auto.today), budget: String(usage.auto.budget) })),
|
|
3033
3308
|
SpendTile(kit, 'month', t('usage.month'), usage.month),
|
|
3034
3309
|
SpendTile(kit, 'last30', t('usage.last30'), last30),
|
|
3035
3310
|
SpendTile(kit, 'lifetime', t('usage.since', { day: since }), usage.lifetime)),
|
|
3036
3311
|
h('div', { className: 'tacit-tiles' },
|
|
3037
3312
|
UsageTile('calls', t('usage.calls'), fmtTokens(last30.billedCalls)),
|
|
3313
|
+
UsageTile('tokens', t('usage.tokens'), fmtTokensSplit(last30.tokens)),
|
|
3038
3314
|
UsageTile('avg', t('usage.avgAnalysis'), last30.avgAnalysisUsd === null
|
|
3039
3315
|
? t('usage.priceUnavailable')
|
|
3040
3316
|
: fmtUsd(last30.avgAnalysisUsd)),
|
|
3041
3317
|
UsageTile('cached', t('usage.cachedRate'), fmtPct(last30.cachedInputRate)),
|
|
3042
|
-
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls))
|
|
3318
|
+
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls)),
|
|
3319
|
+
UsageTile('failedRepair', t('usage.failedRepair'), fmtUsd(last30.failedOrRepairUsd),
|
|
3320
|
+
t('usage.failedRepairSplit', { failed: fmtUsd(last30.failedUsd), repair: fmtUsd(last30.repairUsd) }))),
|
|
3043
3321
|
BarStrip(kit, { series: which === '7' ? usage.series7 : usage.series30, which, onSeries: props.onSeries }),
|
|
3044
|
-
UsageBreakdown(kit, {
|
|
3322
|
+
UsageBreakdown(kit, { buckets: usage.byType, title: 'usage.breakdown', label: (type) => t('runtype.' + type) }),
|
|
3323
|
+
UsageBreakdown(kit, { buckets: usage.byProvider, title: 'usage.byRoute', label: (provider) => provider }),
|
|
3324
|
+
UsageBreakdown(kit, { buckets: usage.byTrigger, title: 'usage.byTrigger', label: (trigger) => triggerLabel(t, trigger) }),
|
|
3045
3325
|
WarnBar(kit, { id: 'daily', warning: usage.warnings.daily }),
|
|
3046
3326
|
WarnBar(kit, { id: 'monthly', warning: usage.warnings.monthly }),
|
|
3047
3327
|
UsageFilters(kit, {
|
|
@@ -3149,13 +3429,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3149
3429
|
|
|
3150
3430
|
/**
|
|
3151
3431
|
* The Pricing card body. Like `UsageCard` a plain renderer, not a
|
|
3152
|
-
* component: it owns no hooks and no state. It
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3432
|
+
* component: it owns no hooks and no state. It renders whether or not its
|
|
3433
|
+
* card is open, because the `SectionCard` around it hides a collapsed body
|
|
3434
|
+
* rather than unmounting it, which is what keeps find-in-page working over
|
|
3435
|
+
* the rate table.
|
|
3155
3436
|
*/
|
|
3156
|
-
function PricingCard(kit, { pricing,
|
|
3437
|
+
function PricingCard(kit, { pricing, onRefresh, refreshing }) {
|
|
3157
3438
|
const { t, fmtTime } = kit
|
|
3158
|
-
if (open !== true) return null
|
|
3159
3439
|
const priced = pricingOf(pricing)
|
|
3160
3440
|
const models = Object.keys(priced.models).sort()
|
|
3161
3441
|
const when = priced.refreshedAt > 0
|
|
@@ -3224,6 +3504,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3224
3504
|
+ '.tacit-settings{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;margin-bottom:12px;display:flex;flex-direction:column;gap:8px}'
|
|
3225
3505
|
+ '.tacit-settings-title{font-size:12px;font-weight:600}.tacit-settings-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.tacit-settings-label{font-size:12px;color:var(--dsw-alias-label-secondary);min-width:150px}'
|
|
3226
3506
|
+ '.tacit-settings-notice{font-size:11px;color:var(--dsw-alias-state-success-primary)}'
|
|
3507
|
+
// The idle live region has to stay mounted to announce, but as an empty
|
|
3508
|
+
// flex item it still claims one of the panel's 10px gaps; -5px a side
|
|
3509
|
+
// gives that back.
|
|
3510
|
+
+ '.tacit-panel>.tacit-settings-notice:empty{margin-block:-5px}'
|
|
3227
3511
|
+ '.tacit-select,.tacit-input{font:inherit;font-size:12px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:3px 8px}.tacit-input{width:90px}'
|
|
3228
3512
|
+ '.tacit-improve-btn{font:inherit;font-size:13px;font-weight:500;line-height:20px;color:var(--dsw-alias-label-secondary);background:transparent;border:0;border-radius:24px;cursor:pointer;height:28px;padding:0 8px;white-space:nowrap;display:inline-flex;align-items:center;gap:4px}.tacit-improve-btn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}'
|
|
3229
3513
|
+ '.tacit-check{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary);flex:none;margin:2px 4px 0 0}'
|
|
@@ -3280,9 +3564,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3280
3564
|
+ '.tacit-usage-filters{display:flex;flex-direction:column;gap:6px}.tacit-filter-row{display:flex;flex-wrap:wrap;gap:8px}.tacit-filter{display:flex;flex-direction:column;gap:2px;min-width:0}.tacit-filter-label{font-size:10px;color:var(--dsw-alias-label-secondary)}'
|
|
3281
3565
|
+ '.tacit-usage-runs{display:flex;flex-direction:column;gap:6px}'
|
|
3282
3566
|
+ '.tacit-usage-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;overflow:hidden}'
|
|
3283
|
-
+ '.tacit-usage-row{display:grid;grid-template-columns:minmax(90px,1fr) minmax(90px,1.2fr) minmax(80px,1.2fr) minmax(90px,1.2fr) 5rem 4rem
|
|
3567
|
+
+ '.tacit-usage-row{display:grid;grid-template-columns:minmax(90px,1fr) minmax(90px,1.2fr) minmax(80px,1.2fr) minmax(90px,1.2fr) 5rem 4rem 7rem 5rem;gap:6px;align-items:center;padding:5px 8px;border-top:1px solid var(--dsw-alias-border-l1);font-size:11px;font-variant-numeric:tabular-nums}'
|
|
3284
3568
|
+ '.tacit-usage-table>.tacit-usage-row:first-child{border-top:0}.tacit-usage-head{color:var(--dsw-alias-label-secondary);font-weight:600;background:var(--dsw-alias-bg-layer-2)}'
|
|
3285
3569
|
+ '.tacit-usage-cell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}'
|
|
3570
|
+
// The cell is nowrap, so the in/out split needs its own block to sit under the total.
|
|
3571
|
+
+ '.tacit-usage-tokens{display:flex;flex-direction:column}.tacit-usage-split{font-size:10px;color:var(--dsw-alias-label-secondary)}'
|
|
3286
3572
|
+ '.tacit-run-toggle{width:100%;text-align:left}'
|
|
3287
3573
|
+ '.tacit-usage-attempts{border-top:1px dashed var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);padding:6px 8px}.tacit-usage-attempts-cell{display:flex;flex-direction:column;gap:4px}'
|
|
3288
3574
|
+ '.tacit-attempt{display:flex;flex-wrap:wrap;align-items:center;gap:6px;font-size:11px;color:var(--dsw-alias-label-primary)}.tacit-attempt-meta,.tacit-attempt-tokens{color:var(--dsw-alias-label-secondary)}.tacit-attempt-usd{margin-left:auto;font-variant-numeric:tabular-nums}'
|
|
@@ -3393,11 +3679,18 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3393
3679
|
refreshPricing,
|
|
3394
3680
|
openConfirm,
|
|
3395
3681
|
closeConfirm,
|
|
3682
|
+
confirmAction,
|
|
3396
3683
|
fetchBootstrapPreview,
|
|
3684
|
+
fetchReceipt,
|
|
3685
|
+
storeFor,
|
|
3686
|
+
analyzeTurn,
|
|
3687
|
+
coachSelected,
|
|
3397
3688
|
clearAllRoot,
|
|
3398
3689
|
clearUsageHistory,
|
|
3399
3690
|
runNotice,
|
|
3400
3691
|
fmtRate,
|
|
3692
|
+
fmtTokensSplit,
|
|
3693
|
+
ConfirmDialog,
|
|
3401
3694
|
UsageCard,
|
|
3402
3695
|
PricingCard,
|
|
3403
3696
|
},
|