dsh-tacit 0.4.0 → 0.6.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 +3 -1
- package/client/client.js +574 -195
- package/docs/README.zh.md +2 -1
- package/lib/analyze.js +144 -40
- package/lib/fold.js +11 -5
- package/lib/redact.js +53 -0
- package/lib/routes.js +1 -0
- package/lib/schema.js +71 -12
- package/lib/service.js +210 -51
- package/lib/store.js +2 -2
- package/lib/usage.js +57 -14
- package/lib/workspace.js +62 -0
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -53,13 +53,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
53
53
|
'status.trendCorrections': '你纠正智能体:{a} → {b}',
|
|
54
54
|
'status.trendMessy': '混乱轮次:{a} → {b}',
|
|
55
55
|
'status.trendTokens': '每轮 tokens:{a} → {b}',
|
|
56
|
-
'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',
|
|
57
58
|
'turn.enrichment': '发送前补充的上下文',
|
|
58
59
|
'trigger.auto': '自动',
|
|
59
60
|
'trigger.correction': '纠正',
|
|
60
61
|
'trigger.manual': '手动',
|
|
61
62
|
'trigger.bootstrap': '引导',
|
|
62
63
|
'trigger.good': '做对了',
|
|
64
|
+
'trigger.feedback': '反馈',
|
|
65
|
+
'trigger.send': '发送',
|
|
63
66
|
'report.strengths': '这次做对了什么',
|
|
64
67
|
'report.lesson': '经验',
|
|
65
68
|
'settings.learnGood': '也从混乱之后的顺利提示词中学习',
|
|
@@ -89,6 +92,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
89
92
|
'steer.workspace': '仅 {name}',
|
|
90
93
|
'steer.scope': '适用范围',
|
|
91
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.receiptTurns': '{n} 轮',
|
|
112
|
+
'steer.receiptTrialNote': '始于 {started} · 基线:杂乱 {messy}、纠正 {corrected}',
|
|
113
|
+
'steer.receiptCalls': '{n} 次调用',
|
|
114
|
+
'steer.enabled': '启用',
|
|
115
|
+
'steer.options': '选项',
|
|
116
|
+
'steer.receiptTriggers': '触发类别',
|
|
117
|
+
'steer.receiptEvidence': '证据',
|
|
118
|
+
'steer.receiptEvidenceValue': '{turns} 轮,来自 {conversations} 个会话',
|
|
119
|
+
'steer.receiptCost': '提炼花费',
|
|
120
|
+
'steer.moveTo': '移到工作区',
|
|
121
|
+
'steer.notSeen': '自 {date} 起未见',
|
|
92
122
|
'settings.auto': '自动分析混乱轮次',
|
|
93
123
|
'settings.budget': '每日自动分析上限',
|
|
94
124
|
settings: '设置',
|
|
@@ -167,10 +197,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
167
197
|
'usage.last30': '最近 30 天',
|
|
168
198
|
'usage.since': '自 {day} 起',
|
|
169
199
|
'usage.calls': '计费调用(30 天)',
|
|
200
|
+
'usage.tokens': 'Tokens(30 天)',
|
|
170
201
|
'usage.avgAnalysis': '单次分析中位成本',
|
|
171
202
|
'usage.cachedRate': '输入缓存命中率(30 天)',
|
|
172
203
|
'usage.unpriced': '无价目调用(30 天)',
|
|
173
204
|
'usage.unpricedShort': '{n} 次无价目',
|
|
205
|
+
'usage.failedRepair': '失败或修复调用花费(30 天)',
|
|
206
|
+
'usage.failedRepairSplit': '失败 {failed} · 修复 {repair}',
|
|
207
|
+
'usage.autoToday': '今日自动分析 {n} / {budget}',
|
|
174
208
|
'usage.priceUnavailable': '无价目',
|
|
175
209
|
'usage.chartLabel': '每日花费,最近 {n} 天',
|
|
176
210
|
'usage.chartSummary': '最近 {n} 天:合计 {total};最高 {day} {max}',
|
|
@@ -178,6 +212,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
178
212
|
'usage.series7': '7 天',
|
|
179
213
|
'usage.series30': '30 天',
|
|
180
214
|
'usage.breakdown': '按操作分类(最近 30 天)',
|
|
215
|
+
'usage.byRoute': '按线路分类(最近 30 天)',
|
|
216
|
+
'usage.byTrigger': '按触发方式分类(最近 30 天)',
|
|
181
217
|
'usage.filters': '筛选',
|
|
182
218
|
'usage.loading': '正在载入这次运行的调用明细…',
|
|
183
219
|
'usage.page': '第 {page} / {pages} 页',
|
|
@@ -292,6 +328,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
292
328
|
'err.network': '无法连接 Tacit 服务。',
|
|
293
329
|
'err.internal': '内部错误:{detail}',
|
|
294
330
|
'err.continuation': '这是一条纯粹的「继续」类消息(没有可分析的意图)——请分析它之前的那一轮。',
|
|
331
|
+
'err.directive-policy': '指令不能改变工具权限、审批或沙箱设置,请换一种说法。',
|
|
332
|
+
'err.already-analyzed': '这一轮已有报告;点击「重新分析」可再花一次费用分析。',
|
|
333
|
+
'err.unknown-directive': '找不到这条指令。',
|
|
295
334
|
}
|
|
296
335
|
|
|
297
336
|
const DICT_EN = {
|
|
@@ -303,13 +342,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
303
342
|
'status.trendCorrections': 'You corrected the agent: {a} → {b}',
|
|
304
343
|
'status.trendMessy': 'Messy turns: {a} → {b}',
|
|
305
344
|
'status.trendTokens': 'Tokens/turn: {a} → {b}',
|
|
306
|
-
'status.trendHint': '(first {n} turns vs. latest {n}, measured)',
|
|
345
|
+
'status.trendHint': '(first {n} turns vs. latest {n}, measured — before/after on your own turns, not a controlled comparison)',
|
|
346
|
+
'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',
|
|
307
347
|
'turn.enrichment': 'Context added before the send',
|
|
308
348
|
'trigger.auto': 'auto',
|
|
309
349
|
'trigger.correction': 'correction',
|
|
310
350
|
'trigger.manual': 'manual',
|
|
311
351
|
'trigger.bootstrap': 'bootstrap',
|
|
312
352
|
'trigger.good': 'what worked',
|
|
353
|
+
'trigger.feedback': 'feedback',
|
|
354
|
+
'trigger.send': 'send',
|
|
313
355
|
'report.strengths': 'What this prompt got right',
|
|
314
356
|
'report.lesson': 'Lesson',
|
|
315
357
|
'settings.learnGood': 'Also learn from a clean prompt right after a messy turn',
|
|
@@ -339,6 +381,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
339
381
|
'steer.workspace': 'only {name}',
|
|
340
382
|
'steer.scope': 'Applies to',
|
|
341
383
|
'steer.everywhere': 'Everywhere',
|
|
384
|
+
'steer.receipt': 'Where this directive came from',
|
|
385
|
+
'steer.review': 'Review new directives before their trial',
|
|
386
|
+
'steer.startTrial': 'Start trial',
|
|
387
|
+
'steer.receiptLoading': 'Reading…',
|
|
388
|
+
'steer.receiptCopy': 'Copy as JSON',
|
|
389
|
+
'steer.receiptNever': 'never',
|
|
390
|
+
'steer.receiptNoRun': 'no ledger run',
|
|
391
|
+
'steer.receiptId': 'Id',
|
|
392
|
+
'steer.receiptScope': 'Applies to',
|
|
393
|
+
'steer.receiptStatus': 'Status',
|
|
394
|
+
'steer.receiptSource': 'Source',
|
|
395
|
+
'steer.receiptCreated': 'Created',
|
|
396
|
+
'steer.receiptUpdated': 'Updated',
|
|
397
|
+
'steer.receiptEvaluated': 'Evaluated',
|
|
398
|
+
'steer.receiptVersion': 'Version',
|
|
399
|
+
'steer.receiptTrial': 'Trial window',
|
|
400
|
+
'steer.receiptTurns': '{n} turns',
|
|
401
|
+
'steer.receiptTrialNote': 'from {started} · baseline messy {messy}, corrections {corrected}',
|
|
402
|
+
'steer.receiptCalls': '{n} calls',
|
|
403
|
+
'steer.enabled': 'Enabled',
|
|
404
|
+
'steer.options': 'Options',
|
|
405
|
+
'steer.receiptTriggers': 'Trigger categories',
|
|
406
|
+
'steer.receiptEvidence': 'Evidence',
|
|
407
|
+
'steer.receiptEvidenceValue': '{turns} turns across {conversations} conversations',
|
|
408
|
+
'steer.receiptCost': 'Distillation cost',
|
|
409
|
+
'steer.moveTo': 'Move to workspace',
|
|
410
|
+
'steer.notSeen': 'not seen since {date}',
|
|
342
411
|
'settings.auto': 'Auto-analyze messy turns',
|
|
343
412
|
'settings.budget': 'Daily cap on automatic analyses',
|
|
344
413
|
settings: 'Settings',
|
|
@@ -417,10 +486,14 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
417
486
|
'usage.last30': 'Last 30 days',
|
|
418
487
|
'usage.since': 'Since {day}',
|
|
419
488
|
'usage.calls': 'Billed calls (30 d)',
|
|
489
|
+
'usage.tokens': 'Tokens (30 d)',
|
|
420
490
|
'usage.avgAnalysis': 'Median cost per analysis',
|
|
421
491
|
'usage.cachedRate': 'Cached input rate (30 d)',
|
|
422
492
|
'usage.unpriced': 'Unpriced calls (30 d)',
|
|
423
493
|
'usage.unpricedShort': '{n} unpriced',
|
|
494
|
+
'usage.failedRepair': 'Spent on failed or repair calls (30 d)',
|
|
495
|
+
'usage.failedRepairSplit': 'failed {failed} · repair {repair}',
|
|
496
|
+
'usage.autoToday': 'automatic analyses today {n} / {budget}',
|
|
424
497
|
'usage.priceUnavailable': 'No price',
|
|
425
498
|
'usage.chartLabel': 'Daily spend, last {n} days',
|
|
426
499
|
'usage.chartSummary': 'Last {n} days: total {total}; highest {day} {max}',
|
|
@@ -428,6 +501,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
428
501
|
'usage.series7': '7 d',
|
|
429
502
|
'usage.series30': '30 d',
|
|
430
503
|
'usage.breakdown': 'By operation (last 30 days)',
|
|
504
|
+
'usage.byRoute': 'By route (last 30 days)',
|
|
505
|
+
'usage.byTrigger': 'By trigger (last 30 days)',
|
|
431
506
|
'usage.filters': 'Filters',
|
|
432
507
|
'usage.loading': 'Loading this run\u2019s calls…',
|
|
433
508
|
'usage.page': 'Page {page} of {pages}',
|
|
@@ -542,6 +617,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
542
617
|
'err.network': 'Could not reach the Tacit service.',
|
|
543
618
|
'err.internal': 'Internal error: {detail}',
|
|
544
619
|
'err.continuation': 'This is a bare continuation ("continue", "go ahead") with nothing to analyze — analyze the turn before it instead.',
|
|
620
|
+
'err.directive-policy': 'A directive cannot change tool permissions, approvals or the sandbox. Reword it.',
|
|
621
|
+
'err.already-analyzed': 'This turn already has a report; Re-analyze pays for a fresh one.',
|
|
622
|
+
'err.unknown-directive': 'No directive with that id.',
|
|
545
623
|
}
|
|
546
624
|
|
|
547
625
|
// ── API client (the host half's own routes on the harness origin) ──────
|
|
@@ -641,6 +719,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
641
719
|
auto: null,
|
|
642
720
|
steering: null, // {enabled, text}
|
|
643
721
|
workspaces: [], // [{cwd, label}] from /state
|
|
722
|
+
receipts: {}, // directive id → the /directive-receipt receipt, re-read on each expand
|
|
644
723
|
trend: null, // measured early-vs-recent trend
|
|
645
724
|
bootstrap: null, // {running, done, total}
|
|
646
725
|
coached: [], // cross-session coached-prompt entries
|
|
@@ -1029,13 +1108,13 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1029
1108
|
function closeConfirm() {
|
|
1030
1109
|
rootStore.confirm = null
|
|
1031
1110
|
notifyRoot()
|
|
1111
|
+
restoreConfirmOpener()
|
|
1032
1112
|
}
|
|
1033
1113
|
|
|
1034
1114
|
/** Run the parked destructive action, whichever it is, and close the dialog. */
|
|
1035
1115
|
async function confirmAction() {
|
|
1036
1116
|
const kind = rootStore.confirm !== null && typeof rootStore.confirm === 'object' ? rootStore.confirm.kind : null
|
|
1037
|
-
|
|
1038
|
-
notifyRoot()
|
|
1117
|
+
closeConfirm()
|
|
1039
1118
|
if (kind === 'reports') await clearAllRoot()
|
|
1040
1119
|
else if (kind === 'usage') await clearUsageHistory()
|
|
1041
1120
|
}
|
|
@@ -1151,6 +1230,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1151
1230
|
notifyRoot()
|
|
1152
1231
|
}
|
|
1153
1232
|
|
|
1233
|
+
/** One directive's audit receipt. Re-read on every expand: a trial's counters move while it runs. */
|
|
1234
|
+
async function fetchReceipt(id) {
|
|
1235
|
+
const key = String(id)
|
|
1236
|
+
if (key.length === 0) return
|
|
1237
|
+
rootStore.error = null
|
|
1238
|
+
try {
|
|
1239
|
+
const result = await api('/directive-receipt', { id: key })
|
|
1240
|
+
if (result !== null && typeof result === 'object' && result.ok === true
|
|
1241
|
+
&& result.receipt !== null && typeof result.receipt === 'object') {
|
|
1242
|
+
rootStore.receipts[key] = result.receipt
|
|
1243
|
+
} else {
|
|
1244
|
+
rootStore.error = { code: result !== null && typeof result === 'object' && typeof result.code === 'string' ? result.code : 'bad-request', detail: '' }
|
|
1245
|
+
}
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
rootStore.error = errorOf(error)
|
|
1248
|
+
}
|
|
1249
|
+
notifyRoot()
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1154
1252
|
async function initStore(store) {
|
|
1155
1253
|
if (store === null || store.initStarted) return
|
|
1156
1254
|
store.initStarted = true
|
|
@@ -1181,7 +1279,8 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1181
1279
|
store.error = null
|
|
1182
1280
|
store.notice = null
|
|
1183
1281
|
notify(store)
|
|
1184
|
-
|
|
1282
|
+
// A turn that already has a report is only re-analyzed on purpose; the button reads "Re-analyze" then.
|
|
1283
|
+
api('/analyze', { sessionId: store.sessionId, turn, ...(store.reports[String(turn)] ? { force: true } : {}) })
|
|
1185
1284
|
.then((result) => {
|
|
1186
1285
|
if (result !== null && typeof result === 'object' && result.ok && result.report !== null && typeof result.report === 'object') {
|
|
1187
1286
|
store.reports[String(turn)] = result.report
|
|
@@ -1245,7 +1344,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1245
1344
|
// `busy` means an analysis of that turn was already running — a
|
|
1246
1345
|
// race with the auto-analyzer, not a failure of this batch.
|
|
1247
1346
|
const code = typeof entry.code === 'string' && entry.code.length > 0 ? entry.code : 'call-failed'
|
|
1248
|
-
if (failure === null && code !== 'busy') failure = { code, detail: '' }
|
|
1347
|
+
if (failure === null && code !== 'busy' && code !== 'already-analyzed') failure = { code, detail: '' }
|
|
1249
1348
|
}
|
|
1250
1349
|
if (failure !== null) store.error = failure
|
|
1251
1350
|
if (result.profile !== null && typeof result.profile === 'object') store.profile = result.profile
|
|
@@ -1575,14 +1674,30 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1575
1674
|
}
|
|
1576
1675
|
|
|
1577
1676
|
/**
|
|
1578
|
-
* Billed tokens in one bucket set
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1677
|
+
* Billed tokens in one bucket set, split the way they are priced:
|
|
1678
|
+
* everything the prompt cost (uncached input, cache reads, cache writes)
|
|
1679
|
+
* against what came back. `reasoningTokens` is a subset of `outputTokens`,
|
|
1680
|
+
* so adding it would count every reasoning token twice.
|
|
1581
1681
|
*/
|
|
1582
|
-
function
|
|
1583
|
-
if (buckets === null || typeof buckets !== 'object') return 0
|
|
1682
|
+
function tokensInOut(buckets) {
|
|
1683
|
+
if (buckets === null || typeof buckets !== 'object') return { input: 0, output: 0 }
|
|
1584
1684
|
const num = (value) => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0)
|
|
1585
|
-
return
|
|
1685
|
+
return {
|
|
1686
|
+
input: num(buckets.inputTokens) + num(buckets.cacheReadTokens) + num(buckets.cacheWriteTokens),
|
|
1687
|
+
output: num(buckets.outputTokens),
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
/** Both halves of `tokensInOut` at once. */
|
|
1692
|
+
function tokensTotal(buckets) {
|
|
1693
|
+
const split = tokensInOut(buckets)
|
|
1694
|
+
return split.input + split.output
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
/** `tokensInOut` as the one line the tiles and the run rows both show. */
|
|
1698
|
+
function fmtTokensSplit(buckets) {
|
|
1699
|
+
const split = tokensInOut(buckets)
|
|
1700
|
+
return 'in ' + fmtTokens(split.input) + ' · out ' + fmtTokens(split.output)
|
|
1586
1701
|
}
|
|
1587
1702
|
|
|
1588
1703
|
function usageNum(value) {
|
|
@@ -1600,7 +1715,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1600
1715
|
}
|
|
1601
1716
|
}
|
|
1602
1717
|
|
|
1603
|
-
/**
|
|
1718
|
+
/**
|
|
1719
|
+
* One period card, zero-filled; the two averages stay nullable.
|
|
1720
|
+
* `failedUsd` and `repairUsd` overlap on a failed repair, so they can sum
|
|
1721
|
+
* past `failedOrRepairUsd`, which counts that union once.
|
|
1722
|
+
*/
|
|
1604
1723
|
function usagePeriod(value) {
|
|
1605
1724
|
const source = value !== null && typeof value === 'object' ? value : {}
|
|
1606
1725
|
return {
|
|
@@ -1608,8 +1727,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1608
1727
|
billedCalls: usageNum(source.billedCalls),
|
|
1609
1728
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1610
1729
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1730
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1611
1731
|
tokens: usageTokens(source.tokens),
|
|
1612
1732
|
usdKnown: usageNum(source.usdKnown),
|
|
1733
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1734
|
+
repairUsd: usageNum(source.repairUsd),
|
|
1735
|
+
failedOrRepairUsd: usageNum(source.failedOrRepairUsd),
|
|
1613
1736
|
avgAnalysisUsd: typeof source.avgAnalysisUsd === 'number' && Number.isFinite(source.avgAnalysisUsd) ? source.avgAnalysisUsd : null,
|
|
1614
1737
|
cachedInputRate: typeof source.cachedInputRate === 'number' && Number.isFinite(source.cachedInputRate) ? source.cachedInputRate : null,
|
|
1615
1738
|
}
|
|
@@ -1646,8 +1769,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1646
1769
|
billedCalls: usageNum(source.billedCalls),
|
|
1647
1770
|
unmeteredCalls: usageNum(source.unmeteredCalls),
|
|
1648
1771
|
unpricedCalls: usageNum(source.unpricedCalls),
|
|
1772
|
+
failedCalls: usageNum(source.failedCalls),
|
|
1649
1773
|
tokens: usageTokens(source.tokens),
|
|
1650
1774
|
usdKnown: usageNum(source.usdKnown),
|
|
1775
|
+
failedUsd: usageNum(source.failedUsd),
|
|
1651
1776
|
trigger: text('trigger'),
|
|
1652
1777
|
startedAt: usageNum(source.startedAt),
|
|
1653
1778
|
endedAt: usageNum(source.endedAt),
|
|
@@ -1668,6 +1793,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1668
1793
|
if (value === null || typeof value !== 'object' || value.ok !== true) return null
|
|
1669
1794
|
const runs = value.runs !== null && typeof value.runs === 'object' ? value.runs : {}
|
|
1670
1795
|
const warnings = value.warnings !== null && typeof value.warnings === 'object' ? value.warnings : {}
|
|
1796
|
+
const auto = value.auto !== null && typeof value.auto === 'object' ? value.auto : {}
|
|
1671
1797
|
return {
|
|
1672
1798
|
trackingSince: usageNum(value.trackingSince),
|
|
1673
1799
|
pricing: value.pricing !== null && typeof value.pricing === 'object' ? value.pricing : {},
|
|
@@ -1678,9 +1804,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1678
1804
|
lifetime: usagePeriod(value.lifetime),
|
|
1679
1805
|
byType: usageBuckets(value.byType),
|
|
1680
1806
|
byModel: usageBuckets(value.byModel),
|
|
1807
|
+
byProvider: usageBuckets(value.byProvider),
|
|
1808
|
+
byTrigger: usageBuckets(value.byTrigger),
|
|
1681
1809
|
series7: usageSeries(value.series7),
|
|
1682
1810
|
series30: usageSeries(value.series30),
|
|
1683
1811
|
warnings: { daily: usageWarning(warnings.daily), monthly: usageWarning(warnings.monthly) },
|
|
1812
|
+
auto: { today: usageNum(auto.today), budget: usageNum(auto.budget) },
|
|
1684
1813
|
runs: {
|
|
1685
1814
|
items: (Array.isArray(runs.items) ? runs.items : []).map(usageRunItem),
|
|
1686
1815
|
page: usageNum(runs.page) > 0 ? Math.floor(usageNum(runs.page)) : 1,
|
|
@@ -1755,10 +1884,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1755
1884
|
return h('div', { className: 'tacit-progress' },
|
|
1756
1885
|
h('div', { className: 'tacit-progress-head' },
|
|
1757
1886
|
h('span', { className: 'tacit-progress-title' }, t('status.learned', { count: String(analyzed) })),
|
|
1758
|
-
|
|
1887
|
+
// The same badge idiom the directive cards use: a dot plus a label,
|
|
1888
|
+
// tinted by state. Off is said in the line below, not twice.
|
|
1889
|
+
autoOn
|
|
1890
|
+
? h('span', { className: 'tacit-badge tacit-badge-active' },
|
|
1891
|
+
h('span', { className: 'tacit-badge-dot', 'aria-hidden': 'true' }),
|
|
1892
|
+
t('status.autoOn', { today: String(today), budget: String(budget) }))
|
|
1893
|
+
: null),
|
|
1759
1894
|
h('div', { className: 'tacit-progress-text' }, autoOn ? t('status.autoHint') : t('status.autoOff')),
|
|
1760
1895
|
hasTrend
|
|
1761
|
-
? h('div', { className: 'tacit-trend' },
|
|
1896
|
+
? h('div', { className: 'tacit-trend', title: t('status.trendDocs') },
|
|
1762
1897
|
h('span', { className: 'tacit-chip' }, t('status.trendCorrections', { a: pct(trend.early.correctionRate), b: pct(trend.recent.correctionRate) })),
|
|
1763
1898
|
h('span', { className: 'tacit-chip' }, t('status.trendMessy', { a: pct(trend.early.messyRate), b: pct(trend.recent.messyRate) })),
|
|
1764
1899
|
h('span', { className: 'tacit-chip' }, t('status.trendTokens', { a: fmt(trend.early.tokensPerTurn), b: fmt(trend.recent.tokensPerTurn) })),
|
|
@@ -1854,8 +1989,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1854
1989
|
const status = turn.finished === true
|
|
1855
1990
|
? null
|
|
1856
1991
|
: (isNewest === true
|
|
1857
|
-
? h('span', { className: 'tacit-
|
|
1858
|
-
|
|
1992
|
+
? h('span', { className: 'tacit-badge tacit-badge-active' },
|
|
1993
|
+
h('span', { className: 'tacit-badge-dot', 'aria-hidden': 'true' }),
|
|
1994
|
+
t('turn.running'))
|
|
1995
|
+
: h('span', { className: 'tacit-badge tacit-badge-muted' }, t('turn.interrupted')))
|
|
1859
1996
|
|
|
1860
1997
|
return h('div', { className: 'tacit-row' + (report !== null ? ' tacit-row-analyzed' : '') },
|
|
1861
1998
|
h('div', { className: 'tacit-row-head' },
|
|
@@ -1873,35 +2010,47 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1873
2010
|
h('span', { className: 'tacit-row-time' }, fmtTime(turn.startedAt)),
|
|
1874
2011
|
report !== null ? h('span', { className: 'tacit-chip tacit-chip-trigger' }, t('trigger.' + report.trigger)) : null,
|
|
1875
2012
|
status),
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
h('span', { className: 'tacit-chip' }, t('turn.tokens', { n: fmt(tokens) })),
|
|
1880
|
-
typeof turn.retries === 'number' && turn.retries > 0
|
|
1881
|
-
? h('span', { className: 'tacit-chip tacit-chip-warn' }, t('turn.retries', { n: String(turn.retries) }))
|
|
1882
|
-
: null,
|
|
2013
|
+
// Two controls only, and the row's own action is the solid one:
|
|
2014
|
+
// opening the report is a disclosure, so it stays quiet.
|
|
2015
|
+
h('div', { className: 'tacit-row-actions' },
|
|
1883
2016
|
report !== null
|
|
1884
|
-
? h('button', {
|
|
1885
|
-
|
|
2017
|
+
? h('button', {
|
|
2018
|
+
type: 'button',
|
|
2019
|
+
className: 'tacit-btn tacit-btn-sm tacit-btn-quiet',
|
|
2020
|
+
'aria-expanded': reportOpen,
|
|
2021
|
+
onClick: () => toggleReport(store, turn.turn),
|
|
2022
|
+
}, t('turn.report'),
|
|
2023
|
+
h('span', { className: 'tacit-chevron' + (reportOpen ? ' tacit-chevron-open' : ''), 'aria-hidden': 'true' }, '›'))
|
|
1886
2024
|
: null,
|
|
1887
2025
|
h('button', {
|
|
1888
2026
|
type: 'button',
|
|
1889
|
-
className: 'tacit-btn tacit-btn-sm
|
|
2027
|
+
className: 'tacit-btn tacit-btn-sm',
|
|
1890
2028
|
disabled: analyzing,
|
|
1891
2029
|
onClick: () => analyzeTurn(store, turn.turn),
|
|
1892
2030
|
}, analyzing ? t('turn.analyzing') : (report !== null ? t('turn.reanalyze') : t('turn.analyze'))))),
|
|
1893
2031
|
prompt.length > 0
|
|
1894
2032
|
? h('div', { className: 'tacit-row-prompt' },
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
className: 'tacit-row-prompt-text',
|
|
1898
|
-
onClick: () => setExpanded((open) => !open),
|
|
1899
|
-
}, expanded ? prompt : promptPreview),
|
|
2033
|
+
// A long prompt is one control: the text itself toggles, and the
|
|
2034
|
+
// trailing word says so. A short one is plain text.
|
|
1900
2035
|
prompt.length > 120
|
|
1901
|
-
? h('button', {
|
|
1902
|
-
|
|
1903
|
-
|
|
2036
|
+
? h('button', {
|
|
2037
|
+
type: 'button',
|
|
2038
|
+
className: 'tacit-row-prompt-text',
|
|
2039
|
+
'aria-expanded': expanded,
|
|
2040
|
+
onClick: () => setExpanded((open) => !open),
|
|
2041
|
+
}, expanded ? prompt : promptPreview,
|
|
2042
|
+
h('span', { className: 'tacit-row-more' }, expanded ? t('turn.collapse') : t('turn.expand')))
|
|
2043
|
+
: h('div', { className: 'tacit-row-prompt-text tacit-row-prompt-static' }, prompt))
|
|
1904
2044
|
: null,
|
|
2045
|
+
// The measurements are context, not headline: one quiet dot-separated
|
|
2046
|
+
// line under the prompt rather than four bordered chips in the header.
|
|
2047
|
+
h('div', { className: 'tacit-row-meta' },
|
|
2048
|
+
h('span', null, t('turn.tools', { n: String(tools) })),
|
|
2049
|
+
h('span', null, t('turn.steps', { n: String(typeof turn.steps === 'number' ? turn.steps : 0) })),
|
|
2050
|
+
h('span', null, t('turn.tokens', { n: fmt(tokens) })),
|
|
2051
|
+
typeof turn.retries === 'number' && turn.retries > 0
|
|
2052
|
+
? h('span', { className: 'tacit-row-meta-warn' }, t('turn.retries', { n: String(turn.retries) }))
|
|
2053
|
+
: null),
|
|
1905
2054
|
typeof turn.enrichment === 'string' && turn.enrichment.length > 0
|
|
1906
2055
|
? h('div', { className: 'tacit-row-enrichment' },
|
|
1907
2056
|
h('span', { className: 'tacit-report-title' }, t('turn.enrichment')),
|
|
@@ -1965,35 +2114,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
1965
2114
|
|
|
1966
2115
|
return h('div', { className: 'tacit-settings' },
|
|
1967
2116
|
h('div', { className: 'tacit-settings-title' }, t('settings.title')),
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
h('div', { className: 'tacit-settings-row' },
|
|
1978
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.auto')),
|
|
1979
|
-
h('input', { type: 'checkbox', checked: auto, onChange: toggleAuto })),
|
|
1980
|
-
h('div', { className: 'tacit-settings-row' },
|
|
1981
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.learnGood')),
|
|
1982
|
-
h('input', { type: 'checkbox', checked: learnGood, onChange: toggleLearnGood })),
|
|
1983
|
-
h('div', { className: 'tacit-settings-row' },
|
|
1984
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.budget')),
|
|
2117
|
+
FieldRow(t('settings.model'), h('select', {
|
|
2118
|
+
id: 'tacit-tab-model',
|
|
2119
|
+
className: 'tacit-select',
|
|
2120
|
+
value: model,
|
|
2121
|
+
onChange: (event) => applyModel(event.target.value),
|
|
2122
|
+
},
|
|
2123
|
+
h('option', { value: 'deepseek-v4-flash' }, 'deepseek-v4-flash'),
|
|
2124
|
+
h('option', { value: 'deepseek-v4-pro' }, 'deepseek-v4-pro')), 'tacit-tab-model'),
|
|
2125
|
+
FieldRow(t('settings.budget'), [
|
|
1985
2126
|
h('input', {
|
|
2127
|
+
key: 'input',
|
|
2128
|
+
id: 'tacit-tab-budget',
|
|
1986
2129
|
className: 'tacit-input',
|
|
1987
2130
|
type: 'number',
|
|
1988
2131
|
min: 0,
|
|
1989
2132
|
value: budgetText,
|
|
1990
2133
|
onChange: (event) => setBudgetText(event.target.value),
|
|
1991
2134
|
}),
|
|
1992
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: applyBudget }, t('settings.apply'))
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
h('div', { className: 'tacit-
|
|
2135
|
+
h('button', { key: 'apply', type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: applyBudget }, t('settings.apply')),
|
|
2136
|
+
], 'tacit-tab-budget'),
|
|
2137
|
+
// The three switches read as one group of clickable sentences, the way
|
|
2138
|
+
// the Agent guidance options do.
|
|
2139
|
+
h('div', { className: 'tacit-options' },
|
|
2140
|
+
OptionRow(t('settings.auto'), auto, toggleAuto),
|
|
2141
|
+
OptionRow(t('settings.learnGood'), learnGood, toggleLearnGood),
|
|
2142
|
+
OptionRow(t('settings.live'), live, toggleLive)),
|
|
2143
|
+
h('div', { className: 'tacit-inline' },
|
|
1997
2144
|
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: onClear }, t('settings.clear')),
|
|
1998
2145
|
notice !== null ? h('span', { className: 'tacit-settings-notice' }, notice) : null))
|
|
1999
2146
|
}
|
|
@@ -2025,7 +2172,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2025
2172
|
h('div', { className: 'tacit-head-title' }, t('tab')),
|
|
2026
2173
|
h('button', {
|
|
2027
2174
|
type: 'button',
|
|
2028
|
-
className: 'tacit-btn',
|
|
2175
|
+
className: 'tacit-btn tacit-btn-sm tacit-btn-quiet',
|
|
2029
2176
|
onClick: () => setSettingsOpen((open) => !open),
|
|
2030
2177
|
}, settingsOpen ? t('settings.close') : t('settings'))),
|
|
2031
2178
|
StatusCard(kit, { config, profile, auto: store.auto }),
|
|
@@ -2045,11 +2192,10 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2045
2192
|
? h('div', { className: 'tacit-empty' }, t('empty'))
|
|
2046
2193
|
: h('div', null,
|
|
2047
2194
|
h('div', { className: 'tacit-toolbar' },
|
|
2048
|
-
h('span', { className: 'tacit-panel-hint tacit-toolbar-hint' }, t('manual.hint')),
|
|
2049
2195
|
BootstrapButton(kit, { bootstrap: store.bootstrap, onClick: () => bootstrapSession(store) }),
|
|
2050
2196
|
h('button', {
|
|
2051
2197
|
type: 'button',
|
|
2052
|
-
className: 'tacit-btn tacit-btn-sm',
|
|
2198
|
+
className: 'tacit-btn tacit-btn-sm' + (store.selecting ? '' : ' tacit-btn-quiet'),
|
|
2053
2199
|
onClick: () => toggleSelecting(store),
|
|
2054
2200
|
}, store.selecting ? t('coach.selectDone') : t('coach.select')),
|
|
2055
2201
|
store.selecting
|
|
@@ -2060,6 +2206,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2060
2206
|
onClick: () => coachSelected(store),
|
|
2061
2207
|
}, store.batchRunning ? t('coach.batchRunning') : t('coach.selected', { n: String(selectedCount) }))
|
|
2062
2208
|
: null),
|
|
2209
|
+
h('div', { className: 'tacit-toolbar-note' }, t('manual.hint')),
|
|
2063
2210
|
turns.map((turn, index) => h(TurnRow, { key: String(turn.turn), kit, store, turn, isNewest: index === 0 }))))
|
|
2064
2211
|
}
|
|
2065
2212
|
}
|
|
@@ -2119,21 +2266,23 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2119
2266
|
: preview.error !== null
|
|
2120
2267
|
? h('div', { className: 'tacit-error' },
|
|
2121
2268
|
t('err.' + String(preview.error.code), { detail: String(preview.error.detail || '') }))
|
|
2122
|
-
|
|
2269
|
+
// What changed is the one sentence that decides Apply, so it
|
|
2270
|
+
// reads before the two columns rather than under them.
|
|
2271
|
+
: h('div', { className: 'tacit-modal-body' },
|
|
2272
|
+
preview.data !== null && typeof preview.data === 'object' && typeof preview.data.rationale === 'string' && preview.data.rationale.length > 0
|
|
2273
|
+
? h('div', { className: 'tacit-modal-rationale' },
|
|
2274
|
+
h('div', { className: 'tacit-modal-col-title' }, t('preview.rationale')),
|
|
2275
|
+
h('div', { className: 'tacit-modal-rationale-text' }, preview.data.rationale))
|
|
2276
|
+
: null,
|
|
2123
2277
|
unchanged
|
|
2124
2278
|
? h('div', { className: 'tacit-modal-unchanged' }, t('preview.unchanged'))
|
|
2125
2279
|
: h('div', { className: 'tacit-modal-cols' },
|
|
2126
2280
|
h('div', { className: 'tacit-modal-col' },
|
|
2127
2281
|
h('div', { className: 'tacit-modal-col-title' }, t('preview.original')),
|
|
2128
2282
|
h('pre', { className: 'tacit-pre' }, preview.original)),
|
|
2129
|
-
h('div', { className: 'tacit-modal-col' },
|
|
2283
|
+
h('div', { className: 'tacit-modal-col tacit-modal-col-improved' },
|
|
2130
2284
|
h('div', { className: 'tacit-modal-col-title' }, t('preview.improved')),
|
|
2131
2285
|
h('pre', { className: 'tacit-pre' }, improvedText))),
|
|
2132
|
-
preview.data !== null && typeof preview.data === 'object' && typeof preview.data.rationale === 'string' && preview.data.rationale.length > 0
|
|
2133
|
-
? h('div', { className: 'tacit-modal-rationale' },
|
|
2134
|
-
h('div', { className: 'tacit-modal-col-title' }, t('preview.rationale')),
|
|
2135
|
-
h('div', { className: 'tacit-modal-rationale-text' }, preview.data.rationale))
|
|
2136
|
-
: null,
|
|
2137
2286
|
h('div', { className: 'tacit-modal-actions' },
|
|
2138
2287
|
unchanged
|
|
2139
2288
|
? null
|
|
@@ -2214,6 +2363,37 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2214
2363
|
|
|
2215
2364
|
// ── Collapsible section card (Settings → Tacit) ────────────────────────
|
|
2216
2365
|
|
|
2366
|
+
/**
|
|
2367
|
+
* One settings row: a label that takes the slack and wraps, and its
|
|
2368
|
+
* control(s) right-aligned. `htmlFor` names the control when it has an id;
|
|
2369
|
+
* checkbox settings use `OptionRow` instead, which is a label all through.
|
|
2370
|
+
*/
|
|
2371
|
+
function FieldRow(label, control, htmlFor) {
|
|
2372
|
+
const controls = Array.isArray(control) ? control : [control]
|
|
2373
|
+
return h('div', { className: 'tacit-field' },
|
|
2374
|
+
h('label', {
|
|
2375
|
+
className: 'tacit-field-label',
|
|
2376
|
+
...(typeof htmlFor === 'string' && htmlFor.length > 0 ? { htmlFor } : {}),
|
|
2377
|
+
}, label),
|
|
2378
|
+
h('div', { className: 'tacit-field-control' }, ...controls))
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* One checkbox setting as a clickable `<label>`, so the hit target is the
|
|
2383
|
+
* whole sentence rather than a 14px box.
|
|
2384
|
+
*/
|
|
2385
|
+
function OptionRow(label, checked, onChange, extra) {
|
|
2386
|
+
return h('label', { className: 'tacit-option' },
|
|
2387
|
+
h('input', {
|
|
2388
|
+
type: 'checkbox',
|
|
2389
|
+
className: 'tacit-check',
|
|
2390
|
+
checked,
|
|
2391
|
+
onChange,
|
|
2392
|
+
...(extra !== null && typeof extra === 'object' ? extra : {}),
|
|
2393
|
+
}),
|
|
2394
|
+
h('span', null, label))
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2217
2397
|
/**
|
|
2218
2398
|
* One accessible collapsible card.
|
|
2219
2399
|
*
|
|
@@ -2243,10 +2423,15 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2243
2423
|
typeof summary === 'string' && summary.length > 0
|
|
2244
2424
|
? h('span', { className: 'tacit-card-summary' }, summary)
|
|
2245
2425
|
: null,
|
|
2246
|
-
count
|
|
2426
|
+
// A zero count is silence, not a figure worth a chip.
|
|
2427
|
+
typeof count === 'number' && count > 0
|
|
2247
2428
|
? h('span', { className: 'tacit-card-count' }, String(count))
|
|
2248
2429
|
: null,
|
|
2249
|
-
h('span', {
|
|
2430
|
+
h('span', {
|
|
2431
|
+
className: 'tacit-chevron tacit-card-chevron' + (isOpen ? ' tacit-chevron-open' : ''),
|
|
2432
|
+
'aria-label': label,
|
|
2433
|
+
title: label,
|
|
2434
|
+
}, '›')),
|
|
2250
2435
|
h('div', { className: 'tacit-card-body', id: bodyId, hidden: !isOpen }, ...kids))
|
|
2251
2436
|
}
|
|
2252
2437
|
|
|
@@ -2257,7 +2442,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2257
2442
|
*
|
|
2258
2443
|
* It is captured by the store action that opens the dialog rather than by
|
|
2259
2444
|
* the dialog itself: React focuses the `autoFocus`ed Cancel button while it
|
|
2260
|
-
* commits, so by the time
|
|
2445
|
+
* commits, so by the time the dialog is mounted the opener is already gone.
|
|
2261
2446
|
*/
|
|
2262
2447
|
let confirmOpener = null
|
|
2263
2448
|
|
|
@@ -2272,26 +2457,29 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2272
2457
|
confirmOpener = typeof document !== 'undefined' && document !== null ? document.activeElement : null
|
|
2273
2458
|
}
|
|
2274
2459
|
|
|
2460
|
+
/**
|
|
2461
|
+
* Hand focus back one macrotask after the close. The host's Settings modal
|
|
2462
|
+
* moves focus on the same Escape, so a restore that runs inside the close is
|
|
2463
|
+
* overwritten and the user lands on the host's Close button instead.
|
|
2464
|
+
*/
|
|
2465
|
+
function restoreConfirmOpener() {
|
|
2466
|
+
const opener = confirmOpener
|
|
2467
|
+
confirmOpener = null
|
|
2468
|
+
if (opener === null || typeof opener.focus !== 'function') return
|
|
2469
|
+
setTimeout(() => opener.focus(), 0)
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2275
2472
|
/**
|
|
2276
2473
|
* One small modal confirmation, rendered unconditionally by its owner (and
|
|
2277
2474
|
* `null` when closed) so React's hook count never changes between renders.
|
|
2278
|
-
* Hook-free
|
|
2279
|
-
* with markup alone.
|
|
2475
|
+
* Hook-free: focus is restored by `closeConfirm`, the one path out.
|
|
2280
2476
|
*
|
|
2281
2477
|
* Cancel comes first and takes focus: the safe answer is the one a keyboard
|
|
2282
2478
|
* or screen-reader user reaches without looking.
|
|
2283
2479
|
*/
|
|
2284
2480
|
function ConfirmDialog(kit, { open, title, body, confirmLabel, onConfirm, onCancel }) {
|
|
2285
2481
|
const { t } = kit
|
|
2286
|
-
|
|
2287
|
-
useEffect(() => {
|
|
2288
|
-
if (!isOpen || typeof document === 'undefined') return undefined
|
|
2289
|
-
const opener = confirmOpener
|
|
2290
|
-
return () => {
|
|
2291
|
-
if (opener !== null && typeof opener.focus === 'function') opener.focus()
|
|
2292
|
-
}
|
|
2293
|
-
}, [isOpen])
|
|
2294
|
-
if (!isOpen) return null
|
|
2482
|
+
if (open !== true) return null
|
|
2295
2483
|
const cancel = typeof onCancel === 'function' ? onCancel : () => {}
|
|
2296
2484
|
return h('div', {
|
|
2297
2485
|
className: 'tacit-modal-backdrop',
|
|
@@ -2318,7 +2506,7 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2318
2506
|
},
|
|
2319
2507
|
},
|
|
2320
2508
|
h('div', {
|
|
2321
|
-
className: 'tacit-modal-card',
|
|
2509
|
+
className: 'tacit-modal-card tacit-modal-card-sm',
|
|
2322
2510
|
role: 'dialog',
|
|
2323
2511
|
'aria-modal': 'true',
|
|
2324
2512
|
'aria-labelledby': 'tacit-confirm-title',
|
|
@@ -2336,9 +2524,91 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2336
2524
|
|
|
2337
2525
|
function DirectivesEditor(kit) {
|
|
2338
2526
|
const { t } = kit
|
|
2527
|
+
const fmtDay = (ms) => {
|
|
2528
|
+
const date = new Date(Number(ms))
|
|
2529
|
+
return !(Number(ms) > 0) || Number.isNaN(date.getTime()) ? t('steer.receiptNever') : date.toISOString().slice(0, 10)
|
|
2530
|
+
}
|
|
2531
|
+
const pct = (value) => (typeof value === 'number' && value >= 0 ? Math.round(value * 100) + '%' : t('steer.receiptNever'))
|
|
2532
|
+
const triggerName = (name) => {
|
|
2533
|
+
const key = 'trigger.' + name
|
|
2534
|
+
const label = t(key)
|
|
2535
|
+
return label === key ? name : label
|
|
2536
|
+
}
|
|
2537
|
+
const copyReceipt = (receipt) => {
|
|
2538
|
+
if (typeof navigator === 'undefined' || navigator === null || !navigator.clipboard || typeof navigator.clipboard.writeText !== 'function') return
|
|
2539
|
+
navigator.clipboard.writeText(JSON.stringify(receipt, null, 2)).catch(() => {})
|
|
2540
|
+
}
|
|
2541
|
+
const metaRow = (label, value) => [h('dt', { key: label + '-t' }, label), h('dd', { key: label + '-d' }, value)]
|
|
2542
|
+
/** A null `note` marks an absent value ("never", "no ledger run"), shown quiet rather than as a headline figure. */
|
|
2543
|
+
const tile = (key, label, value, note) => h('div', { key, className: 'tacit-tile tacit-receipt-tile' },
|
|
2544
|
+
h('span', { className: 'tacit-tile-label' }, label),
|
|
2545
|
+
h('span', { className: 'tacit-tile-value' + (note === null ? ' tacit-tile-muted' : '') }, value),
|
|
2546
|
+
note === null ? null : h('span', { className: 'tacit-receipt-tile-note' }, note))
|
|
2547
|
+
const step = (key, label, ms) => h('li', { key, className: 'tacit-timeline-step' + (Number(ms) > 0 ? '' : ' tacit-timeline-never') },
|
|
2548
|
+
h('span', { className: 'tacit-timeline-label' }, label),
|
|
2549
|
+
h('span', { className: 'tacit-timeline-value' }, fmtDay(ms)))
|
|
2550
|
+
/**
|
|
2551
|
+
* What the receipt route returned for this directive, layered: three
|
|
2552
|
+
* tiles, a lifecycle timeline, the trigger chips, then the identity grid;
|
|
2553
|
+
* never any prompt text.
|
|
2554
|
+
*/
|
|
2555
|
+
function Receipt(receipt) {
|
|
2556
|
+
if (receipt === null || typeof receipt === 'object' === false) return h('div', { className: 'tacit-panel-hint' }, t('steer.receiptLoading'))
|
|
2557
|
+
const triggers = Object.entries(receipt.triggers !== null && typeof receipt.triggers === 'object' ? receipt.triggers : {})
|
|
2558
|
+
const evidence = receipt.evidence !== null && typeof receipt.evidence === 'object' ? receipt.evidence : { turns: 0, conversations: 0 }
|
|
2559
|
+
const cost = receipt.cost !== null && typeof receipt.cost === 'object' ? receipt.cost : { usd: null, calls: 0 }
|
|
2560
|
+
const trial = receipt.trial !== null && typeof receipt.trial === 'object' ? receipt.trial : null
|
|
2561
|
+
return h('div', { className: 'tacit-receipt' },
|
|
2562
|
+
h('div', { className: 'tacit-tiles tacit-receipt-tiles' },
|
|
2563
|
+
tile('evidence', t('steer.receiptEvidence'), String(evidence.turns),
|
|
2564
|
+
t('steer.receiptEvidenceValue', { turns: String(evidence.turns), conversations: String(evidence.conversations) })),
|
|
2565
|
+
tile('trial', t('steer.receiptTrial'),
|
|
2566
|
+
trial === null ? t('steer.receiptNever') : t('steer.receiptTurns', { n: String(trial.turns) }),
|
|
2567
|
+
trial === null ? null : t('steer.receiptTrialNote', {
|
|
2568
|
+
started: fmtDay(trial.startedAt), messy: pct(trial.baselineMessyRate), corrected: pct(trial.baselineCorrectionRate) })),
|
|
2569
|
+
tile('cost', t('steer.receiptCost'),
|
|
2570
|
+
cost.usd === null ? t('steer.receiptNoRun') : fmtUsd(cost.usd),
|
|
2571
|
+
cost.usd === null ? null : t('steer.receiptCalls', { n: String(cost.calls) }))),
|
|
2572
|
+
h('ol', { className: 'tacit-timeline' },
|
|
2573
|
+
step('created', t('steer.receiptCreated'), receipt.createdAt),
|
|
2574
|
+
step('updated', t('steer.receiptUpdated'), receipt.updatedAt),
|
|
2575
|
+
step('evaluated', t('steer.receiptEvaluated'), receipt.evaluatedAt)),
|
|
2576
|
+
h('div', { className: 'tacit-receipt-row' },
|
|
2577
|
+
h('span', { className: 'tacit-receipt-label' }, t('steer.receiptTriggers')),
|
|
2578
|
+
triggers.length === 0
|
|
2579
|
+
? h('span', { className: 'tacit-chip tacit-chip-muted' }, t('steer.receiptNever'))
|
|
2580
|
+
: triggers.map(([name, count]) => h('span', { key: name, className: 'tacit-chip' }, triggerName(name) + ' ' + String(count)))),
|
|
2581
|
+
h('dl', { className: 'tacit-receipt-meta' },
|
|
2582
|
+
...metaRow(t('steer.receiptId'), String(receipt.id)),
|
|
2583
|
+
...metaRow(t('steer.receiptVersion'), String(receipt.version)),
|
|
2584
|
+
...metaRow(t('steer.receiptScope'), typeof receipt.scope === 'string' && receipt.scope.length > 0 ? receipt.scope : t('steer.everywhere')),
|
|
2585
|
+
...metaRow(t('steer.receiptSource'), receipt.source === 'user' ? t('steer.user') : t('steer.distilled')),
|
|
2586
|
+
...metaRow(t('steer.receiptStatus'), String(receipt.status))),
|
|
2587
|
+
h('div', { className: 'tacit-receipt-actions' },
|
|
2588
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => copyReceipt(receipt) }, t('steer.receiptCopy'))))
|
|
2589
|
+
}
|
|
2339
2590
|
return function DirectivesEditorView(props) {
|
|
2340
|
-
const { config,
|
|
2591
|
+
const { config, steering } = props
|
|
2592
|
+
const directives = (Array.isArray(props.directives) ? props.directives : []).filter((entry) => entry.status !== 'removed')
|
|
2593
|
+
const receipts = rootStore.receipts !== null && typeof rootStore.receipts === 'object' ? rootStore.receipts : {}
|
|
2594
|
+
const reviewOn = config !== null && config.reviewCandidates === true
|
|
2341
2595
|
const workspaces = Array.isArray(props.workspaces) ? props.workspaces : []
|
|
2596
|
+
const seenAt = props.seenAt !== null && typeof props.seenAt === 'object' ? props.seenAt : {}
|
|
2597
|
+
const liveScope = (scope) => workspaces.some((entry) => entry.cwd === scope || String(entry.cwd).startsWith(scope + '/'))
|
|
2598
|
+
const notSeenSince = (entry) => {
|
|
2599
|
+
const stamp = typeof seenAt[entry.workspace] === 'number' ? seenAt[entry.workspace] : entry.createdAt
|
|
2600
|
+
const date = new Date(Number(stamp))
|
|
2601
|
+
return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)
|
|
2602
|
+
}
|
|
2603
|
+
const scopeOptions = (current) => {
|
|
2604
|
+
const options = [
|
|
2605
|
+
h('option', { key: '-', value: '-' }, t('steer.moveTo')),
|
|
2606
|
+
h('option', { key: '', value: '' }, t('steer.everywhere')),
|
|
2607
|
+
]
|
|
2608
|
+
for (const entry of workspaces) options.push(h('option', { key: entry.cwd, value: entry.cwd }, entry.label))
|
|
2609
|
+
if (current.length > 0 && !workspaces.some((entry) => entry.cwd === current)) options.push(h('option', { key: current, value: current }, labelOf(current)))
|
|
2610
|
+
return options
|
|
2611
|
+
}
|
|
2342
2612
|
const [draft, setDraft] = useState('')
|
|
2343
2613
|
const [scope, setScope] = useState('')
|
|
2344
2614
|
const labelOf = (cwd) => {
|
|
@@ -2361,25 +2631,49 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2361
2631
|
setEnrichOn(next)
|
|
2362
2632
|
updateRootConfig({ enrichPrompts: next })
|
|
2363
2633
|
}
|
|
2634
|
+
const toggleReview = () => updateRootConfig({ reviewCandidates: !reviewOn })
|
|
2364
2635
|
const onAdd = () => {
|
|
2365
2636
|
const text = draft.trim()
|
|
2366
2637
|
if (text.length === 0) return
|
|
2367
2638
|
setDraft('')
|
|
2368
2639
|
editDirectives({ action: 'add', text: text.slice(0, 300), ...(scope.length > 0 ? { workspace: scope } : {}) })
|
|
2369
2640
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2641
|
+
/** The lifecycle badge: one colour per status, the label the chips used to carry. */
|
|
2642
|
+
const statusOf = (entry) => {
|
|
2643
|
+
if (entry.status === 'queued') return { kind: 'queued', label: t('steer.queued') }
|
|
2644
|
+
if (entry.status === 'candidate') {
|
|
2645
|
+
return {
|
|
2646
|
+
kind: 'trial',
|
|
2647
|
+
label: t('steer.trial', {
|
|
2648
|
+
n: String(entry.trial !== null && typeof entry.trial === 'object' && typeof entry.trial.turns === 'number' ? entry.trial.turns : 0),
|
|
2649
|
+
total: String(config !== null && typeof config.directiveTrialTurns === 'number' ? config.directiveTrialTurns : 10),
|
|
2650
|
+
}),
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
if (entry.status === 'retired') return { kind: 'retired', label: t('steer.retired', { reason: String(entry.retiredReason || '') }) }
|
|
2654
|
+
return { kind: 'active', label: t('steer.active') }
|
|
2655
|
+
}
|
|
2656
|
+
const directiveCard = (entry) => {
|
|
2657
|
+
const status = statusOf(entry)
|
|
2658
|
+
const scoped = typeof entry.workspace === 'string' && entry.workspace.length > 0
|
|
2659
|
+
return h('div', { key: entry.id, className: 'tacit-dir tacit-dir-' + status.kind + (entry.enabled === false ? ' tacit-dir-off' : '') },
|
|
2660
|
+
h('div', { className: 'tacit-dir-head' },
|
|
2661
|
+
h('span', { className: 'tacit-badge tacit-badge-' + status.kind },
|
|
2662
|
+
h('span', { className: 'tacit-badge-dot', 'aria-hidden': 'true' }),
|
|
2663
|
+
status.label),
|
|
2664
|
+
h('span', { className: 'tacit-chip' }, entry.source === 'user' ? t('steer.user') : t('steer.distilled')),
|
|
2665
|
+
scoped
|
|
2666
|
+
? h('span', { className: 'tacit-chip tacit-chip-scope', title: entry.workspace }, t('steer.workspace', { name: labelOf(entry.workspace) }))
|
|
2667
|
+
: null,
|
|
2668
|
+
scoped && !liveScope(entry.workspace)
|
|
2669
|
+
? h('span', { className: 'tacit-chip tacit-chip-muted', title: entry.workspace }, t('steer.notSeen', { date: notSeenSince(entry) }))
|
|
2670
|
+
: null,
|
|
2671
|
+
reviewOn && entry.status === 'queued' && !(entry.approvedAt > 0)
|
|
2672
|
+
? h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm tacit-btn-primary tacit-dir-start', onClick: () => editDirectives({ action: 'start-trial', id: entry.id }) }, t('steer.startTrial'))
|
|
2673
|
+
: null),
|
|
2674
|
+
h('div', { className: 'tacit-dir-text' }, String(entry.text)),
|
|
2675
|
+
h('div', { className: 'tacit-dir-actions' },
|
|
2676
|
+
h('label', { className: 'tacit-dir-enabled' },
|
|
2383
2677
|
h('input', {
|
|
2384
2678
|
type: 'checkbox',
|
|
2385
2679
|
className: 'tacit-check',
|
|
@@ -2387,23 +2681,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2387
2681
|
checked: entry.enabled !== false,
|
|
2388
2682
|
onChange: () => editDirectives({ action: 'toggle', id: entry.id, enabled: entry.enabled === false }),
|
|
2389
2683
|
}),
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2684
|
+
t('steer.enabled')),
|
|
2685
|
+
h('select', {
|
|
2686
|
+
className: 'tacit-input tacit-select tacit-select-sm',
|
|
2687
|
+
'aria-label': t('steer.moveTo'),
|
|
2688
|
+
onChange: (event) => {
|
|
2689
|
+
if (event.target.value !== '-') editDirectives({ action: 'rescope', id: entry.id, workspace: event.target.value })
|
|
2690
|
+
},
|
|
2691
|
+
}, ...scopeOptions(typeof entry.workspace === 'string' ? entry.workspace : '')),
|
|
2692
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm tacit-btn-quiet tacit-dir-remove', onClick: () => editDirectives({ action: 'remove', id: entry.id }) }, t('steer.remove'))),
|
|
2693
|
+
h('details', {
|
|
2694
|
+
className: 'tacit-dir-receipt',
|
|
2695
|
+
'data-testid': 'tacit-receipt',
|
|
2696
|
+
'data-directive-id': entry.id,
|
|
2697
|
+
onToggle: (event) => {
|
|
2698
|
+
if (event.target.open) fetchReceipt(entry.id)
|
|
2699
|
+
},
|
|
2700
|
+
},
|
|
2701
|
+
h('summary', null, t('steer.receipt')),
|
|
2702
|
+
Receipt(receipts[entry.id] ?? null)))
|
|
2703
|
+
}
|
|
2704
|
+
return h('div', { className: 'tacit-panel-section', 'data-testid': 'tacit-steering' },
|
|
2705
|
+
h('div', { className: 'tacit-report-title' }, t('steer.title')),
|
|
2706
|
+
h('div', { className: 'tacit-panel-hint' }, t('steer.desc')),
|
|
2707
|
+
directives.length === 0
|
|
2708
|
+
? h('div', { className: 'tacit-empty' }, t('steer.empty'))
|
|
2709
|
+
: h('div', { className: 'tacit-dir-list' }, directives.map(directiveCard)),
|
|
2710
|
+
h('div', { className: 'tacit-dir-add' },
|
|
2407
2711
|
h('input', {
|
|
2408
2712
|
className: 'tacit-input tacit-directive-input',
|
|
2409
2713
|
type: 'text',
|
|
@@ -2426,7 +2730,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2426
2730
|
h('option', { value: '' }, t('steer.everywhere')),
|
|
2427
2731
|
...workspaces.map((entry) => h('option', { key: entry.cwd, value: entry.cwd }, entry.label)))
|
|
2428
2732
|
: null,
|
|
2429
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', disabled: draft.trim().length === 0, onClick: onAdd }, t('steer.add'))),
|
|
2733
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm tacit-btn-primary', disabled: draft.trim().length === 0, onClick: onAdd }, t('steer.add'))),
|
|
2734
|
+
h('div', { className: 'tacit-options' },
|
|
2735
|
+
h('div', { className: 'tacit-report-title' }, t('steer.options')),
|
|
2736
|
+
OptionRow(t('steer.toggle'), steerOn, toggleSteer),
|
|
2737
|
+
OptionRow(t('steer.enrich'), enrichOn, toggleEnrich),
|
|
2738
|
+
OptionRow(t('steer.review'), reviewOn, toggleReview)),
|
|
2430
2739
|
steering !== null && typeof steering === 'object' && typeof steering.text === 'string' && steering.text.length > 0
|
|
2431
2740
|
? h('details', { className: 'tacit-preview' },
|
|
2432
2741
|
h('summary', null, t('steer.preview')),
|
|
@@ -2576,19 +2885,15 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2576
2885
|
h('div', { className: 'tacit-settings-notice', role: 'status' }, notice === null ? '' : notice.text),
|
|
2577
2886
|
card('overview', [
|
|
2578
2887
|
StatusCard(kit, { config, profile, auto: rootStore.auto, trend: rootStore.trend }),
|
|
2579
|
-
|
|
2580
|
-
? h('div', { className: 'tacit-settings-row' },
|
|
2581
|
-
h('span', { className: 'tacit-chip' }, t('overview.model', { model: config.model })))
|
|
2582
|
-
: null,
|
|
2583
|
-
h('div', { className: 'tacit-settings-row' },
|
|
2888
|
+
h('div', { className: 'tacit-inline' },
|
|
2584
2889
|
BootstrapButton(kit, {
|
|
2585
2890
|
bootstrap: rootStore.bootstrap,
|
|
2586
2891
|
// Only a loaded preview may disable the button: before one
|
|
2587
2892
|
// lands, "0 eligible" is an absence of data, not a fact.
|
|
2588
2893
|
disabled: preview !== null && preview.eligible === 0,
|
|
2589
2894
|
onClick: () => bootstrapAll(t),
|
|
2590
|
-
}),
|
|
2591
|
-
|
|
2895
|
+
})),
|
|
2896
|
+
h('p', { className: 'tacit-panel-hint' }, t('bootstrap.hint')),
|
|
2592
2897
|
// What the run would actually cost, priced from the ledger once it
|
|
2593
2898
|
// holds enough analyses; until the preview lands, the documented
|
|
2594
2899
|
// figure stands in.
|
|
@@ -2627,40 +2932,40 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2627
2932
|
}),
|
|
2628
2933
|
], { summary: pricingSummary(kit, usagePricing) }),
|
|
2629
2934
|
card('learning', [
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
h('div', { className: 'tacit-settings-row' },
|
|
2640
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.auto')),
|
|
2641
|
-
h('input', { type: 'checkbox', checked: auto, onChange: toggleAuto })),
|
|
2642
|
-
h('div', { className: 'tacit-settings-row' },
|
|
2643
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.learnGood')),
|
|
2644
|
-
h('input', { type: 'checkbox', checked: learnGood, onChange: toggleLearnGood })),
|
|
2645
|
-
h('div', { className: 'tacit-settings-row' },
|
|
2646
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.budget')),
|
|
2935
|
+
FieldRow(t('settings.model'), h('select', {
|
|
2936
|
+
id: 'tacit-learning-model',
|
|
2937
|
+
className: 'tacit-select',
|
|
2938
|
+
value: model,
|
|
2939
|
+
onChange: (event) => applyModel(event.target.value),
|
|
2940
|
+
},
|
|
2941
|
+
h('option', { value: 'deepseek-v4-flash' }, 'deepseek-v4-flash'),
|
|
2942
|
+
h('option', { value: 'deepseek-v4-pro' }, 'deepseek-v4-pro')), 'tacit-learning-model'),
|
|
2943
|
+
FieldRow(t('settings.budget'), [
|
|
2647
2944
|
h('input', {
|
|
2945
|
+
key: 'input',
|
|
2946
|
+
id: 'tacit-learning-budget',
|
|
2648
2947
|
className: 'tacit-input',
|
|
2649
2948
|
type: 'number',
|
|
2650
2949
|
min: 0,
|
|
2651
2950
|
value: budgetText,
|
|
2652
2951
|
onChange: (event) => setBudgetText(event.target.value),
|
|
2653
2952
|
}),
|
|
2654
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: applyBudget }, t('settings.apply'))
|
|
2655
|
-
|
|
2953
|
+
h('button', { key: 'apply', type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: applyBudget }, t('settings.apply')),
|
|
2954
|
+
], 'tacit-learning-budget'),
|
|
2955
|
+
h('div', { className: 'tacit-options' },
|
|
2956
|
+
OptionRow(t('settings.auto'), auto, toggleAuto),
|
|
2957
|
+
OptionRow(t('settings.learnGood'), learnGood, toggleLearnGood)),
|
|
2958
|
+
// The model in force reads from the collapsed head, where it is set.
|
|
2959
|
+
], config !== null && typeof config.model === 'string' && config.model.length > 0
|
|
2960
|
+
? { summary: t('overview.model', { model: config.model }) }
|
|
2961
|
+
: undefined),
|
|
2656
2962
|
card('guidance', [
|
|
2657
2963
|
h(DirectivesEditorView, {
|
|
2658
|
-
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering
|
|
2659
|
-
|
|
2964
|
+
workspaces: rootStore.workspaces, config, directives, steering: rootStore.steering,
|
|
2965
|
+
seenAt: rootStore.profile !== null && typeof rootStore.profile === 'object' ? rootStore.profile.workspaceSeenAt : null }),
|
|
2966
|
+
], { count: directives.filter((entry) => entry.status !== 'removed').length }),
|
|
2660
2967
|
card('improve', [
|
|
2661
|
-
|
|
2662
|
-
h('label', { className: 'tacit-settings-label' }, t('settings.live')),
|
|
2663
|
-
h('input', { type: 'checkbox', checked: live, onChange: toggleLive })),
|
|
2968
|
+
OptionRow(t('settings.live'), live, toggleLive),
|
|
2664
2969
|
h('div', { className: 'tacit-panel-section' },
|
|
2665
2970
|
h('div', { className: 'tacit-report-title' }, t('panel.styleRules')),
|
|
2666
2971
|
styleRules.length === 0
|
|
@@ -2690,18 +2995,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2690
2995
|
], { count: coached.length }),
|
|
2691
2996
|
card('privacy', [
|
|
2692
2997
|
h('p', { className: 'tacit-panel-hint' }, t('privacy.stored')),
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
...retentionDays.map((days) => h('option', { key: days, value: String(days) }, String(days))))),
|
|
2702
|
-
h('div', { className: 'tacit-settings-row' },
|
|
2703
|
-
h('label', { className: 'tacit-settings-label', htmlFor: 'tacit-warn-daily' }, t('privacy.warnDaily')),
|
|
2998
|
+
FieldRow(t('privacy.retention'), h('select', {
|
|
2999
|
+
id: 'tacit-retention',
|
|
3000
|
+
className: 'tacit-select',
|
|
3001
|
+
value: String(retention),
|
|
3002
|
+
onChange: (event) => updateRootConfig({ costHistoryDays: Number(event.target.value) }),
|
|
3003
|
+
},
|
|
3004
|
+
...retentionDays.map((days) => h('option', { key: days, value: String(days) }, String(days)))), 'tacit-retention'),
|
|
3005
|
+
FieldRow(t('privacy.warnDaily'), [
|
|
2704
3006
|
h('input', {
|
|
3007
|
+
key: 'input',
|
|
2705
3008
|
id: 'tacit-warn-daily',
|
|
2706
3009
|
className: 'tacit-input',
|
|
2707
3010
|
type: 'number',
|
|
@@ -2711,16 +3014,18 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2711
3014
|
onChange: (event) => setDailyText(event.target.value),
|
|
2712
3015
|
}),
|
|
2713
3016
|
h('button', {
|
|
3017
|
+
key: 'apply',
|
|
2714
3018
|
type: 'button',
|
|
2715
3019
|
className: 'tacit-btn tacit-btn-sm',
|
|
2716
3020
|
// Two identically labelled buttons in one card: the accessible
|
|
2717
3021
|
// name has to say which threshold each one applies.
|
|
2718
3022
|
'aria-label': t('privacy.applyTo', { action: t('privacy.apply'), field: t('privacy.warnDaily') }),
|
|
2719
3023
|
onClick: () => applyWarn('costWarnDailyUsd', dailyText, setDailyText),
|
|
2720
|
-
}, t('privacy.apply'))
|
|
2721
|
-
|
|
2722
|
-
|
|
3024
|
+
}, t('privacy.apply')),
|
|
3025
|
+
], 'tacit-warn-daily'),
|
|
3026
|
+
FieldRow(t('privacy.warnMonthly'), [
|
|
2723
3027
|
h('input', {
|
|
3028
|
+
key: 'input',
|
|
2724
3029
|
id: 'tacit-warn-monthly',
|
|
2725
3030
|
className: 'tacit-input',
|
|
2726
3031
|
type: 'number',
|
|
@@ -2730,18 +3035,20 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2730
3035
|
onChange: (event) => setMonthlyText(event.target.value),
|
|
2731
3036
|
}),
|
|
2732
3037
|
h('button', {
|
|
3038
|
+
key: 'apply',
|
|
2733
3039
|
type: 'button',
|
|
2734
3040
|
className: 'tacit-btn tacit-btn-sm',
|
|
2735
3041
|
// Two identically labelled buttons in one card: the accessible
|
|
2736
3042
|
// name has to say which threshold each one applies.
|
|
2737
3043
|
'aria-label': t('privacy.applyTo', { action: t('privacy.apply'), field: t('privacy.warnMonthly') }),
|
|
2738
3044
|
onClick: () => applyWarn('costWarnMonthlyUsd', monthlyText, setMonthlyText),
|
|
2739
|
-
}, t('privacy.apply'))
|
|
3045
|
+
}, t('privacy.apply')),
|
|
3046
|
+
], 'tacit-warn-monthly'),
|
|
2740
3047
|
h('p', { className: 'tacit-panel-hint' }, t('privacy.warnHint')),
|
|
2741
3048
|
// Both clears are irreversible, so both go through the dialog.
|
|
2742
|
-
h('div', { className: 'tacit-
|
|
2743
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => openConfirm('reports') }, t('settings.clear')),
|
|
2744
|
-
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm', onClick: () => openConfirm('usage') }, t('privacy.clearUsage'))),
|
|
3049
|
+
h('div', { className: 'tacit-inline' },
|
|
3050
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm tacit-btn-quiet', onClick: () => openConfirm('reports') }, t('settings.clear')),
|
|
3051
|
+
h('button', { type: 'button', className: 'tacit-btn tacit-btn-sm tacit-btn-quiet', onClick: () => openConfirm('usage') }, t('privacy.clearUsage'))),
|
|
2745
3052
|
]),
|
|
2746
3053
|
// Always rendered (null while closed) so its one effect keeps this
|
|
2747
3054
|
// component's hook count stable.
|
|
@@ -2792,10 +3099,12 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2792
3099
|
typeof note === 'string' && note.length > 0 ? h('span', { className: 'tacit-tile-note' }, note) : null)
|
|
2793
3100
|
}
|
|
2794
3101
|
|
|
2795
|
-
/** Money tile: the period's spend
|
|
2796
|
-
function SpendTile(kit, id, label, totals) {
|
|
2797
|
-
const
|
|
2798
|
-
|
|
3102
|
+
/** Money tile: the period's spend, its unpriced-call footnote, and any second note that period carries. */
|
|
3103
|
+
function SpendTile(kit, id, label, totals, extraNote) {
|
|
3104
|
+
const notes = []
|
|
3105
|
+
if (totals.unpricedCalls > 0) notes.push(kit.t('usage.unpricedShort', { n: String(totals.unpricedCalls) }))
|
|
3106
|
+
if (typeof extraNote === 'string' && extraNote.length > 0) notes.push(extraNote)
|
|
3107
|
+
return UsageTile(id, label, usageSpend(kit, totals), notes.join(' · '))
|
|
2799
3108
|
}
|
|
2800
3109
|
|
|
2801
3110
|
/**
|
|
@@ -2858,18 +3167,33 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
2858
3167
|
seriesButton('30', t('usage.series30'))))
|
|
2859
3168
|
}
|
|
2860
3169
|
|
|
2861
|
-
/**
|
|
2862
|
-
|
|
3170
|
+
/**
|
|
3171
|
+
* `trigger` is a free string on the wire, so a host that starts tagging
|
|
3172
|
+
* runs with something this dictionary has never heard of shows the tag
|
|
3173
|
+
* itself rather than a raw `trigger.<key>` lookup miss.
|
|
3174
|
+
*/
|
|
3175
|
+
function triggerLabel(t, trigger) {
|
|
3176
|
+
const key = 'trigger.' + trigger
|
|
3177
|
+
const label = t(key)
|
|
3178
|
+
return label === key ? trigger : label
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
/**
|
|
3182
|
+
* Spend across one bucket record, costliest first, each row ending in a
|
|
3183
|
+
* share bar. `label` turns a bucket key into its display name, which is a
|
|
3184
|
+
* dictionary lookup for the closed sets and the raw key for the open ones.
|
|
3185
|
+
*/
|
|
3186
|
+
function UsageBreakdown(kit, { buckets, title, label }) {
|
|
2863
3187
|
const { t } = kit
|
|
2864
|
-
const rows = Object.entries(
|
|
3188
|
+
const rows = Object.entries(buckets).sort((a, b) => b[1].usdKnown - a[1].usdKnown)
|
|
2865
3189
|
if (rows.length === 0) return null
|
|
2866
3190
|
const top = rows[0][1].usdKnown
|
|
2867
|
-
return h('div', { className: 'tacit-usage-breakdown' },
|
|
2868
|
-
h('div', { className: 'tacit-report-title' }, t(
|
|
3191
|
+
return h('div', { key: title, className: 'tacit-usage-breakdown' },
|
|
3192
|
+
h('div', { className: 'tacit-report-title' }, t(title)),
|
|
2869
3193
|
rows.map(([type, totals]) => {
|
|
2870
3194
|
const share = top > 0 ? Math.round((totals.usdKnown / top) * 100) : 0
|
|
2871
3195
|
return h('div', { key: type, className: 'tacit-breakdown-row' },
|
|
2872
|
-
h('span', { className: 'tacit-breakdown-name' },
|
|
3196
|
+
h('span', { className: 'tacit-breakdown-name' }, label(type)),
|
|
2873
3197
|
h('span', { className: 'tacit-breakdown-calls' }, fmtTokens(totals.billedCalls)),
|
|
2874
3198
|
h('span', { className: 'tacit-breakdown-tokens' }, fmtTokens(tokensTotal(totals.tokens))),
|
|
2875
3199
|
h('span', { className: 'tacit-breakdown-usd' }, usageSpend(kit, totals)),
|
|
@@ -3009,7 +3333,9 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3009
3333
|
cell('model', item.model.length > 0 ? item.model : '—'),
|
|
3010
3334
|
cell('status', h('span', { className: 'tacit-chip tacit-status-' + item.status }, t('status.' + item.status))),
|
|
3011
3335
|
cell('calls', fmtTokens(item.billedCalls)),
|
|
3012
|
-
cell('tokens',
|
|
3336
|
+
cell('tokens', h('span', { className: 'tacit-usage-tokens' },
|
|
3337
|
+
h('span', null, fmtTokens(tokensTotal(item.tokens))),
|
|
3338
|
+
h('span', { className: 'tacit-usage-split' }, fmtTokensSplit(item.tokens)))),
|
|
3013
3339
|
cell('cost', usageSpend(kit, item)))
|
|
3014
3340
|
}
|
|
3015
3341
|
|
|
@@ -3070,19 +3396,25 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3070
3396
|
return h('div', { className: 'tacit-usage' },
|
|
3071
3397
|
h('p', { className: 'tacit-panel-hint' }, t('usage.label')),
|
|
3072
3398
|
h('div', { className: 'tacit-tiles' },
|
|
3073
|
-
SpendTile(kit, 'today', t('usage.today'), usage.today
|
|
3399
|
+
SpendTile(kit, 'today', t('usage.today'), usage.today,
|
|
3400
|
+
t('usage.autoToday', { n: String(usage.auto.today), budget: String(usage.auto.budget) })),
|
|
3074
3401
|
SpendTile(kit, 'month', t('usage.month'), usage.month),
|
|
3075
3402
|
SpendTile(kit, 'last30', t('usage.last30'), last30),
|
|
3076
3403
|
SpendTile(kit, 'lifetime', t('usage.since', { day: since }), usage.lifetime)),
|
|
3077
3404
|
h('div', { className: 'tacit-tiles' },
|
|
3078
3405
|
UsageTile('calls', t('usage.calls'), fmtTokens(last30.billedCalls)),
|
|
3406
|
+
UsageTile('tokens', t('usage.tokens'), fmtTokensSplit(last30.tokens)),
|
|
3079
3407
|
UsageTile('avg', t('usage.avgAnalysis'), last30.avgAnalysisUsd === null
|
|
3080
3408
|
? t('usage.priceUnavailable')
|
|
3081
3409
|
: fmtUsd(last30.avgAnalysisUsd)),
|
|
3082
3410
|
UsageTile('cached', t('usage.cachedRate'), fmtPct(last30.cachedInputRate)),
|
|
3083
|
-
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls))
|
|
3411
|
+
UsageTile('unpriced', t('usage.unpriced'), fmtTokens(last30.unpricedCalls)),
|
|
3412
|
+
UsageTile('failedRepair', t('usage.failedRepair'), fmtUsd(last30.failedOrRepairUsd),
|
|
3413
|
+
t('usage.failedRepairSplit', { failed: fmtUsd(last30.failedUsd), repair: fmtUsd(last30.repairUsd) }))),
|
|
3084
3414
|
BarStrip(kit, { series: which === '7' ? usage.series7 : usage.series30, which, onSeries: props.onSeries }),
|
|
3085
|
-
UsageBreakdown(kit, {
|
|
3415
|
+
UsageBreakdown(kit, { buckets: usage.byType, title: 'usage.breakdown', label: (type) => t('runtype.' + type) }),
|
|
3416
|
+
UsageBreakdown(kit, { buckets: usage.byProvider, title: 'usage.byRoute', label: (provider) => provider }),
|
|
3417
|
+
UsageBreakdown(kit, { buckets: usage.byTrigger, title: 'usage.byTrigger', label: (trigger) => triggerLabel(t, trigger) }),
|
|
3086
3418
|
WarnBar(kit, { id: 'daily', warning: usage.warnings.daily }),
|
|
3087
3419
|
WarnBar(kit, { id: 'monthly', warning: usage.warnings.monthly }),
|
|
3088
3420
|
UsageFilters(kit, {
|
|
@@ -3237,17 +3569,19 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3237
3569
|
|
|
3238
3570
|
const css = '.tacit-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}'
|
|
3239
3571
|
+ '.tacit-head{display:flex;align-items:baseline;gap:8px;margin-bottom:10px}.tacit-head-title{font-size:14px;font-weight:600;margin-right:auto}'
|
|
3240
|
-
+ '.tacit-progress{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 12px;margin-bottom:12px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}.tacit-progress-title{font-weight:600;color:var(--dsw-alias-label-primary)}'
|
|
3241
|
-
+ '.tacit-empty{color:var(--dsw-alias-label-secondary);text-align:center;
|
|
3572
|
+
+ '.tacit-progress{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 12px;margin-bottom:12px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}.tacit-progress-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin-right:auto}'
|
|
3573
|
+
+ '.tacit-empty{color:var(--dsw-alias-label-secondary);text-align:center;font-size:12px;line-height:1.6;max-width:46ch;margin-inline:auto;padding:18px 12px;border:1px dashed var(--dsw-alias-border-l1);border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-bg-layer-2) 50%, transparent)}'
|
|
3242
3574
|
+ '.tacit-error{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 12px;margin-bottom:12px;overflow-wrap:anywhere;font-size:12px}'
|
|
3243
3575
|
+ '.tacit-btn{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;cursor:pointer;padding:4px 10px}.tacit-btn:hover{border-color:var(--dsw-alias-label-primary)}.tacit-btn:disabled{opacity:.55;cursor:default}'
|
|
3244
3576
|
+ '.tacit-btn-primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:transparent}.tacit-btn-primary:hover{border-color:transparent;filter:brightness(1.1)}'
|
|
3245
3577
|
+ '.tacit-btn-sm{padding:2px 8px;font-size:11px}'
|
|
3246
3578
|
+ '.tacit-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent)}.tacit-btn-danger:hover{border-color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}'
|
|
3247
3579
|
+ '.tacit-row{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:10px 12px;margin-bottom:10px}'
|
|
3248
|
-
+ '.tacit-row-head{display:flex;flex-wrap:wrap;align-items:
|
|
3249
|
-
+ '.tacit-
|
|
3250
|
-
|
|
3580
|
+
+ '.tacit-row-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px}.tacit-row-heading{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;font-weight:600;min-width:0}.tacit-row-turn{font-size:13px;font-variant-numeric:tabular-nums}.tacit-row-time{color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:400}.tacit-row-actions{display:flex;align-items:center;gap:6px;margin-left:auto}'
|
|
3581
|
+
+ '.tacit-chip{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 7px;font-size:11px}.tacit-chip-warn{color:var(--dsw-alias-state-warn-primary)}'
|
|
3582
|
+
// Turn facts read as one dot-separated line, not four bordered chips.
|
|
3583
|
+
+ '.tacit-row-meta{display:flex;flex-wrap:wrap;align-items:center;margin-top:8px;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.tacit-row-meta>span+span::before{content:"·";margin:0 7px;opacity:.6}.tacit-row-meta-warn{color:var(--dsw-alias-state-warn-primary)}'
|
|
3584
|
+
+ '.tacit-row-prompt{display:flex;align-items:flex-start;gap:8px;margin-top:8px}.tacit-row-prompt-text{font:inherit;font-size:12px;color:var(--dsw-alias-label-primary);background:none;border:0;cursor:pointer;text-align:left;padding:0;white-space:pre-wrap;word-break:break-word;flex:1;line-height:1.55}.tacit-row-prompt-static{cursor:auto}.tacit-row-more{margin-left:8px;font-size:11px;color:var(--dsw-alias-brand-primary);white-space:nowrap}'
|
|
3251
3585
|
+ '.tacit-report{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:10px;padding-top:10px;display:flex;flex-direction:column;gap:6px}'
|
|
3252
3586
|
+ '.tacit-report-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary)}'
|
|
3253
3587
|
+ '.tacit-report-note{color:var(--dsw-alias-label-secondary);font-size:12px}'
|
|
@@ -3263,7 +3597,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3263
3597
|
+ '.tacit-problem-what{font-size:12px;color:var(--dsw-alias-label-primary);line-height:1.5}.tacit-problem-why{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.5}'
|
|
3264
3598
|
+ '.tacit-pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-primary)}'
|
|
3265
3599
|
+ '.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}'
|
|
3266
|
-
+ '.tacit-settings-title{font-size:12px;font-weight:600}
|
|
3600
|
+
+ '.tacit-settings-title{font-size:12px;font-weight:600}'
|
|
3601
|
+
// One settings row everywhere: the label takes the slack and wraps, the
|
|
3602
|
+
// control stays right-aligned, and nothing needs a 150px minimum.
|
|
3603
|
+
+ '.tacit-field{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px}.tacit-field-label{flex:1 1 150px;min-width:0;font-size:12px;line-height:1.45;color:var(--dsw-alias-label-secondary)}.tacit-field-control{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-left:auto}'
|
|
3604
|
+
+ '.tacit-inline,.tacit-settings-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px}'
|
|
3267
3605
|
+ '.tacit-settings-notice{font-size:11px;color:var(--dsw-alias-state-success-primary)}'
|
|
3268
3606
|
// The idle live region has to stay mounted to announce, but as an empty
|
|
3269
3607
|
// flex item it still claims one of the panel's 10px gaps; -5px a side
|
|
@@ -3272,22 +3610,22 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3272
3610
|
+ '.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}'
|
|
3273
3611
|
+ '.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)}'
|
|
3274
3612
|
+ '.tacit-check{width:14px;height:14px;accent-color:var(--dsw-alias-brand-primary);flex:none;margin:2px 4px 0 0}'
|
|
3275
|
-
+ '.tacit-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:
|
|
3276
|
-
+ '.tacit-progress-head{display:flex;align-items:
|
|
3613
|
+
+ '.tacit-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap}.tacit-toolbar-note{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary);margin-bottom:10px}.tacit-btn-quiet{color:var(--dsw-alias-label-secondary);background:transparent;border-color:transparent}.tacit-btn-quiet:hover{color:var(--dsw-alias-label-primary);border-color:transparent;background:var(--dsw-alias-interactive-bg-hover)}.tacit-chip-muted{opacity:.7}.tacit-row-analyzed{border-left:3px solid var(--dsw-alias-brand-primary)}'
|
|
3614
|
+
+ '.tacit-progress-head{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;margin-bottom:6px}'
|
|
3277
3615
|
+ '.tacit-progress-text{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.5}'
|
|
3278
|
-
+ '.tacit-panel{display:flex;flex-direction:column;gap:10px}.tacit-panel-section{display:flex;flex-direction:column;gap:6px}.tacit-panel-hint{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.5}'
|
|
3616
|
+
+ '.tacit-panel{display:flex;flex-direction:column;gap:10px}.tacit-panel-section{display:flex;flex-direction:column;gap:6px}.tacit-panel-hint{margin:0;font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.5}'
|
|
3279
3617
|
+ '.tacit-coached-list{display:flex;flex-direction:column;gap:6px}.tacit-coached-row{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 10px;display:flex;flex-direction:column;gap:3px}'
|
|
3280
3618
|
+ '.tacit-coached-meta{display:flex;align-items:baseline;gap:8px}.tacit-coached-turn{font-weight:600;font-size:12px}.tacit-coached-time{color:var(--dsw-alias-label-secondary);font-size:11px}'
|
|
3281
3619
|
+ '.tacit-coached-excerpt{font-size:12px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;line-height:1.5}.tacit-coached-improved{font-size:11px;color:var(--dsw-alias-label-secondary);line-height:1.5;white-space:pre-wrap;word-break:break-word}'
|
|
3282
3620
|
+ ''
|
|
3283
3621
|
+ '.tacit-modal-backdrop{z-index:200;background:#00000073;display:flex;justify-content:center;align-items:center;position:fixed;inset:0}'
|
|
3284
|
-
+ '.tacit-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(760px,100vw - 48px);max-height:min(84vh,800px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;display:flex;flex-direction:column;gap:10px;overflow-y:auto}'
|
|
3622
|
+
+ '.tacit-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(760px,100vw - 48px);max-height:min(84vh,800px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;display:flex;flex-direction:column;gap:10px;overflow-y:auto}.tacit-modal-card-sm{width:min(440px,100vw - 48px)}'
|
|
3285
3623
|
+ '.tacit-modal-head{display:flex;align-items:baseline;gap:8px}.tacit-modal-title{font-size:14px;font-weight:600;margin:0 auto 0 0}.tacit-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;background:none;border:0;border-radius:6px;padding:2px 8px;font-family:inherit;font-size:18px;line-height:1}.tacit-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}'
|
|
3286
3624
|
+ '.tacit-modal-pending{color:var(--dsw-alias-label-secondary);padding:16px 0;text-align:center}'
|
|
3287
3625
|
+ '.tacit-modal-unchanged{color:var(--dsw-alias-label-primary);padding:16px 0;text-align:center}'
|
|
3288
|
-
+ '.tacit-modal-cols{display:grid;grid-template-columns:1fr 1fr;gap:10px}.tacit-modal-col{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2);padding:8px 10px;min-width:0;max-height:300px;overflow-y:auto}'
|
|
3626
|
+
+ '.tacit-modal-body{display:flex;flex-direction:column;gap:10px}.tacit-modal-cols{display:grid;grid-template-columns:1fr 1fr;gap:10px}.tacit-modal-col{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2);padding:8px 10px;min-width:0;max-height:300px;overflow-y:auto}'
|
|
3289
3627
|
+ '.tacit-modal-col-title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary);margin-bottom:6px}'
|
|
3290
|
-
+ '.tacit-modal-rationale-text{font-size:12px;color:var(--dsw-alias-label-primary);line-height:1.6}.tacit-modal-savings{color:var(--dsw-alias-state-success-primary);font-size:12px;font-weight:600}'
|
|
3628
|
+
+ '.tacit-modal-col-improved{border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 40%, transparent)}.tacit-modal-rationale{display:flex;flex-direction:column;gap:4px}.tacit-modal-rationale-text{font-size:12px;color:var(--dsw-alias-label-primary);line-height:1.6}.tacit-modal-savings{color:var(--dsw-alias-state-success-primary);font-size:12px;font-weight:600}'
|
|
3291
3629
|
+ '.tacit-modal-actions{display:flex;gap:8px;justify-content:flex-end}'
|
|
3292
3630
|
+ '.tacit-confirm-body{margin:0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-primary)}'
|
|
3293
3631
|
+ '.tacit-confirm-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:4px}'
|
|
@@ -3298,15 +3636,16 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3298
3636
|
+ '.tacit-feedback-noted{color:var(--dsw-alias-state-success-primary);font-weight:600}'
|
|
3299
3637
|
+ '.tacit-trend{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.tacit-chip-trigger,.tacit-coached-trigger{text-transform:uppercase;letter-spacing:.04em;font-size:10px}.tacit-coached-trigger{margin-left:auto}.tacit-row-enrichment{margin-top:8px;border-left:3px solid var(--dsw-alias-brand-primary);padding:4px 10px;background:var(--dsw-alias-bg-layer-2);border-radius:6px}'
|
|
3300
3638
|
+ '.tacit-chip-trial{color:var(--dsw-alias-brand-primary)}.tacit-chip-ok{color:var(--dsw-alias-state-success-primary)}.tacit-chip-scope{color:var(--dsw-alias-brand-primary)}.tacit-select{max-width:11rem}'
|
|
3301
|
-
+ '.tacit-directive
|
|
3639
|
+
+ '.tacit-directive-input{width:min(420px,100%);flex:1}.tacit-preview summary{cursor:pointer;font-size:11px;color:var(--dsw-alias-label-secondary)}.tacit-preview pre{margin-top:6px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:8px 10px}'
|
|
3302
3640
|
+ '.tacit-rules-list{display:flex;flex-direction:column;gap:6px}.tacit-rule{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-left:3px solid var(--dsw-alias-brand-primary);border-radius:6px;padding:6px 10px;font-size:12px;line-height:1.55;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word}'
|
|
3303
3641
|
+ '.tacit-card{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}'
|
|
3304
|
-
+ '.tacit-card-head{width:100%;display:flex;align-items:center;gap:8px;text-align:left;padding:10px 12px;background:none;border:0;cursor:pointer;font:inherit;color:inherit}'
|
|
3642
|
+
+ '.tacit-card-head{width:100%;display:flex;align-items:center;gap:8px;text-align:left;padding:10px 12px;background:none;border:0;border-radius:7px;cursor:pointer;font:inherit;color:inherit}.tacit-card-head:hover{background:var(--dsw-alias-interactive-bg-hover)}'
|
|
3305
3643
|
+ '.tacit-card-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary)}'
|
|
3306
3644
|
+ '.tacit-card-title{font-size:13px;font-weight:600}.tacit-card-summary{font-size:11px;color:var(--dsw-alias-label-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}'
|
|
3307
3645
|
+ '.tacit-card-count{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:1px 7px;font-size:11px;font-variant-numeric:tabular-nums}'
|
|
3308
|
-
|
|
3309
|
-
+ '.tacit-
|
|
3646
|
+
// One disclosure glyph everywhere: a › that rotates down when open.
|
|
3647
|
+
+ '.tacit-chevron{display:inline-block;font-size:12px;line-height:1;color:inherit;transition:transform .15s ease}.tacit-chevron-open{transform:rotate(90deg)}.tacit-card-chevron{margin-left:auto;color:var(--dsw-alias-label-secondary)}'
|
|
3648
|
+
+ '.tacit-card-body{display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 60%, transparent)}.tacit-card-body[hidden]{display:none}'
|
|
3310
3649
|
+ '.tacit-usage{display:flex;flex-direction:column;gap:10px}'
|
|
3311
3650
|
+ '.tacit-tiles{display:flex;flex-wrap:wrap;gap:8px}'
|
|
3312
3651
|
+ '.tacit-tile{flex:1 1 140px;display:flex;flex-direction:column;gap:2px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 10px;min-width:0}'
|
|
@@ -3325,9 +3664,11 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3325
3664
|
+ '.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)}'
|
|
3326
3665
|
+ '.tacit-usage-runs{display:flex;flex-direction:column;gap:6px}'
|
|
3327
3666
|
+ '.tacit-usage-table{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;overflow:hidden}'
|
|
3328
|
-
+ '.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
|
|
3667
|
+
+ '.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}'
|
|
3329
3668
|
+ '.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)}'
|
|
3330
3669
|
+ '.tacit-usage-cell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}'
|
|
3670
|
+
// The cell is nowrap, so the in/out split needs its own block to sit under the total.
|
|
3671
|
+
+ '.tacit-usage-tokens{display:flex;flex-direction:column}.tacit-usage-split{font-size:10px;color:var(--dsw-alias-label-secondary)}'
|
|
3331
3672
|
+ '.tacit-run-toggle{width:100%;text-align:left}'
|
|
3332
3673
|
+ '.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}'
|
|
3333
3674
|
+ '.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}'
|
|
@@ -3339,7 +3680,39 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3339
3680
|
+ '.tacit-pricing-table>.tacit-pricing-row:first-child{border-top:0}.tacit-pricing-head{color:var(--dsw-alias-label-secondary);font-weight:600;background:var(--dsw-alias-bg-layer-2)}'
|
|
3340
3681
|
+ '.tacit-pricing-cell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tacit-pricing-model{font-weight:600}'
|
|
3341
3682
|
+ '.tacit-pricing-error{font-size:11px;color:var(--dsw-alias-state-warn-primary);line-height:1.5}'
|
|
3342
|
-
|
|
3683
|
+
// Directive cards: a status-coloured rail and pill badge, the text on
|
|
3684
|
+
// its own line, and a wrapping actions row, so nothing fights for width.
|
|
3685
|
+
+ '.tacit-dir-list{display:flex;flex-direction:column;gap:8px}'
|
|
3686
|
+
+ '.tacit-dir{--tacit-status:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-left:3px solid var(--tacit-status);border-radius:8px;padding:8px 10px 8px 12px;display:flex;flex-direction:column;gap:6px;min-width:0}'
|
|
3687
|
+
+ '.tacit-dir-active{--tacit-status:var(--dsw-alias-state-success-primary)}.tacit-dir-trial{--tacit-status:var(--dsw-alias-brand-primary)}.tacit-dir-retired{--tacit-status:var(--dsw-alias-state-warn-primary)}'
|
|
3688
|
+
+ '.tacit-dir-head{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px}.tacit-dir-start{margin-left:auto}'
|
|
3689
|
+
+ '.tacit-badge{--tacit-badge:var(--tacit-status, var(--dsw-alias-label-secondary));display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:1px 8px 1px 6px;font-size:11px;font-weight:600;line-height:1.5;color:var(--tacit-badge);background:color-mix(in srgb, var(--tacit-badge) 14%, transparent)}'
|
|
3690
|
+
+ '.tacit-badge-active{--tacit-status:var(--dsw-alias-state-success-primary)}.tacit-badge-trial{--tacit-status:var(--dsw-alias-brand-primary)}.tacit-badge-retired{--tacit-status:var(--dsw-alias-state-warn-primary)}.tacit-badge-muted{--tacit-status:var(--dsw-alias-label-secondary)}'
|
|
3691
|
+
+ '.tacit-badge-dot{width:6px;height:6px;border-radius:50%;background:var(--tacit-badge);flex:none}'
|
|
3692
|
+
+ '.tacit-dir-text{font-size:13px;line-height:1.55;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word}.tacit-dir-off .tacit-dir-text{opacity:.5;text-decoration:line-through}'
|
|
3693
|
+
+ '.tacit-dir-actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;font-size:11px;color:var(--dsw-alias-label-secondary)}'
|
|
3694
|
+
+ '.tacit-dir-enabled{display:inline-flex;align-items:center;gap:5px;cursor:pointer}.tacit-dir-enabled .tacit-check{margin:0}.tacit-dir-remove{margin-left:auto}'
|
|
3695
|
+
+ '.tacit-select-sm{font-size:11px;padding:2px 6px;width:auto}'
|
|
3696
|
+
+ '.tacit-dir-receipt summary{cursor:pointer;font-size:11px;color:var(--dsw-alias-label-secondary)}.tacit-dir-receipt summary:hover{color:var(--dsw-alias-label-primary)}'
|
|
3697
|
+
+ '.tacit-dir-add{display:flex;flex-wrap:wrap;align-items:center;gap:6px}'
|
|
3698
|
+
+ '.tacit-options{display:flex;flex-direction:column;gap:6px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:8px 10px}'
|
|
3699
|
+
+ '.tacit-option{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:1.5;cursor:pointer}.tacit-option .tacit-check{margin-top:2px}'
|
|
3700
|
+
// Hairlines between rows keep the group one soft box, not a list of boxes.
|
|
3701
|
+
+ '.tacit-options>*+*{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l1) 60%, transparent);padding-top:6px}.tacit-options .tacit-select,.tacit-options .tacit-input{background:var(--dsw-alias-bg-layer-1)}'
|
|
3702
|
+
// The receipt: tiles, a lifecycle timeline, trigger chips, then the
|
|
3703
|
+
// identity grid.
|
|
3704
|
+
+ '.tacit-receipt{margin-top:8px;padding-top:10px;border-top:1px dashed var(--dsw-alias-border-l1);display:flex;flex-direction:column;gap:10px}'
|
|
3705
|
+
+ '.tacit-receipt-tiles .tacit-tile{flex-basis:120px;background:var(--dsw-alias-bg-layer-1)}.tacit-receipt-tile-note{font-size:10px;color:var(--dsw-alias-label-secondary);line-height:1.4}.tacit-tile-muted{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary)}'
|
|
3706
|
+
+ '.tacit-timeline{list-style:none;margin:0;padding:0;display:flex}'
|
|
3707
|
+
+ '.tacit-timeline-step{flex:1;position:relative;min-width:0;padding:14px 6px 0 0;font-size:11px}'
|
|
3708
|
+
+ '.tacit-timeline-step::before{content:"";position:absolute;left:0;top:0;width:9px;height:9px;border-radius:50%;box-sizing:border-box;background:var(--dsw-alias-brand-primary)}'
|
|
3709
|
+
+ '.tacit-timeline-step::after{content:"";position:absolute;left:12px;right:2px;top:4px;height:1px;background:var(--dsw-alias-border-l1)}.tacit-timeline-step:last-child::after{display:none}'
|
|
3710
|
+
+ '.tacit-timeline-never::before{background:var(--dsw-alias-bg-layer-2);border:1.5px solid var(--dsw-alias-border-l1)}.tacit-timeline-never .tacit-timeline-value{color:var(--dsw-alias-label-secondary);font-weight:400}'
|
|
3711
|
+
+ '.tacit-timeline-label{display:block;color:var(--dsw-alias-label-secondary)}.tacit-timeline-value{display:block;font-weight:600;font-variant-numeric:tabular-nums}'
|
|
3712
|
+
+ '.tacit-receipt-row{display:flex;flex-wrap:wrap;align-items:center;gap:4px 6px}.tacit-receipt-label{font-size:11px;color:var(--dsw-alias-label-secondary);margin-right:2px}'
|
|
3713
|
+
+ '.tacit-receipt-meta{margin:0;display:grid;grid-template-columns:max-content minmax(0,1fr);gap:3px 12px;font-size:11px}.tacit-receipt-meta dt{color:var(--dsw-alias-label-secondary)}.tacit-receipt-meta dd{margin:0;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}'
|
|
3714
|
+
+ '.tacit-receipt-actions{display:flex;justify-content:flex-end}'
|
|
3715
|
+
+ '@media (max-width:640px){.tacit-field-label{flex-basis:100%}.tacit-field-control{margin-left:0}.tacit-modal-cols{grid-template-columns:1fr}.tacit-usage-row{display:flex;flex-direction:column}.tacit-usage-cell{white-space:normal}.tacit-pricing-row{display:flex;flex-direction:column}.tacit-pricing-cell{white-space:normal}.tacit-tile{flex-basis:45%}.tacit-breakdown-row{grid-template-columns:minmax(0,1fr) 4rem 5rem}}'
|
|
3343
3716
|
|
|
3344
3717
|
function injectCss() {
|
|
3345
3718
|
const tagId = 'dsh-tacit/styles.css'
|
|
@@ -3438,11 +3811,17 @@ if (typeof window === 'undefined' || window.__ModuleLoader__ === undefined || ty
|
|
|
3438
3811
|
refreshPricing,
|
|
3439
3812
|
openConfirm,
|
|
3440
3813
|
closeConfirm,
|
|
3814
|
+
confirmAction,
|
|
3441
3815
|
fetchBootstrapPreview,
|
|
3816
|
+
fetchReceipt,
|
|
3817
|
+
storeFor,
|
|
3818
|
+
analyzeTurn,
|
|
3819
|
+
coachSelected,
|
|
3442
3820
|
clearAllRoot,
|
|
3443
3821
|
clearUsageHistory,
|
|
3444
3822
|
runNotice,
|
|
3445
3823
|
fmtRate,
|
|
3824
|
+
fmtTokensSplit,
|
|
3446
3825
|
ConfirmDialog,
|
|
3447
3826
|
UsageCard,
|
|
3448
3827
|
PricingCard,
|