@cat-factory/app 0.47.9 → 0.47.11
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 +96 -59
- package/app/components/clarity/ClarityReviewWindow.vue +84 -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/github/AddServiceFromRepoModal.vue +76 -23
- package/app/components/humanTest/HumanTestWindow.vue +70 -35
- package/app/components/requirements/RequirementsReviewWindow.vue +130 -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/i18n/locales/en.json +587 -3
- package/i18n/locales/es.json +551 -3
- package/i18n/locales/fr.json +551 -3
- package/i18n/locales/pl.json +551 -3
- package/i18n/locales/uk.json +551 -3
- package/package.json +1 -1
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
const board = useBoardStore()
|
|
23
23
|
const clarity = useClarityStore()
|
|
24
24
|
const toast = useToast()
|
|
25
|
+
const { t } = useI18n()
|
|
25
26
|
|
|
26
27
|
// Draft replies, keyed by item id, so editing one item doesn't disturb others.
|
|
27
28
|
const drafts = ref<Record<string, string>>({})
|
|
@@ -112,6 +113,27 @@ const STATUS_COLOR = {
|
|
|
112
113
|
recommend_requested: 'primary',
|
|
113
114
|
} as const satisfies Record<ReviewItemStatus, string>
|
|
114
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
|
+
|
|
115
137
|
function notifyError(title: string, e: unknown) {
|
|
116
138
|
toast.add({
|
|
117
139
|
title,
|
|
@@ -129,7 +151,7 @@ async function submitReply(item: ClarityReviewItem) {
|
|
|
129
151
|
await clarity.reply(review.value, item.id, text)
|
|
130
152
|
drafts.value = { ...drafts.value, [item.id]: '' }
|
|
131
153
|
} catch (e) {
|
|
132
|
-
notifyError('
|
|
154
|
+
notifyError(t('clarity.error.saveAnswer'), e)
|
|
133
155
|
}
|
|
134
156
|
}
|
|
135
157
|
|
|
@@ -138,7 +160,7 @@ async function setStatus(item: ClarityReviewItem, itemStatus: ClarityItemStatus)
|
|
|
138
160
|
try {
|
|
139
161
|
await clarity.setItemStatus(review.value, item.id, itemStatus)
|
|
140
162
|
} catch (e) {
|
|
141
|
-
notifyError('
|
|
163
|
+
notifyError(t('clarity.error.updateFinding'), e)
|
|
142
164
|
}
|
|
143
165
|
}
|
|
144
166
|
|
|
@@ -147,7 +169,7 @@ async function incorporate(feedback?: string) {
|
|
|
147
169
|
try {
|
|
148
170
|
await clarity.incorporate(review.value, feedback)
|
|
149
171
|
} catch (e) {
|
|
150
|
-
notifyError('
|
|
172
|
+
notifyError(t('clarity.error.incorporate'), e)
|
|
151
173
|
return
|
|
152
174
|
}
|
|
153
175
|
redoComment.value = ''
|
|
@@ -155,8 +177,8 @@ async function incorporate(feedback?: string) {
|
|
|
155
177
|
// The fold + re-review now run in the durable driver. Hand the user back to the board;
|
|
156
178
|
// a notification calls them back only if the re-review needs more input.
|
|
157
179
|
toast.add({
|
|
158
|
-
title: '
|
|
159
|
-
description:
|
|
180
|
+
title: t('clarity.toast.clarifyingTitle'),
|
|
181
|
+
description: t('clarity.toast.clarifyingDescription'),
|
|
160
182
|
icon: 'i-lucide-wand-sparkles',
|
|
161
183
|
})
|
|
162
184
|
close()
|
|
@@ -166,17 +188,18 @@ async function reReview() {
|
|
|
166
188
|
if (!blockId.value) return
|
|
167
189
|
try {
|
|
168
190
|
const updated = await clarity.reReview(blockId.value)
|
|
191
|
+
const newFindings = clarity.openCount(updated)
|
|
169
192
|
toast.add({
|
|
170
193
|
title:
|
|
171
194
|
updated.status === 'incorporated'
|
|
172
|
-
? '
|
|
195
|
+
? t('clarity.toast.reReviewSatisfied')
|
|
173
196
|
: updated.status === 'exceeded'
|
|
174
|
-
? '
|
|
175
|
-
:
|
|
197
|
+
? t('clarity.toast.reReviewExceeded')
|
|
198
|
+
: t('clarity.toast.reReviewNewFindings', { count: newFindings }, newFindings),
|
|
176
199
|
icon: 'i-lucide-sparkles',
|
|
177
200
|
})
|
|
178
201
|
} catch (e) {
|
|
179
|
-
notifyError('
|
|
202
|
+
notifyError(t('clarity.error.reReview'), e)
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
|
|
@@ -185,9 +208,9 @@ async function proceed() {
|
|
|
185
208
|
acting.value = true
|
|
186
209
|
try {
|
|
187
210
|
await clarity.proceed(blockId.value)
|
|
188
|
-
toast.add({ title: '
|
|
211
|
+
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
189
212
|
} catch (e) {
|
|
190
|
-
notifyError('
|
|
213
|
+
notifyError(t('clarity.error.proceed'), e)
|
|
191
214
|
} finally {
|
|
192
215
|
acting.value = false
|
|
193
216
|
}
|
|
@@ -199,15 +222,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
199
222
|
try {
|
|
200
223
|
await clarity.resolveExceeded(blockId.value, choice)
|
|
201
224
|
if (choice === 'stop-reset') {
|
|
202
|
-
toast.add({ title: '
|
|
225
|
+
toast.add({ title: t('clarity.toast.taskReset'), icon: 'i-lucide-undo' })
|
|
203
226
|
close()
|
|
204
227
|
} else if (choice === 'proceed') {
|
|
205
|
-
toast.add({ title: '
|
|
228
|
+
toast.add({ title: t('clarity.toast.proceeding'), icon: 'i-lucide-arrow-right' })
|
|
206
229
|
} else {
|
|
207
|
-
toast.add({ title: '
|
|
230
|
+
toast.add({ title: t('clarity.toast.extraRoundGranted'), icon: 'i-lucide-rotate-cw' })
|
|
208
231
|
}
|
|
209
232
|
} catch (e) {
|
|
210
|
-
notifyError('
|
|
233
|
+
notifyError(t('clarity.error.resolve'), e)
|
|
211
234
|
} finally {
|
|
212
235
|
acting.value = false
|
|
213
236
|
}
|
|
@@ -232,12 +255,12 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
232
255
|
<UIcon name="i-lucide-bug" class="h-5 w-5 text-indigo-300" />
|
|
233
256
|
</div>
|
|
234
257
|
<div class="min-w-0">
|
|
235
|
-
<h1 class="truncate text-base font-semibold text-white">
|
|
258
|
+
<h1 class="truncate text-base font-semibold text-white">{{ t('clarity.title') }}</h1>
|
|
236
259
|
<p v-if="block" class="truncate text-xs text-slate-500">{{ block.title }}</p>
|
|
237
260
|
</div>
|
|
238
261
|
<div class="ml-auto flex items-center gap-1.5">
|
|
239
262
|
<UBadge v-if="review" color="neutral" variant="subtle" size="sm">
|
|
240
|
-
|
|
263
|
+
{{ t('clarity.iteration', { current: iteration, max: maxIterations }) }}
|
|
241
264
|
</UBadge>
|
|
242
265
|
<UButton icon="i-lucide-x" color="neutral" variant="ghost" size="sm" @click="close" />
|
|
243
266
|
</div>
|
|
@@ -247,11 +270,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
247
270
|
<!-- main column -->
|
|
248
271
|
<div class="min-w-0 flex-1 overflow-y-auto px-6 py-5">
|
|
249
272
|
<p class="mb-4 text-sm text-slate-400">
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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>
|
|
255
282
|
</p>
|
|
256
283
|
|
|
257
284
|
<!-- empty state — the reviewer runs automatically as the first pipeline
|
|
@@ -260,8 +287,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
260
287
|
v-if="!review && !busy && !loading"
|
|
261
288
|
class="rounded-lg border border-dashed border-slate-700 p-8 text-center text-sm text-slate-500"
|
|
262
289
|
>
|
|
263
|
-
|
|
264
|
-
pipeline starts.
|
|
290
|
+
{{ t('clarity.empty') }}
|
|
265
291
|
</div>
|
|
266
292
|
|
|
267
293
|
<!-- working state (initial fetch on open, or a reviewer pass running) -->
|
|
@@ -270,7 +296,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
270
296
|
class="flex items-center justify-center gap-2 p-8 text-sm text-slate-400"
|
|
271
297
|
>
|
|
272
298
|
<UIcon name="i-lucide-loader-circle" class="h-4 w-4 animate-spin" />
|
|
273
|
-
{{ loading && !busy ? '
|
|
299
|
+
{{ loading && !busy ? t('clarity.loadingReview') : t('clarity.triaging') }}
|
|
274
300
|
</div>
|
|
275
301
|
|
|
276
302
|
<template v-else-if="review">
|
|
@@ -280,16 +306,15 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
280
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"
|
|
281
307
|
>
|
|
282
308
|
<UIcon name="i-lucide-circle-check" class="h-5 w-5 shrink-0" />
|
|
283
|
-
|
|
284
|
-
uses.
|
|
309
|
+
{{ t('clarity.converged') }}
|
|
285
310
|
</div>
|
|
286
311
|
|
|
287
312
|
<!-- iteration cap hit -->
|
|
288
313
|
<IterationCapPrompt
|
|
289
314
|
v-else-if="exceeded"
|
|
290
315
|
class="mb-4"
|
|
291
|
-
:heading="
|
|
292
|
-
detail="
|
|
316
|
+
:heading="t('clarity.capHeading', { max: maxIterations })"
|
|
317
|
+
:detail="t('clarity.capDetail')"
|
|
293
318
|
:loading="acting"
|
|
294
319
|
@resolve="resolveExceeded"
|
|
295
320
|
/>
|
|
@@ -302,12 +327,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
302
327
|
>
|
|
303
328
|
<UIcon name="i-lucide-loader-circle" class="h-5 w-5 shrink-0 animate-spin" />
|
|
304
329
|
<span v-if="incorporating">
|
|
305
|
-
|
|
306
|
-
notify you only if more input is needed.
|
|
330
|
+
{{ t('clarity.incorporatingStage') }}
|
|
307
331
|
</span>
|
|
308
332
|
<span v-else>
|
|
309
|
-
|
|
310
|
-
more input is needed.
|
|
333
|
+
{{ t('clarity.reReviewingStage') }}
|
|
311
334
|
</span>
|
|
312
335
|
</div>
|
|
313
336
|
|
|
@@ -328,10 +351,10 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
328
351
|
<div class="flex flex-wrap items-center gap-1.5">
|
|
329
352
|
<span class="text-sm font-medium text-white">{{ item.title }}</span>
|
|
330
353
|
<UBadge size="xs" variant="subtle" :color="SEVERITY_COLOR[item.severity]">
|
|
331
|
-
{{ item.severity }}
|
|
354
|
+
{{ t(SEVERITY_LABELS[item.severity]) }}
|
|
332
355
|
</UBadge>
|
|
333
356
|
<UBadge size="xs" variant="outline" color="neutral">
|
|
334
|
-
{{ item.category }}
|
|
357
|
+
{{ t(CATEGORY_LABELS[item.category]) }}
|
|
335
358
|
</UBadge>
|
|
336
359
|
<UBadge
|
|
337
360
|
size="xs"
|
|
@@ -339,7 +362,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
339
362
|
:color="STATUS_COLOR[item.status]"
|
|
340
363
|
class="ml-auto"
|
|
341
364
|
>
|
|
342
|
-
{{ item.status }}
|
|
365
|
+
{{ t(STATUS_LABELS[item.status]) }}
|
|
343
366
|
</UBadge>
|
|
344
367
|
</div>
|
|
345
368
|
<p class="mt-1 whitespace-pre-line text-sm text-slate-400">
|
|
@@ -352,7 +375,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
352
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"
|
|
353
376
|
>
|
|
354
377
|
<span class="text-[10px] uppercase tracking-wide text-slate-500">
|
|
355
|
-
|
|
378
|
+
{{ t('clarity.answerLabel') }}
|
|
356
379
|
</span>
|
|
357
380
|
<p class="whitespace-pre-line">{{ item.reply }}</p>
|
|
358
381
|
</div>
|
|
@@ -366,7 +389,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
366
389
|
autoresize
|
|
367
390
|
size="sm"
|
|
368
391
|
class="mt-2 w-full"
|
|
369
|
-
:placeholder="
|
|
392
|
+
:placeholder="
|
|
393
|
+
item.reply
|
|
394
|
+
? t('clarity.refineAnswerPlaceholder')
|
|
395
|
+
: t('clarity.answerPlaceholder')
|
|
396
|
+
"
|
|
370
397
|
:disabled="frozen"
|
|
371
398
|
/>
|
|
372
399
|
<div class="mt-2 flex flex-wrap items-center gap-2">
|
|
@@ -378,7 +405,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
378
405
|
:disabled="!(drafts[item.id] ?? '').trim() || frozen"
|
|
379
406
|
@click="submitReply(item)"
|
|
380
407
|
>
|
|
381
|
-
|
|
408
|
+
{{ t('clarity.saveAnswer') }}
|
|
382
409
|
</UButton>
|
|
383
410
|
<UButton
|
|
384
411
|
color="neutral"
|
|
@@ -388,7 +415,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
388
415
|
:disabled="frozen"
|
|
389
416
|
@click="setStatus(item, 'dismissed')"
|
|
390
417
|
>
|
|
391
|
-
|
|
418
|
+
{{ t('clarity.dismissIrrelevant') }}
|
|
392
419
|
</UButton>
|
|
393
420
|
</div>
|
|
394
421
|
</template>
|
|
@@ -403,7 +430,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
403
430
|
:disabled="frozen"
|
|
404
431
|
@click="setStatus(item, 'open')"
|
|
405
432
|
>
|
|
406
|
-
|
|
433
|
+
{{ t('clarity.reopen') }}
|
|
407
434
|
</UButton>
|
|
408
435
|
</div>
|
|
409
436
|
</div>
|
|
@@ -416,7 +443,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
416
443
|
<div class="mb-3 flex items-center gap-1.5 text-[11px] text-emerald-400">
|
|
417
444
|
<UIcon name="i-lucide-file-check-2" class="h-3.5 w-3.5" />
|
|
418
445
|
<span class="font-semibold uppercase tracking-wide">
|
|
419
|
-
{{ incorporated ? '
|
|
446
|
+
{{ incorporated ? t('clarity.docHeading') : t('clarity.docHeadingDraft') }}
|
|
420
447
|
</span>
|
|
421
448
|
</div>
|
|
422
449
|
<div v-for="s in outline.sections" :key="s.id" class="mb-2">
|
|
@@ -451,19 +478,19 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
451
478
|
<div class="flex flex-col gap-4 px-4 py-5">
|
|
452
479
|
<div v-if="review" class="space-y-2 text-xs text-slate-400">
|
|
453
480
|
<div class="flex items-center justify-between">
|
|
454
|
-
<span>
|
|
481
|
+
<span>{{ t('clarity.rail.findings') }}</span>
|
|
455
482
|
<span class="text-slate-300">{{ review.items.length }}</span>
|
|
456
483
|
</div>
|
|
457
484
|
<div class="flex items-center justify-between">
|
|
458
|
-
<span>
|
|
485
|
+
<span>{{ t('clarity.rail.open') }}</span>
|
|
459
486
|
<span class="text-slate-300">{{ openCount }}</span>
|
|
460
487
|
</div>
|
|
461
488
|
<div class="flex items-center justify-between">
|
|
462
|
-
<span>
|
|
489
|
+
<span>{{ t('clarity.rail.answered') }}</span>
|
|
463
490
|
<span class="text-slate-300">{{ answeredCount }}</span>
|
|
464
491
|
</div>
|
|
465
492
|
<div v-if="review.model" class="flex items-center justify-between">
|
|
466
|
-
<span>
|
|
493
|
+
<span>{{ t('clarity.rail.model') }}</span>
|
|
467
494
|
<span class="truncate pl-2 text-slate-500">{{ review.model }}</span>
|
|
468
495
|
</div>
|
|
469
496
|
</div>
|
|
@@ -482,7 +509,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
482
509
|
:loading="acting"
|
|
483
510
|
@click="proceed"
|
|
484
511
|
>
|
|
485
|
-
|
|
512
|
+
{{ t('clarity.proceedNothing') }}
|
|
486
513
|
</UButton>
|
|
487
514
|
<UButton
|
|
488
515
|
v-else
|
|
@@ -494,17 +521,16 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
494
521
|
:disabled="!canIncorporate"
|
|
495
522
|
@click="incorporate()"
|
|
496
523
|
>
|
|
497
|
-
|
|
524
|
+
{{ t('clarity.incorporateAnswers') }}
|
|
498
525
|
</UButton>
|
|
499
526
|
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
500
527
|
<template v-if="canProceed">
|
|
501
|
-
|
|
528
|
+
{{ t('clarity.hint.proceed') }}
|
|
502
529
|
</template>
|
|
503
530
|
<template v-else-if="canIncorporate">
|
|
504
|
-
|
|
505
|
-
automatically.
|
|
531
|
+
{{ t('clarity.hint.incorporate') }}
|
|
506
532
|
</template>
|
|
507
|
-
<template v-else>
|
|
533
|
+
<template v-else> {{ t('clarity.hint.answerAll') }} </template>
|
|
508
534
|
</p>
|
|
509
535
|
</div>
|
|
510
536
|
|
|
@@ -518,7 +544,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
518
544
|
:loading="busy"
|
|
519
545
|
@click="reReview"
|
|
520
546
|
>
|
|
521
|
-
{{ busy ? '
|
|
547
|
+
{{ busy ? t('clarity.reReviewing') : t('clarity.looksGoodReReview') }}
|
|
522
548
|
</UButton>
|
|
523
549
|
<UButton
|
|
524
550
|
color="neutral"
|
|
@@ -528,7 +554,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
528
554
|
icon="i-lucide-pencil"
|
|
529
555
|
@click="showRedo = !showRedo"
|
|
530
556
|
>
|
|
531
|
-
|
|
557
|
+
{{ t('clarity.redoIncorporation') }}
|
|
532
558
|
</UButton>
|
|
533
559
|
<div v-if="showRedo" class="space-y-2">
|
|
534
560
|
<UTextarea
|
|
@@ -537,7 +563,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
537
563
|
autoresize
|
|
538
564
|
size="sm"
|
|
539
565
|
class="w-full"
|
|
540
|
-
placeholder="
|
|
566
|
+
:placeholder="t('clarity.redoPlaceholder')"
|
|
541
567
|
/>
|
|
542
568
|
<UButton
|
|
543
569
|
color="primary"
|
|
@@ -549,12 +575,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
549
575
|
:disabled="!redoComment.trim()"
|
|
550
576
|
@click="incorporate(redoComment.trim())"
|
|
551
577
|
>
|
|
552
|
-
|
|
578
|
+
{{ t('clarity.redoWithDirection') }}
|
|
553
579
|
</UButton>
|
|
554
580
|
</div>
|
|
555
581
|
<p class="text-[11px] leading-relaxed text-slate-500">
|
|
556
|
-
|
|
557
|
-
merged, redo it with a comment instead.
|
|
582
|
+
{{ t('clarity.redoHint') }}
|
|
558
583
|
</p>
|
|
559
584
|
</div>
|
|
560
585
|
|
|
@@ -562,7 +587,7 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
562
587
|
v-if="review && incorporated"
|
|
563
588
|
class="border-t border-slate-800 pt-4 text-[11px] leading-relaxed text-slate-500"
|
|
564
589
|
>
|
|
565
|
-
|
|
590
|
+
{{ t('clarity.incorporatedFooter') }}
|
|
566
591
|
</div>
|
|
567
592
|
</div>
|
|
568
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>
|