@cat-factory/app 0.92.2 → 0.93.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/app/components/auth/LoginScreen.vue +5 -1
- package/app/components/board/AddTaskModal.vue +25 -5
- package/app/components/board/CreateInitiativeModal.vue +9 -1
- package/app/components/board/RecurringPipelineModal.vue +20 -4
- package/app/components/bootstrap/BootstrapModal.vue +9 -1
- package/app/components/brainstorm/BrainstormWindow.vue +5 -1
- package/app/components/clarity/ClarityReviewWindow.vue +5 -1
- package/app/components/docs/DocInterviewWindow.vue +209 -0
- package/app/components/documents/DocumentTemplatesModal.vue +5 -1
- package/app/components/fragments/FragmentLibraryManager.vue +10 -2
- package/app/components/github/GitHubPanel.vue +25 -4
- package/app/components/layout/BoardSwitcher.vue +10 -1
- package/app/components/layout/BoardToolbar.vue +15 -3
- package/app/components/layout/GitHubPatBanner.vue +5 -1
- package/app/components/layout/ProviderConfigBanner.vue +5 -1
- package/app/components/panels/AgentStepDetail.vue +5 -1
- package/app/components/panels/InspectorPanel.vue +5 -1
- package/app/components/panels/StepRestartControl.vue +10 -2
- package/app/components/panels/StepResultViewHost.vue +4 -0
- package/app/components/panels/inspector/FrontendConfig.vue +10 -1
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +11 -3
- package/app/components/pipeline/PipelineBuilder.vue +15 -2
- package/app/components/pipeline/PipelineProgress.vue +10 -2
- package/app/components/providers/AiPresetMismatchDialog.vue +10 -1
- package/app/components/providers/PersonalCredentialModal.vue +18 -2
- package/app/components/requirements/RequirementsReviewWindow.vue +5 -1
- package/app/components/sandbox/SandboxPanel.vue +10 -1
- package/app/components/settings/InfraHandlersConfigurator.vue +5 -1
- package/app/components/settings/IssueTrackerPanel.vue +20 -4
- package/app/components/settings/LocalModelEndpointsPanel.vue +5 -1
- package/app/components/settings/ModelConfigurationPanel.vue +20 -3
- package/app/components/settings/ProviderConnectionTab.vue +5 -1
- package/app/components/spec/ServiceSpecWindow.vue +15 -3
- package/app/components/testing/TestReportWindow.vue +10 -2
- package/app/composables/api/docInterview.ts +36 -0
- package/app/composables/useApi.ts +2 -0
- package/app/composables/useWorkspaceStream.ts +5 -0
- package/app/stores/docInterview.ts +89 -0
- package/app/stores/workspace.ts +1 -0
- package/app/types/doc-interview.ts +9 -0
- package/app/types/domain.ts +1 -0
- package/i18n/locales/en.json +16 -0
- package/i18n/locales/es.json +16 -0
- package/i18n/locales/fr.json +16 -0
- package/i18n/locales/he.json +16 -0
- package/i18n/locales/ja.json +16 -0
- package/i18n/locales/pl.json +16 -0
- package/i18n/locales/tr.json +16 -0
- package/i18n/locales/uk.json +16 -0
- package/package.json +5 -5
|
@@ -279,7 +279,16 @@ onUnmounted(() => preview.stopPolling(props.block.id))
|
|
|
279
279
|
>
|
|
280
280
|
{{ t('inspector.frontendConfig.detect.apply') }}
|
|
281
281
|
</UButton>
|
|
282
|
-
<UButton
|
|
282
|
+
<UButton
|
|
283
|
+
size="xs"
|
|
284
|
+
variant="ghost"
|
|
285
|
+
color="neutral"
|
|
286
|
+
@click="
|
|
287
|
+
() => {
|
|
288
|
+
detectResult = null
|
|
289
|
+
}
|
|
290
|
+
"
|
|
291
|
+
>
|
|
283
292
|
{{ t('inspector.frontendConfig.detect.dismiss') }}
|
|
284
293
|
</UButton>
|
|
285
294
|
</div>
|
|
@@ -213,9 +213,17 @@ function fmtTime(ms: number) {
|
|
|
213
213
|
<template v-else-if="draft">
|
|
214
214
|
<RecurringRecurrenceEditor v-model="draft" />
|
|
215
215
|
<div class="flex justify-end gap-1.5 pt-1">
|
|
216
|
-
<UButton
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
<UButton
|
|
217
|
+
size="xs"
|
|
218
|
+
variant="ghost"
|
|
219
|
+
color="neutral"
|
|
220
|
+
@click="
|
|
221
|
+
() => {
|
|
222
|
+
editing = false
|
|
223
|
+
}
|
|
224
|
+
"
|
|
225
|
+
>{{ t('common.cancel') }}</UButton
|
|
226
|
+
>
|
|
219
227
|
<UButton size="xs" color="primary" :loading="busy" @click="saveEdit">{{
|
|
220
228
|
t('common.save')
|
|
221
229
|
}}</UButton>
|
|
@@ -762,7 +762,11 @@ async function clone(p: Pipeline) {
|
|
|
762
762
|
:color="showArchived ? 'primary' : 'neutral'"
|
|
763
763
|
variant="ghost"
|
|
764
764
|
size="xs"
|
|
765
|
-
@click="
|
|
765
|
+
@click="
|
|
766
|
+
() => {
|
|
767
|
+
showArchived = !showArchived
|
|
768
|
+
}
|
|
769
|
+
"
|
|
766
770
|
>
|
|
767
771
|
{{
|
|
768
772
|
showArchived
|
|
@@ -987,7 +991,16 @@ async function clone(p: Pipeline) {
|
|
|
987
991
|
|
|
988
992
|
<template #footer>
|
|
989
993
|
<div class="flex w-full items-center justify-end gap-2">
|
|
990
|
-
<UButton
|
|
994
|
+
<UButton
|
|
995
|
+
color="neutral"
|
|
996
|
+
variant="ghost"
|
|
997
|
+
size="sm"
|
|
998
|
+
@click="
|
|
999
|
+
() => {
|
|
1000
|
+
addAgentOpen = false
|
|
1001
|
+
}
|
|
1002
|
+
"
|
|
1003
|
+
>
|
|
991
1004
|
{{ t('common.cancel') }}
|
|
992
1005
|
</UButton>
|
|
993
1006
|
<UButton
|
|
@@ -324,7 +324,11 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
324
324
|
size="xs"
|
|
325
325
|
class="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
|
|
326
326
|
:title="t('pipeline.progress.restartTooltip')"
|
|
327
|
-
@click.stop="
|
|
327
|
+
@click.stop="
|
|
328
|
+
() => {
|
|
329
|
+
restartArmed = i
|
|
330
|
+
}
|
|
331
|
+
"
|
|
328
332
|
/>
|
|
329
333
|
<template v-else>
|
|
330
334
|
<UButton
|
|
@@ -344,7 +348,11 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
344
348
|
size="xs"
|
|
345
349
|
class="shrink-0"
|
|
346
350
|
:disabled="restarting === i"
|
|
347
|
-
@click.stop="
|
|
351
|
+
@click.stop="
|
|
352
|
+
() => {
|
|
353
|
+
restartArmed = null
|
|
354
|
+
}
|
|
355
|
+
"
|
|
348
356
|
>
|
|
349
357
|
{{ t('common.cancel') }}
|
|
350
358
|
</UButton>
|
|
@@ -75,7 +75,16 @@ function go(action: () => void) {
|
|
|
75
75
|
</p>
|
|
76
76
|
|
|
77
77
|
<div class="flex flex-wrap justify-end gap-2">
|
|
78
|
-
<UButton
|
|
78
|
+
<UButton
|
|
79
|
+
color="neutral"
|
|
80
|
+
variant="ghost"
|
|
81
|
+
size="sm"
|
|
82
|
+
@click="
|
|
83
|
+
() => {
|
|
84
|
+
open = false
|
|
85
|
+
}
|
|
86
|
+
"
|
|
87
|
+
>
|
|
79
88
|
{{ t('providers.presetMismatch.later') }}
|
|
80
89
|
</UButton>
|
|
81
90
|
<UButton
|
|
@@ -97,7 +97,15 @@ function goConnect() {
|
|
|
97
97
|
{{ t('providers.personalCredential.connectBody', { vendor: vendorLabel }) }}
|
|
98
98
|
</p>
|
|
99
99
|
<div class="flex justify-end gap-2">
|
|
100
|
-
<UButton
|
|
100
|
+
<UButton
|
|
101
|
+
color="neutral"
|
|
102
|
+
variant="ghost"
|
|
103
|
+
@click="
|
|
104
|
+
() => {
|
|
105
|
+
open = false
|
|
106
|
+
}
|
|
107
|
+
"
|
|
108
|
+
>
|
|
101
109
|
{{ t('providers.personalCredential.cancel') }}
|
|
102
110
|
</UButton>
|
|
103
111
|
<UButton icon="i-lucide-shield-check" @click="goConnect()">
|
|
@@ -120,7 +128,15 @@ function goConnect() {
|
|
|
120
128
|
/>
|
|
121
129
|
</UFormField>
|
|
122
130
|
<div class="flex justify-end gap-2">
|
|
123
|
-
<UButton
|
|
131
|
+
<UButton
|
|
132
|
+
color="neutral"
|
|
133
|
+
variant="ghost"
|
|
134
|
+
@click="
|
|
135
|
+
() => {
|
|
136
|
+
open = false
|
|
137
|
+
}
|
|
138
|
+
"
|
|
139
|
+
>
|
|
124
140
|
{{ t('providers.personalCredential.cancel') }}
|
|
125
141
|
</UButton>
|
|
126
142
|
<UButton :loading="busy" :disabled="password.length < 6" @click="submit()">
|
|
@@ -979,7 +979,11 @@ async function resolveExceeded(choice: 'extra-round' | 'proceed' | 'stop-reset')
|
|
|
979
979
|
size="sm"
|
|
980
980
|
block
|
|
981
981
|
icon="i-lucide-pencil"
|
|
982
|
-
@click="
|
|
982
|
+
@click="
|
|
983
|
+
() => {
|
|
984
|
+
showRedo = !showRedo
|
|
985
|
+
}
|
|
986
|
+
"
|
|
983
987
|
>
|
|
984
988
|
{{ t('requirements.actions.redoIncorporation') }}
|
|
985
989
|
</UButton>
|
|
@@ -561,7 +561,16 @@ async function archive(prompt: SandboxPromptVersion) {
|
|
|
561
561
|
</p>
|
|
562
562
|
<UTextarea v-model="editText" :rows="16" class="w-full font-mono text-xs" autoresize />
|
|
563
563
|
<div class="flex justify-end gap-2">
|
|
564
|
-
<UButton
|
|
564
|
+
<UButton
|
|
565
|
+
color="neutral"
|
|
566
|
+
variant="ghost"
|
|
567
|
+
size="sm"
|
|
568
|
+
@click="
|
|
569
|
+
() => {
|
|
570
|
+
editing = null
|
|
571
|
+
}
|
|
572
|
+
"
|
|
573
|
+
>
|
|
565
574
|
{{ t('common.cancel') }}
|
|
566
575
|
</UButton>
|
|
567
576
|
<UButton
|
|
@@ -443,7 +443,11 @@ function notifyError(e: unknown) {
|
|
|
443
443
|
:color="selectedKubeEngine === e ? 'primary' : 'neutral'"
|
|
444
444
|
:variant="selectedKubeEngine === e ? 'soft' : 'ghost'"
|
|
445
445
|
size="xs"
|
|
446
|
-
@click="
|
|
446
|
+
@click="
|
|
447
|
+
() => {
|
|
448
|
+
selectedKubeEngine = e
|
|
449
|
+
}
|
|
450
|
+
"
|
|
447
451
|
>
|
|
448
452
|
{{ t(KUBE_ENGINE_KEYS[e]) }}
|
|
449
453
|
</UButton>
|
|
@@ -234,7 +234,11 @@ const STATUS_UI: Record<
|
|
|
234
234
|
size="sm"
|
|
235
235
|
:color="trackerKind === null ? 'primary' : 'neutral'"
|
|
236
236
|
:variant="trackerKind === null ? 'solid' : 'subtle'"
|
|
237
|
-
@click="
|
|
237
|
+
@click="
|
|
238
|
+
() => {
|
|
239
|
+
trackerKind = null
|
|
240
|
+
}
|
|
241
|
+
"
|
|
238
242
|
>
|
|
239
243
|
{{ t('settings.issueTracker.filing.none') }}
|
|
240
244
|
</UButton>
|
|
@@ -243,7 +247,11 @@ const STATUS_UI: Record<
|
|
|
243
247
|
size="sm"
|
|
244
248
|
:color="trackerKind === 'github' ? 'primary' : 'neutral'"
|
|
245
249
|
:variant="trackerKind === 'github' ? 'solid' : 'subtle'"
|
|
246
|
-
@click="
|
|
250
|
+
@click="
|
|
251
|
+
() => {
|
|
252
|
+
trackerKind = 'github'
|
|
253
|
+
}
|
|
254
|
+
"
|
|
247
255
|
>
|
|
248
256
|
{{ t('settings.issueTracker.vendor.github') }}
|
|
249
257
|
</UButton>
|
|
@@ -252,7 +260,11 @@ const STATUS_UI: Record<
|
|
|
252
260
|
size="sm"
|
|
253
261
|
:color="trackerKind === 'jira' ? 'primary' : 'neutral'"
|
|
254
262
|
:variant="trackerKind === 'jira' ? 'solid' : 'subtle'"
|
|
255
|
-
@click="
|
|
263
|
+
@click="
|
|
264
|
+
() => {
|
|
265
|
+
trackerKind = 'jira'
|
|
266
|
+
}
|
|
267
|
+
"
|
|
256
268
|
>
|
|
257
269
|
Jira
|
|
258
270
|
</UButton>
|
|
@@ -261,7 +273,11 @@ const STATUS_UI: Record<
|
|
|
261
273
|
size="sm"
|
|
262
274
|
:color="trackerKind === 'linear' ? 'primary' : 'neutral'"
|
|
263
275
|
:variant="trackerKind === 'linear' ? 'solid' : 'subtle'"
|
|
264
|
-
@click="
|
|
276
|
+
@click="
|
|
277
|
+
() => {
|
|
278
|
+
trackerKind = 'linear'
|
|
279
|
+
}
|
|
280
|
+
"
|
|
265
281
|
>
|
|
266
282
|
Linear
|
|
267
283
|
</UButton>
|
|
@@ -225,7 +225,11 @@ async function remove(p: LocalRunner) {
|
|
|
225
225
|
size="xs"
|
|
226
226
|
:disabled="busy"
|
|
227
227
|
:title="t('settings.localModelEndpoints.edit')"
|
|
228
|
-
@click="
|
|
228
|
+
@click="
|
|
229
|
+
() => {
|
|
230
|
+
provider = e.provider
|
|
231
|
+
}
|
|
232
|
+
"
|
|
229
233
|
/>
|
|
230
234
|
<UButton
|
|
231
235
|
icon="i-lucide-trash-2"
|
|
@@ -281,7 +281,11 @@ function fail(title: string, e: unknown) {
|
|
|
281
281
|
variant="ghost"
|
|
282
282
|
size="sm"
|
|
283
283
|
class="ms-auto"
|
|
284
|
-
@click="
|
|
284
|
+
@click="
|
|
285
|
+
() => {
|
|
286
|
+
editor = null
|
|
287
|
+
}
|
|
288
|
+
"
|
|
285
289
|
>
|
|
286
290
|
{{ t('settings.modelConfiguration.back') }}
|
|
287
291
|
</UButton>
|
|
@@ -292,7 +296,11 @@ function fail(title: string, e: unknown) {
|
|
|
292
296
|
size="sm"
|
|
293
297
|
:class="editor ? '' : 'ms-auto'"
|
|
294
298
|
:title="t('settings.modelConfiguration.closeEsc')"
|
|
295
|
-
@click="
|
|
299
|
+
@click="
|
|
300
|
+
() => {
|
|
301
|
+
open = false
|
|
302
|
+
}
|
|
303
|
+
"
|
|
296
304
|
/>
|
|
297
305
|
</header>
|
|
298
306
|
|
|
@@ -499,7 +507,16 @@ function fail(title: string, e: unknown) {
|
|
|
499
507
|
</div>
|
|
500
508
|
|
|
501
509
|
<div class="flex items-center justify-end gap-2">
|
|
502
|
-
<UButton
|
|
510
|
+
<UButton
|
|
511
|
+
color="neutral"
|
|
512
|
+
variant="ghost"
|
|
513
|
+
size="sm"
|
|
514
|
+
@click="
|
|
515
|
+
() => {
|
|
516
|
+
editor = null
|
|
517
|
+
}
|
|
518
|
+
"
|
|
519
|
+
>
|
|
503
520
|
{{ t('common.cancel') }}
|
|
504
521
|
</UButton>
|
|
505
522
|
<UButton color="primary" size="sm" :loading="busy" @click="save">
|
|
@@ -147,7 +147,11 @@ function kindLabel(item: RequirementItem): string {
|
|
|
147
147
|
:variant="mode === 'structured' ? 'soft' : 'ghost'"
|
|
148
148
|
size="xs"
|
|
149
149
|
icon="i-lucide-list-tree"
|
|
150
|
-
@click="
|
|
150
|
+
@click="
|
|
151
|
+
() => {
|
|
152
|
+
mode = 'structured'
|
|
153
|
+
}
|
|
154
|
+
"
|
|
151
155
|
>
|
|
152
156
|
{{ t('spec.mode.structured') }}
|
|
153
157
|
</UButton>
|
|
@@ -158,7 +162,11 @@ function kindLabel(item: RequirementItem): string {
|
|
|
158
162
|
icon="i-lucide-square-check-big"
|
|
159
163
|
:disabled="!hasGherkin"
|
|
160
164
|
:title="hasGherkin ? t('spec.mode.gherkinTooltip') : t('spec.mode.gherkinNone')"
|
|
161
|
-
@click="
|
|
165
|
+
@click="
|
|
166
|
+
() => {
|
|
167
|
+
mode = 'gherkin'
|
|
168
|
+
}
|
|
169
|
+
"
|
|
162
170
|
>
|
|
163
171
|
{{ t('spec.mode.gherkin') }}
|
|
164
172
|
</UButton>
|
|
@@ -210,7 +218,11 @@ function kindLabel(item: RequirementItem): string {
|
|
|
210
218
|
:variant="selected === null ? 'soft' : 'ghost'"
|
|
211
219
|
size="xs"
|
|
212
220
|
icon="i-lucide-info"
|
|
213
|
-
@click="
|
|
221
|
+
@click="
|
|
222
|
+
() => {
|
|
223
|
+
selected = null
|
|
224
|
+
}
|
|
225
|
+
"
|
|
214
226
|
>
|
|
215
227
|
{{ t('spec.overview') }}
|
|
216
228
|
</UButton>
|
|
@@ -444,7 +444,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
444
444
|
size="xs"
|
|
445
445
|
class="mt-1.5"
|
|
446
446
|
data-testid="tester-infra-setup-logs-toggle"
|
|
447
|
-
@click="
|
|
447
|
+
@click="
|
|
448
|
+
() => {
|
|
449
|
+
showInfraSetupLogs = !showInfraSetupLogs
|
|
450
|
+
}
|
|
451
|
+
"
|
|
448
452
|
>
|
|
449
453
|
{{
|
|
450
454
|
showInfraSetupLogs
|
|
@@ -480,7 +484,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
480
484
|
variant="ghost"
|
|
481
485
|
size="xs"
|
|
482
486
|
data-testid="tester-infra-attempts-toggle"
|
|
483
|
-
@click="
|
|
487
|
+
@click="
|
|
488
|
+
() => {
|
|
489
|
+
showProvisioning = !showProvisioning
|
|
490
|
+
}
|
|
491
|
+
"
|
|
484
492
|
>
|
|
485
493
|
{{
|
|
486
494
|
showProvisioning
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
answerDocInterviewContract,
|
|
3
|
+
continueDocInterviewContract,
|
|
4
|
+
getDocInterviewContract,
|
|
5
|
+
proceedDocInterviewContract,
|
|
6
|
+
} from '@cat-factory/contracts'
|
|
7
|
+
import type { ApiContext } from './context'
|
|
8
|
+
|
|
9
|
+
/** Interactive document-interview session (WS5): load + answer / continue / proceed. */
|
|
10
|
+
export function docInterviewApi({ send, ws }: ApiContext) {
|
|
11
|
+
return {
|
|
12
|
+
// The interview window's load path: the session anchored to a board block (or null).
|
|
13
|
+
getDocInterview: (workspaceId: string, blockId: string) =>
|
|
14
|
+
send(getDocInterviewContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
|
|
15
|
+
|
|
16
|
+
// Answer one interview question (no run resume), then continue (interviewer re-runs, may
|
|
17
|
+
// ask more) or proceed (skip remaining, synthesize the brief and advance to the writer).
|
|
18
|
+
answerDocInterview: (
|
|
19
|
+
workspaceId: string,
|
|
20
|
+
blockId: string,
|
|
21
|
+
questionId: string,
|
|
22
|
+
answer: string,
|
|
23
|
+
) =>
|
|
24
|
+
send(answerDocInterviewContract, {
|
|
25
|
+
pathPrefix: ws(workspaceId),
|
|
26
|
+
pathParams: { blockId },
|
|
27
|
+
body: { questionId, answer },
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
continueDocInterview: (workspaceId: string, blockId: string) =>
|
|
31
|
+
send(continueDocInterviewContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
|
|
32
|
+
|
|
33
|
+
proceedDocInterview: (workspaceId: string, blockId: string) =>
|
|
34
|
+
send(proceedDocInterviewContract, { pathPrefix: ws(workspaceId), pathParams: { blockId } }),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -14,6 +14,7 @@ import { humanReviewApi } from './api/humanReview'
|
|
|
14
14
|
import { humanTestApi } from './api/humanTest'
|
|
15
15
|
import { infraHandlersApi } from './api/infraHandlers'
|
|
16
16
|
import { initiativeApi } from './api/initiative'
|
|
17
|
+
import { docInterviewApi } from './api/docInterview'
|
|
17
18
|
import { visualConfirmApi } from './api/visualConfirm'
|
|
18
19
|
import { kaizenApi } from './api/kaizen'
|
|
19
20
|
import { localSettingsApi } from './api/localSettings'
|
|
@@ -114,6 +115,7 @@ export function useApi() {
|
|
|
114
115
|
...providerConnectionsApi(ctx),
|
|
115
116
|
...infraHandlersApi(ctx),
|
|
116
117
|
...initiativeApi(ctx),
|
|
118
|
+
...docInterviewApi(ctx),
|
|
117
119
|
...provisioningLogsApi(ctx),
|
|
118
120
|
...releaseHealthApi(ctx),
|
|
119
121
|
...packageRegistriesApi(ctx),
|
|
@@ -26,6 +26,7 @@ export function useWorkspaceStream() {
|
|
|
26
26
|
const brainstorm = useBrainstormStore()
|
|
27
27
|
const kaizen = useKaizenStore()
|
|
28
28
|
const initiatives = useInitiativesStore()
|
|
29
|
+
const docInterview = useDocInterviewStore()
|
|
29
30
|
const api = useApi()
|
|
30
31
|
const apiBase = useRuntimeConfig().public.apiBase
|
|
31
32
|
|
|
@@ -108,6 +109,10 @@ export function useWorkspaceStream() {
|
|
|
108
109
|
// An initiative changed (created, plan ingested, an item settled) — patch the cache
|
|
109
110
|
// so an open tracker window / the board card reflects the transition live.
|
|
110
111
|
initiatives.upsert(event.initiative)
|
|
112
|
+
} else if (event.type === 'docInterview') {
|
|
113
|
+
// The interactive document interview advanced (a fresh batch of questions, an answer, or
|
|
114
|
+
// convergence) — patch the cache so an open interview window reflects it live.
|
|
115
|
+
docInterview.upsert(event.session)
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { DocInterviewSession } from '~/types/domain'
|
|
4
|
+
import { useWorkspaceStore } from '~/stores/workspace'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interactive document-interview sessions (WS5), keyed by their anchor BLOCK id. Loaded on
|
|
8
|
+
* demand when the interview window opens (`load`) and patched live from `docInterview` stream
|
|
9
|
+
* events (`upsert`), so an open window follows the interview as the interviewer asks / converges.
|
|
10
|
+
* Not carried in the workspace snapshot (a transient per-run gate, unlike initiatives).
|
|
11
|
+
* Per-workspace; nothing is persisted client-side.
|
|
12
|
+
*/
|
|
13
|
+
export const useDocInterviewStore = defineStore('docInterview', () => {
|
|
14
|
+
const api = useApi()
|
|
15
|
+
const workspace = useWorkspaceStore()
|
|
16
|
+
|
|
17
|
+
/** The sessions keyed by their anchor block id. */
|
|
18
|
+
const byBlock = ref<Record<string, DocInterviewSession>>({})
|
|
19
|
+
/** True while a window action (continue/proceed) is resuming the run. */
|
|
20
|
+
const resuming = ref(false)
|
|
21
|
+
|
|
22
|
+
function forBlock(blockId: string): DocInterviewSession | null {
|
|
23
|
+
return byBlock.value[blockId] ?? null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Patch from a live `docInterview` stream event or a call response (newest write wins). */
|
|
27
|
+
function upsert(session: DocInterviewSession) {
|
|
28
|
+
const existing = byBlock.value[session.blockId]
|
|
29
|
+
if (existing && existing.updatedAt > session.updatedAt) return
|
|
30
|
+
byBlock.value = { ...byBlock.value, [session.blockId]: session }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Re-fetch one block's session (the interview window's load path). */
|
|
34
|
+
async function load(blockId: string) {
|
|
35
|
+
if (!workspace.workspaceId) return
|
|
36
|
+
const session = await api.getDocInterview(workspace.workspaceId, blockId)
|
|
37
|
+
if (session) upsert(session)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Record the human's answer to one pending interview question (no run resume). */
|
|
41
|
+
async function answerQuestion(blockId: string, questionId: string, answer: string) {
|
|
42
|
+
if (!workspace.workspaceId) throw new Error('No active workspace')
|
|
43
|
+
const updated = await api.answerDocInterview(workspace.workspaceId, blockId, questionId, answer)
|
|
44
|
+
upsert(updated)
|
|
45
|
+
return updated
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Submit the answers and resume the interview (the interviewer re-runs, may ask more). */
|
|
49
|
+
async function continueInterview(blockId: string) {
|
|
50
|
+
if (!workspace.workspaceId) throw new Error('No active workspace')
|
|
51
|
+
resuming.value = true
|
|
52
|
+
try {
|
|
53
|
+
const updated = await api.continueDocInterview(workspace.workspaceId, blockId)
|
|
54
|
+
upsert(updated)
|
|
55
|
+
return updated
|
|
56
|
+
} finally {
|
|
57
|
+
resuming.value = false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Skip remaining questions: the interviewer converges and the run advances to the writer. */
|
|
62
|
+
async function proceedInterview(blockId: string) {
|
|
63
|
+
if (!workspace.workspaceId) throw new Error('No active workspace')
|
|
64
|
+
resuming.value = true
|
|
65
|
+
try {
|
|
66
|
+
const updated = await api.proceedDocInterview(workspace.workspaceId, blockId)
|
|
67
|
+
upsert(updated)
|
|
68
|
+
return updated
|
|
69
|
+
} finally {
|
|
70
|
+
resuming.value = false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function reset() {
|
|
75
|
+
byBlock.value = {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
byBlock,
|
|
80
|
+
resuming,
|
|
81
|
+
forBlock,
|
|
82
|
+
upsert,
|
|
83
|
+
load,
|
|
84
|
+
answerQuestion,
|
|
85
|
+
continueInterview,
|
|
86
|
+
proceedInterview,
|
|
87
|
+
reset,
|
|
88
|
+
}
|
|
89
|
+
})
|
package/app/stores/workspace.ts
CHANGED
|
@@ -84,6 +84,7 @@ export const useWorkspaceStore = defineStore(
|
|
|
84
84
|
useConsensusStore().reset()
|
|
85
85
|
useGitHubStore().reset()
|
|
86
86
|
useInitiativesStore().reset()
|
|
87
|
+
useDocInterviewStore().reset()
|
|
87
88
|
// The fragment picker catalog is per-board (the merged tenant catalog), so drop
|
|
88
89
|
// it too — the next inspector open re-fetches it for the switched-to board rather
|
|
89
90
|
// than showing the previous board's (or a raw-id placeholder for) fragments.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Interactive document-interview wire shapes (WS5), re-exported from the shared contracts
|
|
2
|
+
// package (the single source of truth across the wire boundary). The SPA imports these through
|
|
3
|
+
// `~/types/domain` like every other domain type.
|
|
4
|
+
export type {
|
|
5
|
+
AnswerDocInterviewInput,
|
|
6
|
+
DocInterviewQa,
|
|
7
|
+
DocInterviewSession,
|
|
8
|
+
DocInterviewStatus,
|
|
9
|
+
} from '@cat-factory/contracts'
|
package/app/types/domain.ts
CHANGED
package/i18n/locales/en.json
CHANGED
|
@@ -2718,6 +2718,22 @@
|
|
|
2718
2718
|
"body": "Slack notifications will stop until you reconnect."
|
|
2719
2719
|
}
|
|
2720
2720
|
},
|
|
2721
|
+
"docInterview": {
|
|
2722
|
+
"title": "Refine the document",
|
|
2723
|
+
"subtitle": "Answer the interviewer's questions so it can shape the document",
|
|
2724
|
+
"intro": "The interviewer is refining this document before it's written. Answer its questions to shape the scope, audience and structure, then continue — or proceed to draft with what it has.",
|
|
2725
|
+
"empty": "No interview found for this block.",
|
|
2726
|
+
"brief": "Authoring brief",
|
|
2727
|
+
"converged": "No questions are pending. The interviewer has what it needs and the document is being drafted.",
|
|
2728
|
+
"answerPlaceholder": "Your answer",
|
|
2729
|
+
"hint": "Continue lets the interviewer ask follow-ups; Proceed drafts with the answers so far.",
|
|
2730
|
+
"proceed": "Proceed to draft",
|
|
2731
|
+
"continue": "Continue",
|
|
2732
|
+
"status": {
|
|
2733
|
+
"awaiting": "Awaiting answers",
|
|
2734
|
+
"done": "Done"
|
|
2735
|
+
}
|
|
2736
|
+
},
|
|
2721
2737
|
"documents": {
|
|
2722
2738
|
"picker": {
|
|
2723
2739
|
"searchPlaceholder": "Search pages or paste a URL/ID…",
|
package/i18n/locales/es.json
CHANGED
|
@@ -2645,6 +2645,22 @@
|
|
|
2645
2645
|
"body": "Las notificaciones de Slack se detendrán hasta que vuelvas a conectar."
|
|
2646
2646
|
}
|
|
2647
2647
|
},
|
|
2648
|
+
"docInterview": {
|
|
2649
|
+
"title": "Refinar el documento",
|
|
2650
|
+
"subtitle": "Responde a las preguntas del entrevistador para dar forma al documento",
|
|
2651
|
+
"intro": "El entrevistador está refinando este documento antes de redactarlo. Responde a sus preguntas para definir el alcance, el público y la estructura, y luego continúa — o procede a redactar con lo que tiene.",
|
|
2652
|
+
"empty": "No se encontró ninguna entrevista para este bloque.",
|
|
2653
|
+
"brief": "Resumen de redacción",
|
|
2654
|
+
"converged": "No hay preguntas pendientes. El entrevistador tiene lo que necesita y el documento se está redactando.",
|
|
2655
|
+
"answerPlaceholder": "Tu respuesta",
|
|
2656
|
+
"hint": "Continuar permite al entrevistador hacer preguntas de seguimiento; Proceder redacta con las respuestas actuales.",
|
|
2657
|
+
"proceed": "Proceder a redactar",
|
|
2658
|
+
"continue": "Continuar",
|
|
2659
|
+
"status": {
|
|
2660
|
+
"awaiting": "Esperando respuestas",
|
|
2661
|
+
"done": "Hecho"
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2648
2664
|
"documents": {
|
|
2649
2665
|
"picker": {
|
|
2650
2666
|
"searchPlaceholder": "Busca páginas o pega una URL o ID…",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -2645,6 +2645,22 @@
|
|
|
2645
2645
|
"body": "Les notifications Slack seront interrompues jusqu'à la reconnexion."
|
|
2646
2646
|
}
|
|
2647
2647
|
},
|
|
2648
|
+
"docInterview": {
|
|
2649
|
+
"title": "Affiner le document",
|
|
2650
|
+
"subtitle": "Répondez aux questions de l'intervieweur pour façonner le document",
|
|
2651
|
+
"intro": "L'intervieweur affine ce document avant sa rédaction. Répondez à ses questions pour définir la portée, le public et la structure, puis continuez — ou procédez à la rédaction avec ce qu'il a.",
|
|
2652
|
+
"empty": "Aucun entretien trouvé pour ce bloc.",
|
|
2653
|
+
"brief": "Brief de rédaction",
|
|
2654
|
+
"converged": "Aucune question en attente. L'intervieweur a ce qu'il lui faut et le document est en cours de rédaction.",
|
|
2655
|
+
"answerPlaceholder": "Votre réponse",
|
|
2656
|
+
"hint": "Continuer permet à l'intervieweur de poser des questions complémentaires ; Procéder rédige avec les réponses actuelles.",
|
|
2657
|
+
"proceed": "Procéder à la rédaction",
|
|
2658
|
+
"continue": "Continuer",
|
|
2659
|
+
"status": {
|
|
2660
|
+
"awaiting": "En attente de réponses",
|
|
2661
|
+
"done": "Terminé"
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2648
2664
|
"documents": {
|
|
2649
2665
|
"picker": {
|
|
2650
2666
|
"searchPlaceholder": "Recherchez des pages ou collez une URL ou un ID…",
|