@cat-factory/app 0.54.0 → 0.54.2
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/bootstrap/BootstrapModal.vue +81 -69
- package/app/components/environments/EnvironmentStatusPanel.vue +37 -14
- package/app/components/focus/BlockFocusView.vue +92 -98
- package/app/components/fragments/FragmentLibraryManager.vue +104 -69
- package/app/components/fragments/FragmentLibraryPanel.vue +2 -1
- package/app/components/kaizen/KaizenPanel.vue +36 -25
- package/app/components/kaizen/KaizenStepStatus.vue +13 -10
- package/app/components/media/ArtifactLightbox.vue +20 -14
- package/app/components/media/ImageCompare.vue +35 -22
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +27 -14
- package/app/components/recurring/RecurrenceEditor.vue +22 -15
- package/app/components/sandbox/SandboxPanel.vue +126 -59
- package/app/pages/index.vue +20 -8
- package/app/stores/workspace.ts +10 -5
- package/i18n/locales/en.json +409 -0
- package/i18n/locales/es.json +400 -0
- package/i18n/locales/fr.json +400 -0
- package/i18n/locales/pl.json +400 -0
- package/i18n/locales/uk.json +400 -0
- package/package.json +1 -1
|
@@ -52,115 +52,109 @@ function openApprovalFor(approvalId: string) {
|
|
|
52
52
|
</script>
|
|
53
53
|
|
|
54
54
|
<template>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<UIcon name="
|
|
55
|
+
<!-- The focus-fade Transition is owned by the parent mount (pages/index.vue), so the
|
|
56
|
+
leave animation survives this component being unmounted by its v-if gate. -->
|
|
57
|
+
<div
|
|
58
|
+
v-if="block && statusMeta && typeMeta"
|
|
59
|
+
class="absolute inset-0 z-30 flex flex-col bg-slate-950/95 backdrop-blur"
|
|
60
|
+
>
|
|
61
|
+
<!-- header / breadcrumb -->
|
|
62
|
+
<header class="flex items-center gap-3 border-b border-slate-800 px-6 py-4">
|
|
63
|
+
<UButton icon="i-lucide-arrow-left" color="neutral" variant="ghost" size="sm" @click="close">
|
|
64
|
+
{{ t('focus.board') }}
|
|
65
|
+
</UButton>
|
|
66
|
+
<UIcon name="i-lucide-chevron-right" class="h-4 w-4 text-slate-600" />
|
|
67
|
+
<div
|
|
68
|
+
class="flex h-9 w-9 items-center justify-center rounded-lg"
|
|
69
|
+
:style="{ backgroundColor: typeMeta.accent + '22' }"
|
|
70
|
+
>
|
|
71
|
+
<UIcon :name="typeMeta.icon" class="h-5 w-5" :style="{ color: typeMeta.accent }" />
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
<h1 class="text-lg font-semibold text-white">{{ block.title }}</h1>
|
|
75
|
+
<div class="text-xs text-slate-500">
|
|
76
|
+
{{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<UBadge :color="statusMeta.chip as any" variant="subtle" class="ml-2">
|
|
80
|
+
{{ statusMeta.label }}
|
|
81
|
+
</UBadge>
|
|
82
|
+
<div class="ml-auto flex items-center gap-2">
|
|
83
|
+
<UDropdownMenu :items="runMenu">
|
|
84
|
+
<UButton
|
|
85
|
+
color="primary"
|
|
86
|
+
variant="soft"
|
|
87
|
+
size="sm"
|
|
88
|
+
icon="i-lucide-play"
|
|
89
|
+
trailing-icon="i-lucide-chevron-down"
|
|
90
|
+
>
|
|
91
|
+
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
92
|
+
</UButton>
|
|
93
|
+
</UDropdownMenu>
|
|
94
|
+
<UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
|
|
95
|
+
</div>
|
|
96
|
+
</header>
|
|
97
|
+
|
|
98
|
+
<div class="grid flex-1 grid-cols-[1fr_300px] gap-6 overflow-hidden p-6">
|
|
99
|
+
<!-- main: pipeline flow -->
|
|
100
|
+
<section
|
|
101
|
+
class="flex flex-col overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-6"
|
|
102
|
+
>
|
|
103
|
+
<div class="mb-4 flex items-center gap-2">
|
|
104
|
+
<UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
|
|
105
|
+
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
|
|
106
|
+
{{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
|
|
107
|
+
</h2>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<PipelineProgress
|
|
111
|
+
v-if="instance"
|
|
112
|
+
:instance="instance"
|
|
113
|
+
@open-decision="openDecisionFor"
|
|
114
|
+
@open-approval="openApprovalFor"
|
|
115
|
+
/>
|
|
116
|
+
|
|
72
117
|
<div
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
v-else
|
|
119
|
+
class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
|
|
75
120
|
>
|
|
76
|
-
|
|
121
|
+
{{ t('focus.emptyPipelineHint') }}
|
|
77
122
|
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<!-- side: details -->
|
|
126
|
+
<aside
|
|
127
|
+
class="space-y-4 overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-5"
|
|
128
|
+
>
|
|
78
129
|
<div>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
{{ t('focus.typeSubtitle', { type: typeMeta.label }) }}
|
|
130
|
+
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
131
|
+
{{ t('focus.description') }}
|
|
82
132
|
</div>
|
|
133
|
+
<p class="text-sm text-slate-300">{{ block.description }}</p>
|
|
83
134
|
</div>
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<div class="ml-auto flex items-center gap-2">
|
|
88
|
-
<UDropdownMenu :items="runMenu">
|
|
89
|
-
<UButton
|
|
90
|
-
color="primary"
|
|
91
|
-
variant="soft"
|
|
92
|
-
size="sm"
|
|
93
|
-
icon="i-lucide-play"
|
|
94
|
-
trailing-icon="i-lucide-chevron-down"
|
|
95
|
-
>
|
|
96
|
-
{{ instance ? t('focus.rerunPipeline') : t('focus.runPipeline') }}
|
|
97
|
-
</UButton>
|
|
98
|
-
</UDropdownMenu>
|
|
99
|
-
<UButton icon="i-lucide-x" color="neutral" variant="ghost" @click="close" />
|
|
100
|
-
</div>
|
|
101
|
-
</header>
|
|
102
|
-
|
|
103
|
-
<div class="grid flex-1 grid-cols-[1fr_300px] gap-6 overflow-hidden p-6">
|
|
104
|
-
<!-- main: pipeline flow -->
|
|
105
|
-
<section
|
|
106
|
-
class="flex flex-col overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-6"
|
|
107
|
-
>
|
|
108
|
-
<div class="mb-4 flex items-center gap-2">
|
|
109
|
-
<UIcon name="i-lucide-workflow" class="h-4 w-4 text-slate-500" />
|
|
110
|
-
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-400">
|
|
111
|
-
{{ instance ? instance.pipelineName : t('focus.noPipelineRunning') }}
|
|
112
|
-
</h2>
|
|
135
|
+
<div v-if="instance">
|
|
136
|
+
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
137
|
+
{{ t('focus.overallProgress') }}
|
|
113
138
|
</div>
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
:instance="instance"
|
|
118
|
-
@open-decision="openDecisionFor"
|
|
119
|
-
@open-approval="openApprovalFor"
|
|
120
|
-
/>
|
|
121
|
-
|
|
122
|
-
<div
|
|
123
|
-
v-else
|
|
124
|
-
class="flex flex-1 items-center justify-center rounded-xl border border-dashed border-slate-700 text-sm text-slate-500"
|
|
125
|
-
>
|
|
126
|
-
{{ t('focus.emptyPipelineHint') }}
|
|
139
|
+
<UProgress :model-value="Math.round(block.progress * 100)" />
|
|
140
|
+
<div class="mt-1 text-[11px] text-slate-400">
|
|
141
|
+
{{ n(block.progress, 'percent') }}
|
|
127
142
|
</div>
|
|
128
|
-
</
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class="space-y-4 overflow-auto rounded-2xl border border-slate-800 bg-slate-900/60 p-5"
|
|
133
|
-
>
|
|
134
|
-
<div>
|
|
135
|
-
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
136
|
-
{{ t('focus.description') }}
|
|
137
|
-
</div>
|
|
138
|
-
<p class="text-sm text-slate-300">{{ block.description }}</p>
|
|
139
|
-
</div>
|
|
140
|
-
<div v-if="instance">
|
|
141
|
-
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
142
|
-
{{ t('focus.overallProgress') }}
|
|
143
|
-
</div>
|
|
144
|
-
<UProgress :model-value="Math.round(block.progress * 100)" />
|
|
145
|
-
<div class="mt-1 text-[11px] text-slate-400">
|
|
146
|
-
{{ n(block.progress, 'percent') }}
|
|
147
|
-
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div>
|
|
145
|
+
<div class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
146
|
+
{{ t('focus.dependencies') }}
|
|
148
147
|
</div>
|
|
149
|
-
<div>
|
|
150
|
-
<
|
|
151
|
-
{{
|
|
152
|
-
</
|
|
153
|
-
<div v-if="deps.length" class="flex flex-wrap gap-1">
|
|
154
|
-
<UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
|
|
155
|
-
{{ d.title }}
|
|
156
|
-
</UBadge>
|
|
157
|
-
</div>
|
|
158
|
-
<div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
|
|
148
|
+
<div v-if="deps.length" class="flex flex-wrap gap-1">
|
|
149
|
+
<UBadge v-for="d in deps" :key="d.id" color="neutral" variant="subtle" size="sm">
|
|
150
|
+
{{ d.title }}
|
|
151
|
+
</UBadge>
|
|
159
152
|
</div>
|
|
160
|
-
|
|
161
|
-
|
|
153
|
+
<div v-else class="text-[11px] text-slate-500">{{ t('focus.noDependencies') }}</div>
|
|
154
|
+
</div>
|
|
155
|
+
</aside>
|
|
162
156
|
</div>
|
|
163
|
-
</
|
|
157
|
+
</div>
|
|
164
158
|
</template>
|
|
165
159
|
|
|
166
160
|
<style scoped>
|
|
@@ -30,6 +30,7 @@ const library =
|
|
|
30
30
|
: useFragmentLibrary(props.kind, props.ownerId)
|
|
31
31
|
const documents = useDocumentsStore()
|
|
32
32
|
const toast = useToast()
|
|
33
|
+
const { t, d } = useI18n()
|
|
33
34
|
|
|
34
35
|
const isWorkspace = props.kind === 'workspace'
|
|
35
36
|
/** Linking a document at the account scope needs a workspace connection to fetch through. */
|
|
@@ -60,13 +61,16 @@ const tabs = computed<Tab[]>(() =>
|
|
|
60
61
|
)
|
|
61
62
|
const tab = ref<Tab>(props.showCatalog ? 'catalog' : 'authored')
|
|
62
63
|
|
|
63
|
-
const ownerLabel =
|
|
64
|
+
const ownerLabel = computed(() =>
|
|
65
|
+
isWorkspace ? t('fragments.owner.workspace') : t('fragments.owner.account'),
|
|
66
|
+
)
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Exhaustive tier→label map of literal `t(...)` keys (keeps the typed-key drift guard live).
|
|
69
|
+
const tierLabel = computed<Record<ResolvedFragment['tier'], string>>(() => ({
|
|
70
|
+
builtin: t('fragments.tier.builtin'),
|
|
71
|
+
account: t('fragments.tier.account'),
|
|
72
|
+
workspace: t('fragments.tier.workspace'),
|
|
73
|
+
}))
|
|
70
74
|
// `as const` keeps the literal color names (assignable to UBadge's `color`
|
|
71
75
|
// union) instead of widening to `string`; `satisfies` still checks the shape.
|
|
72
76
|
const tierColor = {
|
|
@@ -75,11 +79,11 @@ const tierColor = {
|
|
|
75
79
|
workspace: 'primary',
|
|
76
80
|
} as const satisfies Record<ResolvedFragment['tier'], string>
|
|
77
81
|
|
|
78
|
-
function tabLabel(
|
|
79
|
-
if (
|
|
80
|
-
if (
|
|
81
|
-
if (
|
|
82
|
-
return '
|
|
82
|
+
function tabLabel(which: Tab): string {
|
|
83
|
+
if (which === 'catalog') return t('fragments.tab.catalog')
|
|
84
|
+
if (which === 'authored') return ownerLabel.value
|
|
85
|
+
if (which === 'documents') return t('fragments.tab.documents')
|
|
86
|
+
return t('fragments.tab.sources')
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
function notifyError(title: string, e: unknown) {
|
|
@@ -110,18 +114,18 @@ async function createFragment() {
|
|
|
110
114
|
.filter(Boolean),
|
|
111
115
|
})
|
|
112
116
|
draft.value = { title: '', summary: '', body: '', tags: '' }
|
|
113
|
-
toast.add({ title: '
|
|
117
|
+
toast.add({ title: t('fragments.toast.added'), icon: 'i-lucide-check' })
|
|
114
118
|
} catch (e) {
|
|
115
|
-
notifyError('
|
|
119
|
+
notifyError(t('fragments.toast.addFailed'), e)
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
async function removeFragment(id: string) {
|
|
120
124
|
try {
|
|
121
125
|
await library.remove(id)
|
|
122
|
-
toast.add({ title: '
|
|
126
|
+
toast.add({ title: t('fragments.toast.removed'), icon: 'i-lucide-trash-2' })
|
|
123
127
|
} catch (e) {
|
|
124
|
-
notifyError('
|
|
128
|
+
notifyError(t('fragments.toast.removeFailed'), e)
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -148,18 +152,18 @@ async function linkDocumentFragment() {
|
|
|
148
152
|
.filter(Boolean),
|
|
149
153
|
})
|
|
150
154
|
docDraft.value = { source: '', ref: '', tags: '' }
|
|
151
|
-
toast.add({ title: '
|
|
155
|
+
toast.add({ title: t('fragments.toast.documentLinked'), icon: 'i-lucide-link' })
|
|
152
156
|
} catch (e) {
|
|
153
|
-
notifyError('
|
|
157
|
+
notifyError(t('fragments.toast.linkDocumentFailed'), e)
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
async function refreshFragment(id: string) {
|
|
158
162
|
try {
|
|
159
163
|
await library.refreshDocumentFragment(id)
|
|
160
|
-
toast.add({ title: '
|
|
164
|
+
toast.add({ title: t('fragments.toast.refreshed'), icon: 'i-lucide-refresh-cw' })
|
|
161
165
|
} catch (e) {
|
|
162
|
-
notifyError('
|
|
166
|
+
notifyError(t('fragments.toast.refreshFailed'), e)
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
|
|
@@ -180,9 +184,9 @@ async function linkSource() {
|
|
|
180
184
|
})
|
|
181
185
|
sourceDraft.value = { repoOwner: '', repoName: '', dirPath: '', gitRef: '' }
|
|
182
186
|
await library.syncSource(source.id)
|
|
183
|
-
toast.add({ title: '
|
|
187
|
+
toast.add({ title: t('fragments.toast.sourceLinked'), icon: 'i-lucide-git-branch' })
|
|
184
188
|
} catch (e) {
|
|
185
|
-
notifyError('
|
|
189
|
+
notifyError(t('fragments.toast.linkSourceFailed'), e)
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
192
|
|
|
@@ -190,12 +194,15 @@ async function syncSource(id: string) {
|
|
|
190
194
|
try {
|
|
191
195
|
const result = await library.syncSource(id)
|
|
192
196
|
toast.add({
|
|
193
|
-
title:
|
|
197
|
+
title: t('fragments.toast.synced', {
|
|
198
|
+
updated: result.upserted,
|
|
199
|
+
removed: result.tombstoned,
|
|
200
|
+
}),
|
|
194
201
|
icon: 'i-lucide-refresh-cw',
|
|
195
202
|
color: 'info',
|
|
196
203
|
})
|
|
197
204
|
} catch (e) {
|
|
198
|
-
notifyError('
|
|
205
|
+
notifyError(t('fragments.toast.syncFailed'), e)
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
|
|
@@ -203,20 +210,22 @@ async function checkSource(id: string) {
|
|
|
203
210
|
try {
|
|
204
211
|
const status = await library.checkSource(id)
|
|
205
212
|
toast.add({
|
|
206
|
-
title: status.changed
|
|
213
|
+
title: status.changed
|
|
214
|
+
? t('fragments.toast.changesAvailable', { count: status.changedCount }, status.changedCount)
|
|
215
|
+
: t('fragments.toast.upToDate'),
|
|
207
216
|
icon: status.changed ? 'i-lucide-bell-dot' : 'i-lucide-check',
|
|
208
217
|
})
|
|
209
218
|
} catch (e) {
|
|
210
|
-
notifyError('
|
|
219
|
+
notifyError(t('fragments.toast.checkSourceFailed'), e)
|
|
211
220
|
}
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
async function unlinkSource(id: string) {
|
|
215
224
|
try {
|
|
216
225
|
await library.unlinkSource(id)
|
|
217
|
-
toast.add({ title: '
|
|
226
|
+
toast.add({ title: t('fragments.toast.sourceUnlinked'), icon: 'i-lucide-unplug' })
|
|
218
227
|
} catch (e) {
|
|
219
|
-
notifyError('
|
|
228
|
+
notifyError(t('fragments.toast.unlinkSourceFailed'), e)
|
|
220
229
|
}
|
|
221
230
|
}
|
|
222
231
|
</script>
|
|
@@ -225,14 +234,10 @@ async function unlinkSource(id: string) {
|
|
|
225
234
|
<div class="flex flex-col gap-4">
|
|
226
235
|
<p class="text-sm text-slate-400">
|
|
227
236
|
<template v-if="isWorkspace">
|
|
228
|
-
|
|
229
|
-
the built-in catalog, your account, and this board — later tiers override earlier ones —
|
|
230
|
-
then the relevant ones are selected for each agent run.
|
|
237
|
+
{{ t('fragments.intro.workspace') }}
|
|
231
238
|
</template>
|
|
232
239
|
<template v-else>
|
|
233
|
-
|
|
234
|
-
merged below the built-in catalog and above nothing, and a board can override or add its own
|
|
235
|
-
on top. The relevant ones are selected for each agent run.
|
|
240
|
+
{{ t('fragments.intro.account') }}
|
|
236
241
|
</template>
|
|
237
242
|
</p>
|
|
238
243
|
|
|
@@ -252,7 +257,13 @@ async function unlinkSource(id: string) {
|
|
|
252
257
|
<!-- Resolved (merged) catalog — workspace scope only -->
|
|
253
258
|
<div v-if="tab === 'catalog'" class="flex flex-col gap-2">
|
|
254
259
|
<p class="text-xs text-slate-500">
|
|
255
|
-
{{
|
|
260
|
+
{{
|
|
261
|
+
t(
|
|
262
|
+
'fragments.catalog.summary',
|
|
263
|
+
{ count: library.resolved.length, builtin: library.builtinCount },
|
|
264
|
+
library.resolved.length,
|
|
265
|
+
)
|
|
266
|
+
}}
|
|
256
267
|
</p>
|
|
257
268
|
<div
|
|
258
269
|
v-for="f in library.resolved"
|
|
@@ -271,7 +282,7 @@ async function unlinkSource(id: string) {
|
|
|
271
282
|
variant="subtle"
|
|
272
283
|
icon="i-lucide-radio"
|
|
273
284
|
>
|
|
274
|
-
|
|
285
|
+
{{ t('fragments.catalog.live', { source: f.documentRef.source }) }}
|
|
275
286
|
</UBadge>
|
|
276
287
|
<span class="ml-auto font-mono text-[11px] text-slate-500">{{ f.id }}</span>
|
|
277
288
|
</div>
|
|
@@ -294,7 +305,9 @@ async function unlinkSource(id: string) {
|
|
|
294
305
|
<div class="min-w-0">
|
|
295
306
|
<div class="flex items-center gap-2">
|
|
296
307
|
<span class="font-medium text-slate-100">{{ f.title }}</span>
|
|
297
|
-
<UBadge v-if="f.source" size="xs" color="info" variant="subtle">
|
|
308
|
+
<UBadge v-if="f.source" size="xs" color="info" variant="subtle">{{
|
|
309
|
+
t('fragments.authored.fromRepo')
|
|
310
|
+
}}</UBadge>
|
|
298
311
|
</div>
|
|
299
312
|
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
300
313
|
</div>
|
|
@@ -308,21 +321,27 @@ async function unlinkSource(id: string) {
|
|
|
308
321
|
/>
|
|
309
322
|
</div>
|
|
310
323
|
<p v-if="!library.fragments.length" class="text-sm text-slate-500">
|
|
311
|
-
|
|
312
|
-
|
|
324
|
+
{{
|
|
325
|
+
isWorkspace
|
|
326
|
+
? t('fragments.authored.empty.workspace')
|
|
327
|
+
: t('fragments.authored.empty.account')
|
|
328
|
+
}}
|
|
313
329
|
</p>
|
|
314
330
|
|
|
315
331
|
<div class="rounded-md border border-slate-800 p-3">
|
|
316
|
-
<p class="mb-2 text-sm font-medium">
|
|
332
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.authored.addTitle') }}</p>
|
|
317
333
|
<div class="flex flex-col gap-2">
|
|
318
|
-
<UInput v-model="draft.title" placeholder="
|
|
319
|
-
<UInput
|
|
334
|
+
<UInput v-model="draft.title" :placeholder="t('fragments.authored.titlePlaceholder')" />
|
|
335
|
+
<UInput
|
|
336
|
+
v-model="draft.summary"
|
|
337
|
+
:placeholder="t('fragments.authored.summaryPlaceholder')"
|
|
338
|
+
/>
|
|
320
339
|
<UTextarea
|
|
321
340
|
v-model="draft.body"
|
|
322
|
-
placeholder="
|
|
341
|
+
:placeholder="t('fragments.authored.bodyPlaceholder')"
|
|
323
342
|
:rows="4"
|
|
324
343
|
/>
|
|
325
|
-
<UInput v-model="draft.tags" placeholder="
|
|
344
|
+
<UInput v-model="draft.tags" :placeholder="t('fragments.authored.tagsPlaceholder')" />
|
|
326
345
|
<UButton
|
|
327
346
|
icon="i-lucide-plus"
|
|
328
347
|
size="sm"
|
|
@@ -331,7 +350,7 @@ async function unlinkSource(id: string) {
|
|
|
331
350
|
class="self-start"
|
|
332
351
|
@click="createFragment"
|
|
333
352
|
>
|
|
334
|
-
|
|
353
|
+
{{ t('fragments.authored.add') }}
|
|
335
354
|
</UButton>
|
|
336
355
|
</div>
|
|
337
356
|
</div>
|
|
@@ -340,9 +359,7 @@ async function unlinkSource(id: string) {
|
|
|
340
359
|
<!-- Document-backed (living) fragments -->
|
|
341
360
|
<div v-else-if="tab === 'documents'" class="flex flex-col gap-3">
|
|
342
361
|
<p class="text-xs text-slate-500">
|
|
343
|
-
|
|
344
|
-
re-resolved from the source at run time — edit the doc and the next agent run follows the
|
|
345
|
-
new version (no re-import).
|
|
362
|
+
{{ t('fragments.documents.intro') }}
|
|
346
363
|
</p>
|
|
347
364
|
|
|
348
365
|
<div
|
|
@@ -360,7 +377,7 @@ async function unlinkSource(id: string) {
|
|
|
360
377
|
</div>
|
|
361
378
|
<p class="text-sm text-slate-400">{{ f.summary }}</p>
|
|
362
379
|
<p v-if="f.resolvedAt" class="text-[11px] text-slate-500">
|
|
363
|
-
|
|
380
|
+
{{ t('fragments.documents.lastResolved', { date: d(new Date(f.resolvedAt), 'long') }) }}
|
|
364
381
|
</p>
|
|
365
382
|
</div>
|
|
366
383
|
<div class="ml-auto flex gap-1">
|
|
@@ -369,7 +386,7 @@ async function unlinkSource(id: string) {
|
|
|
369
386
|
size="xs"
|
|
370
387
|
variant="ghost"
|
|
371
388
|
:loading="library.loading"
|
|
372
|
-
title="
|
|
389
|
+
:title="t('fragments.documents.refreshTitle')"
|
|
373
390
|
@click="refreshFragment(f.id)"
|
|
374
391
|
/>
|
|
375
392
|
<UButton
|
|
@@ -382,17 +399,16 @@ async function unlinkSource(id: string) {
|
|
|
382
399
|
</div>
|
|
383
400
|
</div>
|
|
384
401
|
<p v-if="!documentFragments.length" class="text-sm text-slate-500">
|
|
385
|
-
|
|
402
|
+
{{ t('fragments.documents.empty') }}
|
|
386
403
|
</p>
|
|
387
404
|
|
|
388
405
|
<div class="rounded-md border border-slate-800 p-3">
|
|
389
|
-
<p class="mb-2 text-sm font-medium">
|
|
406
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.documents.linkTitle') }}</p>
|
|
390
407
|
<div v-if="docLinkDisabled" class="text-sm text-slate-500">
|
|
391
|
-
|
|
392
|
-
are fetched through one of this account's boards.
|
|
408
|
+
{{ t('fragments.documents.disabledHint') }}
|
|
393
409
|
</div>
|
|
394
410
|
<div v-else-if="!documents.connectedSources.length" class="text-sm text-slate-500">
|
|
395
|
-
|
|
411
|
+
{{ t('fragments.documents.connectFirst') }}
|
|
396
412
|
</div>
|
|
397
413
|
<div v-else class="flex flex-col gap-2">
|
|
398
414
|
<div class="flex flex-wrap gap-2">
|
|
@@ -407,11 +423,8 @@ async function unlinkSource(id: string) {
|
|
|
407
423
|
{{ s.label }}
|
|
408
424
|
</UButton>
|
|
409
425
|
</div>
|
|
410
|
-
<UInput
|
|
411
|
-
|
|
412
|
-
placeholder="Page id or URL (e.g. a Confluence/Notion page or GitHub file URL)"
|
|
413
|
-
/>
|
|
414
|
-
<UInput v-model="docDraft.tags" placeholder="Tags, comma-separated (optional)" />
|
|
426
|
+
<UInput v-model="docDraft.ref" :placeholder="t('fragments.documents.refPlaceholder')" />
|
|
427
|
+
<UInput v-model="docDraft.tags" :placeholder="t('fragments.documents.tagsPlaceholder')" />
|
|
415
428
|
<UButton
|
|
416
429
|
icon="i-lucide-link"
|
|
417
430
|
size="sm"
|
|
@@ -420,7 +433,7 @@ async function unlinkSource(id: string) {
|
|
|
420
433
|
class="self-start"
|
|
421
434
|
@click="linkDocumentFragment"
|
|
422
435
|
>
|
|
423
|
-
|
|
436
|
+
{{ t('fragments.documents.link') }}
|
|
424
437
|
</UButton>
|
|
425
438
|
</div>
|
|
426
439
|
</div>
|
|
@@ -440,7 +453,11 @@ async function unlinkSource(id: string) {
|
|
|
440
453
|
}}<span class="text-slate-500">/{{ s.dirPath || '' }}</span>
|
|
441
454
|
</span>
|
|
442
455
|
<p class="text-xs text-slate-500">
|
|
443
|
-
{{
|
|
456
|
+
{{
|
|
457
|
+
s.lastSyncedAt
|
|
458
|
+
? t('fragments.sources.metaSynced', { ref: s.gitRef })
|
|
459
|
+
: t('fragments.sources.metaNever', { ref: s.gitRef })
|
|
460
|
+
}}
|
|
444
461
|
</p>
|
|
445
462
|
</div>
|
|
446
463
|
<UBadge
|
|
@@ -450,7 +467,13 @@ async function unlinkSource(id: string) {
|
|
|
450
467
|
variant="subtle"
|
|
451
468
|
class="ml-auto"
|
|
452
469
|
>
|
|
453
|
-
{{
|
|
470
|
+
{{
|
|
471
|
+
t(
|
|
472
|
+
'fragments.sources.changes',
|
|
473
|
+
{ count: library.sourceChanges[s.id] },
|
|
474
|
+
library.sourceChanges[s.id] ?? 0,
|
|
475
|
+
)
|
|
476
|
+
}}
|
|
454
477
|
</UBadge>
|
|
455
478
|
<div class="ml-auto flex gap-1">
|
|
456
479
|
<UButton
|
|
@@ -476,23 +499,35 @@ async function unlinkSource(id: string) {
|
|
|
476
499
|
</div>
|
|
477
500
|
</div>
|
|
478
501
|
<p v-if="!library.sources.length" class="text-sm text-slate-500">
|
|
479
|
-
|
|
502
|
+
{{ t('fragments.sources.empty') }}
|
|
480
503
|
</p>
|
|
481
504
|
|
|
482
505
|
<div class="rounded-md border border-slate-800 p-3">
|
|
483
|
-
<p class="mb-2 text-sm font-medium">
|
|
506
|
+
<p class="mb-2 text-sm font-medium">{{ t('fragments.sources.linkTitle') }}</p>
|
|
484
507
|
<div class="flex flex-col gap-2">
|
|
485
508
|
<div class="flex gap-2">
|
|
486
|
-
<UInput
|
|
487
|
-
|
|
509
|
+
<UInput
|
|
510
|
+
v-model="sourceDraft.repoOwner"
|
|
511
|
+
:placeholder="t('fragments.sources.ownerPlaceholder')"
|
|
512
|
+
class="flex-1"
|
|
513
|
+
/>
|
|
514
|
+
<UInput
|
|
515
|
+
v-model="sourceDraft.repoName"
|
|
516
|
+
:placeholder="t('fragments.sources.repoPlaceholder')"
|
|
517
|
+
class="flex-1"
|
|
518
|
+
/>
|
|
488
519
|
</div>
|
|
489
520
|
<div class="flex gap-2">
|
|
490
521
|
<UInput
|
|
491
522
|
v-model="sourceDraft.dirPath"
|
|
492
|
-
placeholder="
|
|
523
|
+
:placeholder="t('fragments.sources.dirPlaceholder')"
|
|
524
|
+
class="flex-1"
|
|
525
|
+
/>
|
|
526
|
+
<UInput
|
|
527
|
+
v-model="sourceDraft.gitRef"
|
|
528
|
+
:placeholder="t('fragments.sources.refPlaceholder')"
|
|
493
529
|
class="flex-1"
|
|
494
530
|
/>
|
|
495
|
-
<UInput v-model="sourceDraft.gitRef" placeholder="ref (default HEAD)" class="flex-1" />
|
|
496
531
|
</div>
|
|
497
532
|
<UButton
|
|
498
533
|
icon="i-lucide-link"
|
|
@@ -502,7 +537,7 @@ async function unlinkSource(id: string) {
|
|
|
502
537
|
class="self-start"
|
|
503
538
|
@click="linkSource"
|
|
504
539
|
>
|
|
505
|
-
|
|
540
|
+
{{ t('fragments.sources.link') }}
|
|
506
541
|
</UButton>
|
|
507
542
|
</div>
|
|
508
543
|
</div>
|
|
@@ -7,6 +7,7 @@ import FragmentLibraryManager from '~/components/fragments/FragmentLibraryManage
|
|
|
7
7
|
|
|
8
8
|
const ui = useUiStore()
|
|
9
9
|
const workspace = useWorkspaceStore()
|
|
10
|
+
const { t } = useI18n()
|
|
10
11
|
|
|
11
12
|
const open = computed({
|
|
12
13
|
get: () => ui.fragmentLibraryOpen,
|
|
@@ -17,7 +18,7 @@ const open = computed({
|
|
|
17
18
|
</script>
|
|
18
19
|
|
|
19
20
|
<template>
|
|
20
|
-
<UModal v-model:open="open" title="
|
|
21
|
+
<UModal v-model:open="open" :title="t('fragments.panel.title')" :ui="{ content: 'max-w-3xl' }">
|
|
21
22
|
<template #body>
|
|
22
23
|
<FragmentLibraryManager
|
|
23
24
|
v-if="workspace.workspaceId"
|