@cat-factory/app 0.47.8 → 0.47.10
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/app/components/brainstorm/BrainstormWindow.vue +97 -59
- package/app/components/clarity/ClarityReviewWindow.vue +85 -59
- package/app/components/consensus/ConsensusSessionWindow.vue +72 -34
- package/app/components/focus/BlockFocusView.vue +14 -10
- package/app/components/followUp/FollowUpWindow.vue +51 -26
- package/app/components/humanTest/HumanTestWindow.vue +70 -35
- package/app/components/panels/AgentStepDetail.vue +1 -0
- package/app/components/panels/InspectorPanel.vue +1 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +131 -82
- package/app/components/spec/ServiceSpecWindow.vue +54 -28
- package/app/components/testing/TestReportWindow.vue +82 -39
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +49 -32
- package/app/pages/index.vue +1 -0
- package/i18n/locales/en.json +580 -0
- package/i18n/locales/es.json +547 -0
- package/i18n/locales/fr.json +547 -0
- package/i18n/locales/pl.json +547 -0
- package/i18n/locales/uk.json +547 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// findings or hits the iteration cap. The clarified bug report — not the original
|
|
9
9
|
// description — is what every downstream agent step (the bug investigator, the coder)
|
|
10
10
|
// consumes.
|
|
11
|
+
import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
|
|
11
12
|
import { parseOutputOutline } from '~/utils/agentOutput'
|
|
12
13
|
import type {
|
|
13
14
|
ClarityItemStatus,
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
const board = useBoardStore()
|
|
22
23
|
const clarity = useClarityStore()
|
|
23
24
|
const toast = useToast()
|
|
25
|
+
const { t } = useI18n()
|
|
24
26
|
|
|
25
27
|
// Draft replies, keyed by item id, so editing one item doesn't disturb others.
|
|
26
28
|
const drafts = ref<Record<string, string>>({})
|
|
@@ -111,6 +113,27 @@ const STATUS_COLOR = {
|
|
|
111
113
|
recommend_requested: 'primary',
|
|
112
114
|
} as const satisfies Record<ReviewItemStatus, string>
|
|
113
115
|
|
|
116
|
+
// Exhaustive enum→label maps (literal keys keep the typed-key drift guard live).
|
|
117
|
+
const SEVERITY_LABELS: Record<ReviewItemSeverity, string> = {
|
|
118
|
+
low: 'clarity.severity.low',
|
|
119
|
+
medium: 'clarity.severity.medium',
|
|
120
|
+
high: 'clarity.severity.high',
|
|
121
|
+
}
|
|
122
|
+
const CATEGORY_LABELS: Record<ReviewItemCategory, string> = {
|
|
123
|
+
gap: 'clarity.category.gap',
|
|
124
|
+
clarification: 'clarity.category.clarification',
|
|
125
|
+
assumption: 'clarity.category.assumption',
|
|
126
|
+
risk: 'clarity.category.risk',
|
|
127
|
+
question: 'clarity.category.question',
|
|
128
|
+
}
|
|
129
|
+
const STATUS_LABELS: Record<ReviewItemStatus, string> = {
|
|
130
|
+
open: 'clarity.itemStatus.open',
|
|
131
|
+
answered: 'clarity.itemStatus.answered',
|
|
132
|
+
resolved: 'clarity.itemStatus.resolved',
|
|
133
|
+
dismissed: 'clarity.itemStatus.dismissed',
|
|
134
|
+
recommend_requested: 'clarity.itemStatus.recommend_requested',
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
function notifyError(title: string, e: unknown) {
|
|
115
138
|
toast.add({
|
|
116
139
|
title,
|
|
@@ -128,7 +151,7 @@ async function submitReply(item: ClarityReviewItem) {
|
|
|
128
151
|
await clarity.reply(review.value, item.id, text)
|
|
129
152
|
drafts.value = { ...drafts.value, [item.id]: '' }
|
|
130
153
|
} catch (e) {
|
|
131
|
-
notifyError('
|
|
154
|
+
notifyError(t('clarity.error.saveAnswer'), e)
|
|
132
155
|
}
|
|
133
156
|
}
|
|
134
157
|
|
|
@@ -137,7 +160,7 @@ async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus)
|
|
|
137
160
|
try {
|
|
138
161
|
await clarity.setItemStatus(review.value, item.id, itemStatus)
|
|
139
162
|
} catch (e) {
|
|
140
|
-
notifyError('
|
|
163
|
+
notifyError(t('clarity.error.updateFinding'), e)
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
166
|
|
|
@@ -146,7 +169,7 @@ async function incorporate(feedback?: string) {
|
|
|
146
169
|
try {
|
|
147
170
|
await clarity.incorporate(review.value, feedback)
|
|
148
171
|
} catch (e) {
|
|
149
|
-
notifyError('
|
|
172
|
+
notifyError(t('clarity.error.incorporate'), e)
|
|
150
173
|
return
|
|
151
174
|
}
|
|
152
175
|
redoComment.value = ''
|
|
@@ -154,8 +177,8 @@ async function incorporate(feedback?: string) {
|
|
|
154
177
|
// The fold + re-review now run in the durable driver. Hand the user back to the board;
|
|
155
178
|
// a notification calls them back only if the re-review needs more input.
|
|
156
179
|
toast.add({
|
|
157
|
-
title: '
|
|
158
|
-
description:
|
|
180
|
+
title: t('clarity.toast.clarifyingTitle'),
|
|
181
|
+
description: t('clarity.toast.clarifyingDescription'),
|
|
159
182
|
icon: 'i-lucide-wand-sparkles',
|
|
160
183
|
})
|
|
161
184
|
close()
|
|
@@ -165,17 +188,18 @@ async function reReview() {
|
|
|
165
188
|
if (!blockId.value) return
|
|
166
189
|
try {
|
|
167
190
|
const updated = await clarity.reReview(blockId.value)
|
|
191
|
+
const newFindings = clarity.openCount(updated)
|
|
168
192
|
toast.add({
|
|
169
193
|
title:
|
|
170
194
|
updated.status === 'incorporated'
|
|
171
|
-
? '
|
|
195
|
+
? t('clarity.toast.reReviewSatisfied')
|
|
172
196
|
: updated.status === 'exceeded'
|
|
173
|
-
? '
|
|
174
|
-
:
|
|
197
|
+
? t('clarity.toast.reReviewExceeded')
|
|
198
|
+
: t('clarity.toast.reReviewNewFindings', { count: newFindings }, newFindings),
|
|
175
199
|
icon: 'i-lucide-sparkles',
|
|
176
200
|
})
|
|
177
201
|
} catch (e) {
|
|
178
|
-
notifyError('
|
|
202
|
+
notifyError(t('clarity.error.reReview'), e)
|
|
179
203
|
}
|
|
180
204
|
}
|
|
181
205
|
|
|
@@ -184,9 +208,9 @@ async function proceed() {
|
|
|
184
208
|
acting.value = true
|
|
185
209
|
try {
|
|
186
210
|
await clarity.proceed(blockId.value)
|
|
187
|
-
toast.add({ title: '
|
|
211
|
+
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
188
212
|
} catch (e) {
|
|
189
|
-
notifyError('
|
|
213
|
+
notifyError(t('clarity.error.proceed'), e)
|
|
190
214
|
} finally {
|
|
191
215
|
acting.value = false
|
|
192
216
|
}
|
|
@@ -198,15 +222,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
198
222
|
try {
|
|
199
223
|
await clarity.resolveExceeded(blockId.value, choice)
|
|
200
224
|
if (choice === 'stop-reset') {
|
|
201
|
-
toast.add({ title: '
|
|
225
|
+
toast.add({ title: t('clarity.toast.taskReset'), icon: 'i-lucide-undo' })
|
|
202
226
|
close()
|
|
203
227
|
} else if (choice === 'proceed') {
|
|
204
|
-
toast.add({ title: '
|
|
228
|
+
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
205
229
|
} else {
|
|
206
|
-
toast.add({ title: '
|
|
230
|
+
toast.add({ title: t('clarity.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
|
|
207
231
|
}
|
|
208
232
|
} catch (e) {
|
|
209
|
-
notifyError('
|
|
233
|
+
notifyError(t('clarity.error.resolve'), e)
|
|
210
234
|
} finally {
|
|
211
235
|
acting.value = false
|
|
212
236
|
}
|
|
@@ -231,12 +255,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
231
255
|
<UIcon name="i-lucide-bug" class="h-5 w-5 text-indigo-300" />
|
|
232
256
|
</div>
|
|
233
257
|
<div class="min-w-0">
|
|
234
|
-
<h1 class="truncate text-base font-semibold text-white">
|
|
258
|
+
<h1 class="truncate text-base font-semibold text-white">{{ t('clarity.title') }}</h1>
|
|
235
259
|
<p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
|
|
236
260
|
</div>
|
|
237
261
|
<div class="ml-auto flex items-center gap-1.5">
|
|
238
262
|
<UBadge v-if="review" color="neutral" variant="subtle" size="sm">
|
|
239
|
-
|
|
263
|
+
{{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
|
|
240
264
|
</UBadge>
|
|
241
265
|
<UButton icon="i-lucide-x" color="neutral" variant="ghost" size="sm" @click="close" />
|
|
242
266
|
</div>
|
|
@@ -246,11 +270,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
246
270
|
<!-- main column -->
|
|
247
271
|
<div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
|
|
248
272
|
<p class="mb-4 text-sm text-slate-400">
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
273
|
+
<i18n-t keypath="clarity.intro" tag="span" scope="global">
|
|
274
|
+
<template #level>{{ block?.level ?? t('clarity.itemFallback') }}</template>
|
|
275
|
+
<template #answer
|
|
276
|
+
><span class="text-slate-300">{{ t('clarity.introAnswer') }}</span></template
|
|
277
|
+
>
|
|
278
|
+
<template #dismiss
|
|
279
|
+
><span class="text-slate-300">{{ t('clarity.introDismiss') }}</span></template
|
|
280
|
+
>
|
|
281
|
+
</i18n-t>
|
|
254
282
|
</p>
|
|
255
283
|
|
|
256
284
|
<!-- empty state — the reviewer runs automatically as the first pipeline
|
|
@@ -259,8 +287,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
259
287
|
v-if="!review && !busy && !loading"
|
|
260
288
|
class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
|
|
261
289
|
>
|
|
262
|
-
|
|
263
|
-
pipeline starts.
|
|
290
|
+
{{ t('clarity.empty') }}
|
|
264
291
|
</div>
|
|
265
292
|
|
|
266
293
|
<!-- working state (initial fetch on open, or a reviewer pass running) -->
|
|
@@ -269,7 +296,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
269
296
|
class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
|
|
270
297
|
>
|
|
271
298
|
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
|
|
272
|
-
{{ loading && !busy ? '
|
|
299
|
+
{{ loading && !busy ? t('clarity.loadingReview') : t('clarity.triaging') }}
|
|
273
300
|
</div>
|
|
274
301
|
|
|
275
302
|
<template v-else-if="review">
|
|
@@ -279,16 +306,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
279
306
|
class="mb-4 flex items-center gap-2 rounded-lg border border-emerald-900/60 bg-emerald-950/30 p-4 text-sm text-emerald-300"
|
|
280
307
|
>
|
|
281
308
|
<UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
|
|
282
|
-
|
|
283
|
-
uses.
|
|
309
|
+
{{ t('clarity.converged') }}
|
|
284
310
|
</div>
|
|
285
311
|
|
|
286
312
|
<!-- iteration cap hit -->
|
|
287
313
|
<IterationCapPrompt
|
|
288
314
|
v-else-if="exceeded"
|
|
289
315
|
class="mb-4"
|
|
290
|
-
:heading="
|
|
291
|
-
detail="
|
|
316
|
+
:heading="t('clarity.capHeading', { max: maxIterations })"
|
|
317
|
+
:detail="t('clarity.capDetail')"
|
|
292
318
|
:loading="acting"
|
|
293
319
|
@resolve="resolveExceeded"
|
|
294
320
|
/>
|
|
@@ -301,12 +327,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
301
327
|
>
|
|
302
328
|
<UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
|
|
303
329
|
<span v-if="incorporating">
|
|
304
|
-
|
|
305
|
-
notify you only if more input is needed.
|
|
330
|
+
{{ t('clarity.incorporatingStage') }}
|
|
306
331
|
</span>
|
|
307
332
|
<span v-else>
|
|
308
|
-
|
|
309
|
-
more input is needed.
|
|
333
|
+
{{ t('clarity.reReviewingStage') }}
|
|
310
334
|
</span>
|
|
311
335
|
</div>
|
|
312
336
|
|
|
@@ -327,10 +351,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
327
351
|
<div class="flex flex-wrap items-center gap-1.5">
|
|
328
352
|
<span class="text-sm font-medium text-white">{{ item.title }}</span>
|
|
329
353
|
<UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
|
|
330
|
-
{{ item.severity }}
|
|
354
|
+
{{ t(SEVERITY_LABELS[item.severity]) }}
|
|
331
355
|
</UBadge>
|
|
332
356
|
<UBadge size="xs" variant="outline" color="neutral">
|
|
333
|
-
{{ item.category }}
|
|
357
|
+
{{ t(CATEGORY_LABELS[item.category]) }}
|
|
334
358
|
</UBadge>
|
|
335
359
|
<UBadge
|
|
336
360
|
size="xs"
|
|
@@ -338,7 +362,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
338
362
|
:color="STATUS_COLOR[item.status]"
|
|
339
363
|
class="ml-auto"
|
|
340
364
|
>
|
|
341
|
-
{{ item.status }}
|
|
365
|
+
{{ t(STATUS_LABELS[item.status]) }}
|
|
342
366
|
</UBadge>
|
|
343
367
|
</div>
|
|
344
368
|
<p class="mt-1 whitespace-pre-line text-sm text-slate-400">
|
|
@@ -351,7 +375,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
351
375
|
class="mt-2 rounded-md border-l-2 border-slate-700 bg-slate-950/40 px-3 py-1.5 text-sm text-slate-300"
|
|
352
376
|
>
|
|
353
377
|
<span class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
354
|
-
|
|
378
|
+
{{ t('clarity.answerLabel') }}
|
|
355
379
|
</span>
|
|
356
380
|
<p class="whitespace-pre-line">{{ item.reply }}</p>
|
|
357
381
|
</div>
|
|
@@ -365,7 +389,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
365
389
|
autoresize
|
|
366
390
|
size="sm"
|
|
367
391
|
class="mt-2 w-full"
|
|
368
|
-
:placeholder="
|
|
392
|
+
:placeholder="
|
|
393
|
+
item.reply
|
|
394
|
+
? t('clarity.refineAnswerPlaceholder')
|
|
395
|
+
: t('clarity.answerPlaceholder')
|
|
396
|
+
"
|
|
369
397
|
:disabled="frozen"
|
|
370
398
|
/>
|
|
371
399
|
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
@@ -377,7 +405,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
377
405
|
:disabled="!(drafts[item.id] ?? '').trim() || frozen"
|
|
378
406
|
@click="submitReply(item)"
|
|
379
407
|
>
|
|
380
|
-
|
|
408
|
+
{{ t('clarity.saveAnswer') }}
|
|
381
409
|
</UButton>
|
|
382
410
|
<UButton
|
|
383
411
|
color="neutral"
|
|
@@ -387,7 +415,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
387
415
|
:disabled="frozen"
|
|
388
416
|
@click="setStatus(item, 'dismissed')"
|
|
389
417
|
>
|
|
390
|
-
|
|
418
|
+
{{ t('clarity.dismissIrrelevant') }}
|
|
391
419
|
</UButton>
|
|
392
420
|
</div>
|
|
393
421
|
</template>
|
|
@@ -402,7 +430,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
402
430
|
:disabled="frozen"
|
|
403
431
|
@click="setStatus(item, 'open')"
|
|
404
432
|
>
|
|
405
|
-
|
|
433
|
+
{{ t('clarity.reopen') }}
|
|
406
434
|
</UButton>
|
|
407
435
|
</div>
|
|
408
436
|
</div>
|
|
@@ -415,7 +443,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
415
443
|
<div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
|
|
416
444
|
<UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
|
|
417
445
|
<span class="font-semibold uppercase tracking-wide">
|
|
418
|
-
{{ incorporated ? '
|
|
446
|
+
{{ incorporated ? t('clarity.docHeading') : t('clarity.docHeadingDraft') }}
|
|
419
447
|
</span>
|
|
420
448
|
</div>
|
|
421
449
|
<div v-for="s in outline.sections" :key="s.id" class="mb-2">
|
|
@@ -450,19 +478,19 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
450
478
|
<div class="flex flex-col gap-4 px-4 py-5">
|
|
451
479
|
<div v-if="review" class="space-y-2 text-xs text-slate-400">
|
|
452
480
|
<div class="flex items-center justify-between">
|
|
453
|
-
<span>
|
|
481
|
+
<span>{{ t('clarity.rail.findings') }}</span>
|
|
454
482
|
<span class="text-slate-300">{{ review.items.length }}</span>
|
|
455
483
|
</div>
|
|
456
484
|
<div class="flex items-center justify-between">
|
|
457
|
-
<span>
|
|
485
|
+
<span>{{ t('clarity.rail.open') }}</span>
|
|
458
486
|
<span class="text-slate-300">{{ openCount }}</span>
|
|
459
487
|
</div>
|
|
460
488
|
<div class="flex items-center justify-between">
|
|
461
|
-
<span>
|
|
489
|
+
<span>{{ t('clarity.rail.answered') }}</span>
|
|
462
490
|
<span class="text-slate-300">{{ answeredCount }}</span>
|
|
463
491
|
</div>
|
|
464
492
|
<div v-if="review.model" class="flex items-center justify-between">
|
|
465
|
-
<span>
|
|
493
|
+
<span>{{ t('clarity.rail.model') }}</span>
|
|
466
494
|
<span class="truncate pl-2 text-slate-500">{{ review.model }}</span>
|
|
467
495
|
</div>
|
|
468
496
|
</div>
|
|
@@ -481,7 +509,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
481
509
|
:loading="acting"
|
|
482
510
|
@click="proceed"
|
|
483
511
|
>
|
|
484
|
-
|
|
512
|
+
{{ t('clarity.proceedNothing') }}
|
|
485
513
|
</UButton>
|
|
486
514
|
<UButton
|
|
487
515
|
v-else
|
|
@@ -493,17 +521,16 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
493
521
|
:disabled="!canIncorporate"
|
|
494
522
|
@click="incorporate()"
|
|
495
523
|
>
|
|
496
|
-
|
|
524
|
+
{{ t('clarity.incorporateAnswers') }}
|
|
497
525
|
</UButton>
|
|
498
526
|
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
499
527
|
<template v-if="canProceed">
|
|
500
|
-
|
|
528
|
+
{{ t('clarity.hint.proceed') }}
|
|
501
529
|
</template>
|
|
502
530
|
<template v-else-if="canIncorporate">
|
|
503
|
-
|
|
504
|
-
automatically.
|
|
531
|
+
{{ t('clarity.hint.incorporate') }}
|
|
505
532
|
</template>
|
|
506
|
-
<template v-else>
|
|
533
|
+
<template v-else> {{ t('clarity.hint.answerAll') }} </template>
|
|
507
534
|
</p>
|
|
508
535
|
</div>
|
|
509
536
|
|
|
@@ -517,7 +544,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
517
544
|
:loading="busy"
|
|
518
545
|
@click="reReview"
|
|
519
546
|
>
|
|
520
|
-
{{ busy ? '
|
|
547
|
+
{{ busy ? t('clarity.reReviewing') : t('clarity.looksGoodReReview') }}
|
|
521
548
|
</UButton>
|
|
522
549
|
<UButton
|
|
523
550
|
color="neutral"
|
|
@@ -527,7 +554,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
527
554
|
icon="i-lucide-pencil"
|
|
528
555
|
@click="showRedo = !showRedo"
|
|
529
556
|
>
|
|
530
|
-
|
|
557
|
+
{{ t('clarity.redoIncorporation') }}
|
|
531
558
|
</UButton>
|
|
532
559
|
<div v-if="showRedo" class="space-y-2">
|
|
533
560
|
<UTextarea
|
|
@@ -536,7 +563,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
536
563
|
autoresize
|
|
537
564
|
size="sm"
|
|
538
565
|
class="w-full"
|
|
539
|
-
placeholder="
|
|
566
|
+
:placeholder="t('clarity.redoPlaceholder')"
|
|
540
567
|
/>
|
|
541
568
|
<UButton
|
|
542
569
|
color="primary"
|
|
@@ -548,12 +575,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
548
575
|
:disabled="!redoComment.trim()"
|
|
549
576
|
@click="incorporate(redoComment.trim())"
|
|
550
577
|
>
|
|
551
|
-
|
|
578
|
+
{{ t('clarity.redoWithDirection') }}
|
|
552
579
|
</UButton>
|
|
553
580
|
</div>
|
|
554
581
|
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
555
|
-
|
|
556
|
-
merged, redo it with a comment instead.
|
|
582
|
+
{{ t('clarity.redoHint') }}
|
|
557
583
|
</p>
|
|
558
584
|
</div>
|
|
559
585
|
|
|
@@ -561,7 +587,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
561
587
|
v-if="review && incorporated"
|
|
562
588
|
class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
|
|
563
589
|
>
|
|
564
|
-
|
|
590
|
+
{{ t('clarity.incorporatedFooter') }}
|
|
565
591
|
</div>
|
|
566
592
|
</div>
|
|
567
593
|
</aside>
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { computed } from 'vue'
|
|
10
10
|
import type { ConsensusContribution, ConsensusSession } from '~/types/consensus'
|
|
11
11
|
|
|
12
|
+
const { t, n } = useI18n()
|
|
13
|
+
|
|
12
14
|
const board = useBoardStore()
|
|
13
15
|
const consensus = useConsensusStore()
|
|
14
16
|
|
|
@@ -24,36 +26,60 @@ const session = computed<ConsensusSession | null>(() =>
|
|
|
24
26
|
)
|
|
25
27
|
const loading = computed(() => (blockId.value ? consensus.isLoading(blockId.value) : false))
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'
|
|
29
|
+
// Exhaustive enum→key maps (literal key strings keep the typed-key drift guard live,
|
|
30
|
+
// vs a runtime-built `consensus.strategy.${value}`).
|
|
31
|
+
const STRATEGY_LABEL_KEYS: Record<string, string> = {
|
|
32
|
+
'specialist-panel': 'consensus.strategy.specialistPanel',
|
|
33
|
+
debate: 'consensus.strategy.debate',
|
|
34
|
+
'ranked-voting': 'consensus.strategy.rankedVoting',
|
|
35
|
+
}
|
|
36
|
+
const ROUND_LABEL_KEYS: Record<string, string> = {
|
|
37
|
+
draft: 'consensus.round.draft',
|
|
38
|
+
critique: 'consensus.round.critique',
|
|
39
|
+
score: 'consensus.round.score',
|
|
40
|
+
}
|
|
41
|
+
const STATUS_LABEL_KEYS: Record<string, string> = {
|
|
42
|
+
running: 'consensus.status.running',
|
|
43
|
+
synthesizing: 'consensus.status.synthesizing',
|
|
44
|
+
done: 'consensus.status.done',
|
|
45
|
+
failed: 'consensus.status.failed',
|
|
31
46
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
const STATUS_CLASS: Record<string, string> = {
|
|
48
|
+
running: 'bg-sky-500/15 text-sky-300',
|
|
49
|
+
synthesizing: 'bg-indigo-500/15 text-indigo-300',
|
|
50
|
+
done: 'bg-emerald-500/15 text-emerald-300',
|
|
51
|
+
failed: 'bg-rose-500/15 text-rose-300',
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
function strategyLabel(strategy: string): string {
|
|
55
|
+
const key = STRATEGY_LABEL_KEYS[strategy]
|
|
56
|
+
return key ? t(key) : strategy
|
|
57
|
+
}
|
|
58
|
+
function roundLabel(kind: string | null | undefined): string {
|
|
59
|
+
if (!kind) return t('consensus.round.contributions')
|
|
60
|
+
const key = ROUND_LABEL_KEYS[kind]
|
|
61
|
+
return key ? t(key) : kind
|
|
62
|
+
}
|
|
63
|
+
function statusLabel(status: string): string {
|
|
64
|
+
const key = STATUS_LABEL_KEYS[status]
|
|
65
|
+
return key ? t(key) : status
|
|
43
66
|
}
|
|
44
67
|
|
|
45
68
|
/** Anonymous label (Expert A/B/…) for a participant, matching the backend's ordering. */
|
|
46
69
|
function anonLabel(participantId: string): string {
|
|
47
70
|
const idx = session.value?.participants.findIndex((p) => p.id === participantId) ?? -1
|
|
48
|
-
return
|
|
71
|
+
return t('consensus.expert', { letter: String.fromCharCode(65 + (idx < 0 ? 0 : idx % 26)) })
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
function roleFor(participantId: string): string {
|
|
52
|
-
return
|
|
75
|
+
return (
|
|
76
|
+
session.value?.participants.find((p) => p.id === participantId)?.role ??
|
|
77
|
+
t('consensus.participant')
|
|
78
|
+
)
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
function pct(
|
|
56
|
-
return
|
|
81
|
+
function pct(value: number | null | undefined): string {
|
|
82
|
+
return value == null ? '—' : n(value, 'percent')
|
|
57
83
|
}
|
|
58
84
|
|
|
59
85
|
function topScore(c: ConsensusContribution): { label: string; value: number } | null {
|
|
@@ -80,20 +106,27 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
80
106
|
</div>
|
|
81
107
|
<div class="min-w-0 flex-1">
|
|
82
108
|
<h2 class="truncate text-sm font-semibold text-slate-100">
|
|
83
|
-
|
|
84
|
-
{{ session ? (
|
|
109
|
+
{{ t('consensus.titlePrefix') }} ·
|
|
110
|
+
{{ session ? strategyLabel(session.strategy) : '' }}
|
|
85
111
|
<span v-if="block" class="font-normal text-slate-400">— {{ block.title }}</span>
|
|
86
112
|
</h2>
|
|
87
113
|
<p v-if="session" class="text-xs text-slate-500">
|
|
88
|
-
{{ session.agentKind }} ·
|
|
114
|
+
{{ session.agentKind }} ·
|
|
115
|
+
{{
|
|
116
|
+
t(
|
|
117
|
+
'consensus.participantCount',
|
|
118
|
+
{ count: session.participants.length },
|
|
119
|
+
session.participants.length,
|
|
120
|
+
)
|
|
121
|
+
}}
|
|
89
122
|
</p>
|
|
90
123
|
</div>
|
|
91
124
|
<span
|
|
92
125
|
v-if="session"
|
|
93
126
|
class="rounded-full px-2.5 py-1 text-xs font-medium"
|
|
94
|
-
:class="
|
|
127
|
+
:class="STATUS_CLASS[session.status] ?? 'bg-slate-700 text-slate-300'"
|
|
95
128
|
>
|
|
96
|
-
{{
|
|
129
|
+
{{ statusLabel(session.status) }}
|
|
97
130
|
</span>
|
|
98
131
|
<button
|
|
99
132
|
class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
|
|
@@ -105,10 +138,10 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
105
138
|
|
|
106
139
|
<div class="flex-1 overflow-y-auto px-6 py-5">
|
|
107
140
|
<div v-if="loading && !session" class="py-16 text-center text-sm text-slate-500">
|
|
108
|
-
|
|
141
|
+
{{ t('consensus.loading') }}
|
|
109
142
|
</div>
|
|
110
143
|
<div v-else-if="!session" class="py-16 text-center text-sm text-slate-500">
|
|
111
|
-
|
|
144
|
+
{{ t('consensus.empty') }}
|
|
112
145
|
</div>
|
|
113
146
|
<template v-else>
|
|
114
147
|
<!-- failure -->
|
|
@@ -116,19 +149,19 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
116
149
|
v-if="session.status === 'failed'"
|
|
117
150
|
class="mb-5 rounded-lg border border-rose-800/60 bg-rose-950/40 px-4 py-3 text-sm text-rose-200"
|
|
118
151
|
>
|
|
119
|
-
|
|
152
|
+
{{ t('consensus.failed', { error: session.error ?? t('consensus.unknownError') }) }}
|
|
120
153
|
</div>
|
|
121
154
|
|
|
122
155
|
<!-- synthesized result -->
|
|
123
156
|
<section v-if="session.synthesis" class="mb-6">
|
|
124
157
|
<div class="mb-2 flex items-center gap-2">
|
|
125
158
|
<h3 class="text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
126
|
-
|
|
159
|
+
{{ t('consensus.synthesizedResult') }}
|
|
127
160
|
</h3>
|
|
128
161
|
<span
|
|
129
162
|
v-if="session.confidence != null"
|
|
130
163
|
class="rounded bg-emerald-500/15 px-1.5 py-0.5 text-xs text-emerald-300"
|
|
131
|
-
>confidence {
|
|
164
|
+
>{{ t('consensus.confidence', { pct: pct(session.confidence) }) }}</span
|
|
132
165
|
>
|
|
133
166
|
</div>
|
|
134
167
|
<pre
|
|
@@ -150,7 +183,7 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
150
183
|
<!-- participants -->
|
|
151
184
|
<section class="mb-6">
|
|
152
185
|
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
153
|
-
|
|
186
|
+
{{ t('consensus.panel') }}
|
|
154
187
|
</h3>
|
|
155
188
|
<div class="flex flex-wrap gap-2">
|
|
156
189
|
<div
|
|
@@ -158,9 +191,9 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
158
191
|
:key="p.id"
|
|
159
192
|
class="rounded-lg border border-slate-800 bg-slate-950/40 px-3 py-1.5 text-xs"
|
|
160
193
|
>
|
|
161
|
-
<span class="font-medium text-slate-200"
|
|
162
|
-
|
|
163
|
-
>
|
|
194
|
+
<span class="font-medium text-slate-200">{{
|
|
195
|
+
t('consensus.expert', { letter: String.fromCharCode(65 + i) })
|
|
196
|
+
}}</span>
|
|
164
197
|
<span class="text-slate-400"> · {{ p.role }}</span>
|
|
165
198
|
<span v-if="p.modelId" class="ml-1 text-slate-500">({{ p.modelId }})</span>
|
|
166
199
|
</div>
|
|
@@ -170,8 +203,8 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
170
203
|
<!-- rounds -->
|
|
171
204
|
<section v-for="round in session.rounds" :key="round.index" class="mb-5">
|
|
172
205
|
<h3 class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
|
|
173
|
-
|
|
174
|
-
{{
|
|
206
|
+
{{ t('consensus.round.heading', { n: round.index + 1 }) }} ·
|
|
207
|
+
{{ roundLabel(round.kind) }}
|
|
175
208
|
</h3>
|
|
176
209
|
<div class="space-y-3">
|
|
177
210
|
<div
|
|
@@ -187,7 +220,12 @@ function topScore(c: ConsensusContribution): { label: string; value: number } |
|
|
|
187
220
|
<span
|
|
188
221
|
v-if="topScore(c)"
|
|
189
222
|
class="ml-auto rounded bg-slate-800 px-1.5 py-0.5 text-xs text-slate-300"
|
|
190
|
-
>
|
|
223
|
+
>{{
|
|
224
|
+
t('consensus.topScore', {
|
|
225
|
+
label: topScore(c)!.label,
|
|
226
|
+
pct: pct(topScore(c)!.value),
|
|
227
|
+
})
|
|
228
|
+
}}</span
|
|
191
229
|
>
|
|
192
230
|
</div>
|
|
193
231
|
<pre class="whitespace-pre-wrap text-sm text-slate-300">{{ c.text }}</pre>
|