@cat-factory/app 0.47.4 → 0.47.5
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/documents/ContextDocumentPicker.vue +17 -10
- package/app/components/documents/DocumentImportModal.vue +26 -13
- package/app/components/documents/DocumentSourceConnectModal.vue +15 -11
- package/app/components/documents/SpawnPreviewModal.vue +27 -11
- package/app/components/documents/TaskContextDocs.vue +9 -6
- package/app/components/github/AddServiceFromRepoModal.vue +40 -29
- package/app/components/github/GitHubConnect.vue +30 -19
- package/app/components/github/GitHubOnboarding.vue +5 -6
- package/app/components/github/GitHubPanel.vue +101 -63
- package/app/components/github/RepoTreeBrowser.vue +13 -6
- package/app/components/slack/SlackPanel.vue +68 -43
- package/app/components/tasks/ContextIssuePicker.vue +21 -14
- package/app/components/tasks/TaskContextIssues.vue +9 -6
- package/app/components/tasks/TaskImportModal.vue +38 -26
- package/app/components/tasks/TaskSourceConnectModal.vue +21 -16
- package/i18n/locales/en.json +333 -0
- package/i18n/locales/es.json +330 -0
- package/i18n/locales/fr.json +330 -0
- package/i18n/locales/pl.json +330 -0
- package/i18n/locales/uk.json +330 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ const props = defineProps<{
|
|
|
16
16
|
}>()
|
|
17
17
|
const emit = defineEmits<{ pick: [item: PendingContext] }>()
|
|
18
18
|
|
|
19
|
+
const { t } = useI18n()
|
|
19
20
|
const documents = useDocumentsStore()
|
|
20
21
|
|
|
21
22
|
const chosen = computed(() => new Set(props.chosenKeys ?? []))
|
|
@@ -182,14 +183,14 @@ onMounted(() => {
|
|
|
182
183
|
class="w-full"
|
|
183
184
|
:placeholder="
|
|
184
185
|
searchable
|
|
185
|
-
? '
|
|
186
|
-
: (descriptor?.refPlaceholder ?? '
|
|
186
|
+
? t('documents.picker.searchPlaceholder')
|
|
187
|
+
: (descriptor?.refPlaceholder ?? t('documents.picker.refPlaceholder'))
|
|
187
188
|
"
|
|
188
189
|
@keydown.enter="refRow && pickRef(refRow)"
|
|
189
190
|
/>
|
|
190
191
|
|
|
191
192
|
<p v-if="searchError" class="px-1 text-[11px] text-amber-400">
|
|
192
|
-
|
|
193
|
+
{{ t('documents.picker.searchFailed', { error: searchError }) }}
|
|
193
194
|
</p>
|
|
194
195
|
|
|
195
196
|
<div class="max-h-56 space-y-0.5 overflow-y-auto">
|
|
@@ -203,7 +204,9 @@ onMounted(() => {
|
|
|
203
204
|
>
|
|
204
205
|
<UIcon :name="icon" class="h-3.5 w-3.5 shrink-0 text-indigo-400" />
|
|
205
206
|
<span class="truncate">{{ d.title }}</span>
|
|
206
|
-
<UBadge color="neutral" variant="soft" size="xs" class="ml-auto shrink-0">
|
|
207
|
+
<UBadge color="neutral" variant="soft" size="xs" class="ml-auto shrink-0">{{
|
|
208
|
+
t('documents.picker.importedBadge')
|
|
209
|
+
}}</UBadge>
|
|
207
210
|
</button>
|
|
208
211
|
|
|
209
212
|
<!-- Source search hits (imported on add). -->
|
|
@@ -226,18 +229,22 @@ onMounted(() => {
|
|
|
226
229
|
@click="pickRef(refRow)"
|
|
227
230
|
>
|
|
228
231
|
<UIcon name="i-lucide-link" class="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
|
229
|
-
<span class="truncate"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
<span class="truncate">
|
|
233
|
+
<i18n-t keypath="documents.picker.attachByReference" scope="global">
|
|
234
|
+
<template #ref>
|
|
235
|
+
<span class="text-slate-200">{{ refRow }}</span>
|
|
236
|
+
</template>
|
|
237
|
+
</i18n-t>
|
|
238
|
+
</span>
|
|
232
239
|
</button>
|
|
233
240
|
|
|
234
241
|
<p v-if="empty" class="px-2 py-1.5 text-[11px] text-slate-500">
|
|
235
242
|
{{
|
|
236
243
|
query.trim()
|
|
237
|
-
? '
|
|
244
|
+
? t('documents.picker.noMatches')
|
|
238
245
|
: searchable
|
|
239
|
-
? '
|
|
240
|
-
: '
|
|
246
|
+
? t('documents.picker.emptySearchable')
|
|
247
|
+
: t('documents.picker.emptyRefOnly')
|
|
241
248
|
}}
|
|
242
249
|
</p>
|
|
243
250
|
</div>
|
|
@@ -6,6 +6,7 @@ import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
|
6
6
|
// board structure. A source selector lets the user choose which connected source
|
|
7
7
|
// to import from (Confluence, Notion, …). Carries an optional target frame from
|
|
8
8
|
// the inspector so "Preview & spawn" lands the structure inside that frame.
|
|
9
|
+
const { t } = useI18n()
|
|
9
10
|
const ui = useUiStore()
|
|
10
11
|
const documents = useDocumentsStore()
|
|
11
12
|
const board = useBoardStore()
|
|
@@ -60,10 +61,13 @@ async function doImport() {
|
|
|
60
61
|
try {
|
|
61
62
|
const doc = await documents.importDocument(source.value, value)
|
|
62
63
|
ref_.value = ''
|
|
63
|
-
toast.add({
|
|
64
|
+
toast.add({
|
|
65
|
+
title: t('documents.import.imported', { title: doc.title }),
|
|
66
|
+
icon: 'i-lucide-file-down',
|
|
67
|
+
})
|
|
64
68
|
} catch (e) {
|
|
65
69
|
toast.add({
|
|
66
|
-
title: '
|
|
70
|
+
title: t('documents.import.importFailed'),
|
|
67
71
|
description: e instanceof Error ? e.message : String(e),
|
|
68
72
|
icon: 'i-lucide-triangle-alert',
|
|
69
73
|
color: 'error',
|
|
@@ -79,14 +83,14 @@ function preview(externalId: string) {
|
|
|
79
83
|
</script>
|
|
80
84
|
|
|
81
85
|
<template>
|
|
82
|
-
<UModal v-model:open="open" title="
|
|
86
|
+
<UModal v-model:open="open" :title="t('documents.import.title')">
|
|
83
87
|
<template #title>
|
|
84
|
-
<IntegrationBackTitle title="
|
|
88
|
+
<IntegrationBackTitle :title="t('documents.import.title')" @back="back" />
|
|
85
89
|
</template>
|
|
86
90
|
<template #body>
|
|
87
91
|
<div v-if="!documents.anyConnected" class="space-y-3 text-center">
|
|
88
92
|
<UIcon name="i-lucide-plug" class="mx-auto h-8 w-8 text-slate-500" />
|
|
89
|
-
<p class="text-sm text-slate-400">
|
|
93
|
+
<p class="text-sm text-slate-400">{{ t('documents.import.connectFirst') }}</p>
|
|
90
94
|
<div class="flex justify-center gap-2">
|
|
91
95
|
<UButton
|
|
92
96
|
v-for="s in documents.sources"
|
|
@@ -96,22 +100,29 @@ function preview(externalId: string) {
|
|
|
96
100
|
:icon="s.icon"
|
|
97
101
|
@click="ui.openDocumentConnect(s.source)"
|
|
98
102
|
>
|
|
99
|
-
|
|
103
|
+
{{ t('documents.import.connectSource', { source: s.label }) }}
|
|
100
104
|
</UButton>
|
|
101
105
|
</div>
|
|
102
106
|
</div>
|
|
103
107
|
|
|
104
108
|
<div v-else class="space-y-4">
|
|
105
109
|
<p v-if="targetFrameTitle" class="text-xs text-slate-400">
|
|
106
|
-
|
|
110
|
+
<i18n-t keypath="documents.import.spawningInto" scope="global">
|
|
111
|
+
<template #frame>
|
|
112
|
+
<span class="font-medium text-slate-200">{{ targetFrameTitle }}</span>
|
|
113
|
+
</template>
|
|
114
|
+
</i18n-t>
|
|
107
115
|
</p>
|
|
108
116
|
|
|
109
|
-
<UFormField v-if="sourceItems.length > 1" label="
|
|
117
|
+
<UFormField v-if="sourceItems.length > 1" :label="t('documents.import.sourceLabel')">
|
|
110
118
|
<USelect v-model="source" :items="sourceItems" class="w-full" />
|
|
111
119
|
</UFormField>
|
|
112
120
|
|
|
113
121
|
<div class="flex items-end gap-2">
|
|
114
|
-
<UFormField
|
|
122
|
+
<UFormField
|
|
123
|
+
:label="descriptor?.refLabel ?? t('documents.import.refLabel')"
|
|
124
|
+
class="flex-1"
|
|
125
|
+
>
|
|
115
126
|
<UInput
|
|
116
127
|
v-model="ref_"
|
|
117
128
|
:placeholder="descriptor?.refPlaceholder"
|
|
@@ -126,13 +137,13 @@ function preview(externalId: string) {
|
|
|
126
137
|
:disabled="!ref_.trim()"
|
|
127
138
|
@click="doImport"
|
|
128
139
|
>
|
|
129
|
-
|
|
140
|
+
{{ t('documents.import.importButton') }}
|
|
130
141
|
</UButton>
|
|
131
142
|
</div>
|
|
132
143
|
|
|
133
144
|
<div v-if="sourceDocs.length" class="space-y-2">
|
|
134
145
|
<h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
135
|
-
|
|
146
|
+
{{ t('documents.import.importedHeading') }}
|
|
136
147
|
</h3>
|
|
137
148
|
<div
|
|
138
149
|
v-for="doc in sourceDocs"
|
|
@@ -158,12 +169,14 @@ function preview(externalId: string) {
|
|
|
158
169
|
icon="i-lucide-wand-sparkles"
|
|
159
170
|
@click="preview(doc.externalId)"
|
|
160
171
|
>
|
|
161
|
-
|
|
172
|
+
{{ t('documents.import.previewSpawn') }}
|
|
162
173
|
</UButton>
|
|
163
174
|
</div>
|
|
164
175
|
</div>
|
|
165
176
|
</div>
|
|
166
|
-
<p v-else class="text-center text-xs text-slate-500">
|
|
177
|
+
<p v-else class="text-center text-xs text-slate-500">
|
|
178
|
+
{{ t('documents.import.noneImported') }}
|
|
179
|
+
</p>
|
|
167
180
|
</div>
|
|
168
181
|
</template>
|
|
169
182
|
</UModal>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// "Connected" with empty fields.
|
|
7
7
|
import IntegrationBackTitle from '~/components/layout/IntegrationBackTitle.vue'
|
|
8
8
|
|
|
9
|
+
const { t } = useI18n()
|
|
9
10
|
const ui = useUiStore()
|
|
10
11
|
const documents = useDocumentsStore()
|
|
11
12
|
const toast = useToast()
|
|
@@ -54,14 +55,14 @@ async function submit() {
|
|
|
54
55
|
try {
|
|
55
56
|
await documents.connect(source.value, credentials)
|
|
56
57
|
toast.add({
|
|
57
|
-
title:
|
|
58
|
+
title: t('documents.connect.connected', { source: descriptor.value!.label }),
|
|
58
59
|
icon: 'i-lucide-check',
|
|
59
60
|
color: 'success',
|
|
60
61
|
})
|
|
61
62
|
ui.closeDocumentConnect()
|
|
62
63
|
} catch (e) {
|
|
63
64
|
toast.add({
|
|
64
|
-
title: '
|
|
65
|
+
title: t('documents.connect.connectFailed'),
|
|
65
66
|
description: e instanceof Error ? e.message : String(e),
|
|
66
67
|
icon: 'i-lucide-triangle-alert',
|
|
67
68
|
color: 'error',
|
|
@@ -75,7 +76,9 @@ async function disconnect() {
|
|
|
75
76
|
if (!source.value) return
|
|
76
77
|
await documents.disconnect(source.value)
|
|
77
78
|
toast.add({
|
|
78
|
-
title:
|
|
79
|
+
title: t('documents.connect.disconnected', {
|
|
80
|
+
source: descriptor.value?.label ?? t('documents.connect.sourceFallback'),
|
|
81
|
+
}),
|
|
79
82
|
icon: 'i-lucide-unplug',
|
|
80
83
|
})
|
|
81
84
|
ui.closeDocumentConnect()
|
|
@@ -83,15 +86,17 @@ async function disconnect() {
|
|
|
83
86
|
</script>
|
|
84
87
|
|
|
85
88
|
<template>
|
|
86
|
-
<UModal v-model:open="open" :title="descriptor?.label ?? '
|
|
89
|
+
<UModal v-model:open="open" :title="descriptor?.label ?? t('documents.connect.title')">
|
|
87
90
|
<template #title>
|
|
88
|
-
<IntegrationBackTitle
|
|
91
|
+
<IntegrationBackTitle
|
|
92
|
+
:title="descriptor?.label ?? t('documents.connect.title')"
|
|
93
|
+
@back="back"
|
|
94
|
+
/>
|
|
89
95
|
</template>
|
|
90
96
|
<template #body>
|
|
91
97
|
<div v-if="descriptor" class="space-y-4">
|
|
92
98
|
<p class="text-sm text-slate-400">
|
|
93
|
-
|
|
94
|
-
structure or attach them to tasks as agent context.
|
|
99
|
+
{{ t('documents.connect.intro', { source: descriptor.label }) }}
|
|
95
100
|
</p>
|
|
96
101
|
|
|
97
102
|
<p
|
|
@@ -101,8 +106,7 @@ async function disconnect() {
|
|
|
101
106
|
>
|
|
102
107
|
<UIcon name="i-lucide-user" class="mt-0.5 size-3.5 shrink-0" />
|
|
103
108
|
<span>
|
|
104
|
-
|
|
105
|
-
account, never shared with the rest of the workspace.
|
|
109
|
+
{{ t('documents.connect.personalNote') }}
|
|
106
110
|
</span>
|
|
107
111
|
</p>
|
|
108
112
|
|
|
@@ -130,7 +134,7 @@ async function disconnect() {
|
|
|
130
134
|
icon="i-lucide-unplug"
|
|
131
135
|
@click="disconnect"
|
|
132
136
|
>
|
|
133
|
-
|
|
137
|
+
{{ t('documents.connect.disconnect') }}
|
|
134
138
|
</UButton>
|
|
135
139
|
<div v-else />
|
|
136
140
|
<UButton
|
|
@@ -140,7 +144,7 @@ async function disconnect() {
|
|
|
140
144
|
:disabled="!canSubmit"
|
|
141
145
|
@click="submit"
|
|
142
146
|
>
|
|
143
|
-
{{ connected ? '
|
|
147
|
+
{{ connected ? t('documents.connect.update') : t('documents.connect.connect') }}
|
|
144
148
|
</UButton>
|
|
145
149
|
</div>
|
|
146
150
|
</div>
|
|
@@ -4,6 +4,7 @@ import type { DocumentBoardPlan } from '~/types/domain'
|
|
|
4
4
|
// Preview the structure an imported document expands into, then spawn it. The
|
|
5
5
|
// plan is fetched fresh on open; a badge makes clear whether an LLM or the
|
|
6
6
|
// deterministic heading parser produced it.
|
|
7
|
+
const { t } = useI18n()
|
|
7
8
|
const ui = useUiStore()
|
|
8
9
|
const documents = useDocumentsStore()
|
|
9
10
|
const board = useBoardStore()
|
|
@@ -36,7 +37,7 @@ watch(
|
|
|
36
37
|
plan.value = await documents.plan(preview.source, externalId)
|
|
37
38
|
} catch (e) {
|
|
38
39
|
toast.add({
|
|
39
|
-
title: '
|
|
40
|
+
title: t('documents.spawn.planFailed'),
|
|
40
41
|
description: e instanceof Error ? e.message : String(e),
|
|
41
42
|
icon: 'i-lucide-triangle-alert',
|
|
42
43
|
color: 'error',
|
|
@@ -59,8 +60,12 @@ async function spawn() {
|
|
|
59
60
|
targetFrameId.value ?? undefined,
|
|
60
61
|
)
|
|
61
62
|
toast.add({
|
|
62
|
-
title: '
|
|
63
|
-
description:
|
|
63
|
+
title: t('documents.spawn.spawned'),
|
|
64
|
+
description: t('documents.spawn.summary', {
|
|
65
|
+
frames: t('documents.spawn.frameCount', { count: result.frames }, result.frames),
|
|
66
|
+
modules: t('documents.spawn.moduleCount', { count: result.modules }, result.modules),
|
|
67
|
+
tasks: t('documents.spawn.taskCount', { count: result.tasks }, result.tasks),
|
|
68
|
+
}),
|
|
64
69
|
icon: 'i-lucide-check',
|
|
65
70
|
color: 'success',
|
|
66
71
|
})
|
|
@@ -68,7 +73,7 @@ async function spawn() {
|
|
|
68
73
|
ui.closeDocumentImport()
|
|
69
74
|
} catch (e) {
|
|
70
75
|
toast.add({
|
|
71
|
-
title: '
|
|
76
|
+
title: t('documents.spawn.spawnFailed'),
|
|
72
77
|
description: e instanceof Error ? e.message : String(e),
|
|
73
78
|
icon: 'i-lucide-triangle-alert',
|
|
74
79
|
color: 'error',
|
|
@@ -80,7 +85,7 @@ async function spawn() {
|
|
|
80
85
|
</script>
|
|
81
86
|
|
|
82
87
|
<template>
|
|
83
|
-
<UModal v-model:open="open" title="
|
|
88
|
+
<UModal v-model:open="open" :title="t('documents.spawn.title')">
|
|
84
89
|
<template #body>
|
|
85
90
|
<div class="space-y-4">
|
|
86
91
|
<div v-if="plan" class="flex items-center justify-between gap-2">
|
|
@@ -89,16 +94,25 @@ async function spawn() {
|
|
|
89
94
|
variant="subtle"
|
|
90
95
|
size="sm"
|
|
91
96
|
>
|
|
92
|
-
{{
|
|
97
|
+
{{
|
|
98
|
+
plan.planner === 'llm'
|
|
99
|
+
? t('documents.spawn.plannerLlm')
|
|
100
|
+
: t('documents.spawn.plannerHeadings')
|
|
101
|
+
}}
|
|
93
102
|
</UBadge>
|
|
94
103
|
<span v-if="targetFrameTitle" class="text-xs text-slate-400">
|
|
95
|
-
|
|
104
|
+
<i18n-t keypath="documents.spawn.intoFrame" scope="global">
|
|
105
|
+
<template #frame>
|
|
106
|
+
<span class="font-medium text-slate-200">{{ targetFrameTitle }}</span>
|
|
107
|
+
</template>
|
|
108
|
+
</i18n-t>
|
|
96
109
|
</span>
|
|
97
|
-
<span v-else class="text-xs text-slate-400">
|
|
110
|
+
<span v-else class="text-xs text-slate-400">{{ t('documents.spawn.asTopLevel') }}</span>
|
|
98
111
|
</div>
|
|
99
112
|
|
|
100
113
|
<div v-if="loadingPlan" class="flex items-center gap-2 text-sm text-slate-400">
|
|
101
|
-
<UIcon name="i-lucide-loader" class="h-4 w-4 animate-spin" />
|
|
114
|
+
<UIcon name="i-lucide-loader" class="h-4 w-4 animate-spin" />
|
|
115
|
+
{{ t('documents.spawn.buildingPlan') }}
|
|
102
116
|
</div>
|
|
103
117
|
|
|
104
118
|
<div v-else-if="plan" class="max-h-80 space-y-3 overflow-y-auto pr-1">
|
|
@@ -144,7 +158,9 @@ async function spawn() {
|
|
|
144
158
|
</div>
|
|
145
159
|
|
|
146
160
|
<div class="flex justify-end gap-2 pt-1">
|
|
147
|
-
<UButton color="neutral" variant="ghost" @click="ui.closeSpawnPreview()">
|
|
161
|
+
<UButton color="neutral" variant="ghost" @click="ui.closeSpawnPreview()">{{
|
|
162
|
+
t('common.cancel')
|
|
163
|
+
}}</UButton>
|
|
148
164
|
<UButton
|
|
149
165
|
color="primary"
|
|
150
166
|
icon="i-lucide-wand-sparkles"
|
|
@@ -152,7 +168,7 @@ async function spawn() {
|
|
|
152
168
|
:disabled="!plan || loadingPlan"
|
|
153
169
|
@click="spawn"
|
|
154
170
|
>
|
|
155
|
-
|
|
171
|
+
{{ t('documents.spawn.spawnOntoBoard') }}
|
|
156
172
|
</UButton>
|
|
157
173
|
</div>
|
|
158
174
|
</div>
|
|
@@ -7,6 +7,7 @@ import type { Block, DocumentSourceKind } from '~/types/domain'
|
|
|
7
7
|
// backend's userPromptFor). Rendered only when the integration is available.
|
|
8
8
|
const props = defineProps<{ block: Block }>()
|
|
9
9
|
|
|
10
|
+
const { t } = useI18n()
|
|
10
11
|
const documents = useDocumentsStore()
|
|
11
12
|
const ui = useUiStore()
|
|
12
13
|
const toast = useToast()
|
|
@@ -20,10 +21,10 @@ const linked = computed(() => documents.docsForBlock(props.block.id))
|
|
|
20
21
|
async function attach(source: DocumentSourceKind, externalId: string) {
|
|
21
22
|
try {
|
|
22
23
|
await documents.linkToBlock(props.block.id, source, externalId)
|
|
23
|
-
toast.add({ title: '
|
|
24
|
+
toast.add({ title: t('documents.taskDocs.attached'), icon: 'i-lucide-link' })
|
|
24
25
|
} catch (e) {
|
|
25
26
|
toast.add({
|
|
26
|
-
title: '
|
|
27
|
+
title: t('documents.taskDocs.attachFailed'),
|
|
27
28
|
description: e instanceof Error ? e.message : String(e),
|
|
28
29
|
icon: 'i-lucide-triangle-alert',
|
|
29
30
|
color: 'error',
|
|
@@ -41,7 +42,7 @@ const attachMenu = computed<DropdownMenuItem[][]>(() => {
|
|
|
41
42
|
onSelect: () => attach(d.source, d.externalId),
|
|
42
43
|
}))
|
|
43
44
|
items.push({
|
|
44
|
-
label: '
|
|
45
|
+
label: t('documents.taskDocs.importPage'),
|
|
45
46
|
icon: 'i-lucide-file-down',
|
|
46
47
|
onSelect: () => ui.openDocumentImport(null),
|
|
47
48
|
})
|
|
@@ -53,10 +54,12 @@ const attachMenu = computed<DropdownMenuItem[][]>(() => {
|
|
|
53
54
|
<div v-if="documents.available" class="space-y-2">
|
|
54
55
|
<div class="flex items-center justify-between">
|
|
55
56
|
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
|
|
56
|
-
|
|
57
|
+
{{ t('documents.taskDocs.heading') }}
|
|
57
58
|
</span>
|
|
58
59
|
<UDropdownMenu :items="attachMenu" :content="{ side: 'bottom', align: 'end' }">
|
|
59
|
-
<UButton color="neutral" variant="soft" size="xs" icon="i-lucide-plus">
|
|
60
|
+
<UButton color="neutral" variant="soft" size="xs" icon="i-lucide-plus">{{
|
|
61
|
+
t('documents.taskDocs.attach')
|
|
62
|
+
}}</UButton>
|
|
60
63
|
</UDropdownMenu>
|
|
61
64
|
</div>
|
|
62
65
|
|
|
@@ -77,7 +80,7 @@ const attachMenu = computed<DropdownMenuItem[][]>(() => {
|
|
|
77
80
|
</a>
|
|
78
81
|
</div>
|
|
79
82
|
<p v-else class="text-[11px] text-slate-500">
|
|
80
|
-
|
|
83
|
+
{{ t('documents.taskDocs.empty') }}
|
|
81
84
|
</p>
|
|
82
85
|
</div>
|
|
83
86
|
</template>
|
|
@@ -15,6 +15,7 @@ import RepoTreeBrowser from '~/components/github/RepoTreeBrowser.vue'
|
|
|
15
15
|
import ServiceTestConfig from '~/components/panels/inspector/ServiceTestConfig.vue'
|
|
16
16
|
import ServiceFragments from '~/components/panels/inspector/ServiceFragments.vue'
|
|
17
17
|
|
|
18
|
+
const { t } = useI18n()
|
|
18
19
|
const ui = useUiStore()
|
|
19
20
|
const github = useGitHubStore()
|
|
20
21
|
const board = useBoardStore()
|
|
@@ -60,9 +61,13 @@ const onBoardIds = computed(
|
|
|
60
61
|
const repoItems = computed(() =>
|
|
61
62
|
github.availableRepos.map((r) => {
|
|
62
63
|
const onBoard = onBoardIds.value.has(r.githubId) && !r.isMonorepo
|
|
63
|
-
const
|
|
64
|
+
const suffix = [
|
|
65
|
+
r.private ? t('github.addService.repoLabel.private') : '',
|
|
66
|
+
r.isMonorepo ? t('github.addService.repoLabel.monorepo') : '',
|
|
67
|
+
onBoard ? t('github.addService.repoLabel.onBoard') : '',
|
|
68
|
+
].join('')
|
|
64
69
|
return {
|
|
65
|
-
label: `${r.owner}/${r.name}${
|
|
70
|
+
label: `${r.owner}/${r.name}${suffix}`,
|
|
66
71
|
// Searched on (lowercased once) — the owner/name, so the filter matches either.
|
|
67
72
|
search: `${r.owner}/${r.name}`.toLowerCase(),
|
|
68
73
|
value: r.githubId,
|
|
@@ -175,8 +180,8 @@ async function add() {
|
|
|
175
180
|
configuredBlockId.value = block.id
|
|
176
181
|
configuredDirectory.value = isMonorepo.value ? selectedDirectory.value : undefined
|
|
177
182
|
toast.add({
|
|
178
|
-
title: '
|
|
179
|
-
description:
|
|
183
|
+
title: t('github.addService.toast.addedTitle'),
|
|
184
|
+
description: t('github.addService.toast.addedDescription', { title: block.title }),
|
|
180
185
|
icon: 'i-lucide-check',
|
|
181
186
|
color: 'success',
|
|
182
187
|
})
|
|
@@ -184,7 +189,7 @@ async function add() {
|
|
|
184
189
|
selectedDirectory.value = undefined
|
|
185
190
|
} catch (e) {
|
|
186
191
|
toast.add({
|
|
187
|
-
title: '
|
|
192
|
+
title: t('github.addService.toast.addFailedTitle'),
|
|
188
193
|
description: e instanceof Error ? e.message : String(e),
|
|
189
194
|
icon: 'i-lucide-triangle-alert',
|
|
190
195
|
color: 'error',
|
|
@@ -200,12 +205,11 @@ function done() {
|
|
|
200
205
|
</script>
|
|
201
206
|
|
|
202
207
|
<template>
|
|
203
|
-
<UModal v-model:open="open" title="
|
|
208
|
+
<UModal v-model:open="open" :title="t('github.addService.title')" :ui="{ content: 'max-w-xl' }">
|
|
204
209
|
<template #body>
|
|
205
210
|
<div class="space-y-6">
|
|
206
211
|
<p class="text-sm text-slate-400">
|
|
207
|
-
|
|
208
|
-
is linked to a new service frame as-is, and tasks you run on it target that repo.
|
|
212
|
+
{{ t('github.addService.intro') }}
|
|
209
213
|
</p>
|
|
210
214
|
|
|
211
215
|
<!-- not connected: linking a repo needs the App bound to this workspace -->
|
|
@@ -216,8 +220,7 @@ function done() {
|
|
|
216
220
|
<div class="flex items-start gap-2">
|
|
217
221
|
<UIcon name="i-lucide-plug-zap" class="mt-0.5 h-4 w-4 shrink-0 text-amber-400" />
|
|
218
222
|
<p class="text-sm text-amber-200/90">
|
|
219
|
-
|
|
220
|
-
install it.
|
|
223
|
+
{{ t('github.addService.connectFirst') }}
|
|
221
224
|
</p>
|
|
222
225
|
</div>
|
|
223
226
|
<GitHubConnect />
|
|
@@ -225,18 +228,18 @@ function done() {
|
|
|
225
228
|
|
|
226
229
|
<template v-else>
|
|
227
230
|
<UFormField
|
|
228
|
-
label="
|
|
229
|
-
description="
|
|
231
|
+
:label="t('github.addService.repository')"
|
|
232
|
+
:description="t('github.addService.repositoryHint')"
|
|
230
233
|
required
|
|
231
234
|
>
|
|
232
235
|
<div v-if="!hasRepos" class="text-sm text-slate-400">
|
|
233
|
-
|
|
236
|
+
{{ t('github.addService.noReposAvailable') }}
|
|
234
237
|
</div>
|
|
235
238
|
<div v-else class="space-y-1.5">
|
|
236
239
|
<UInput
|
|
237
240
|
v-model="repoSearch"
|
|
238
241
|
icon="i-lucide-search"
|
|
239
|
-
placeholder="
|
|
242
|
+
:placeholder="t('github.addService.filterPlaceholder')"
|
|
240
243
|
class="w-full"
|
|
241
244
|
:ui="{ trailing: 'pe-1' }"
|
|
242
245
|
>
|
|
@@ -246,7 +249,7 @@ function done() {
|
|
|
246
249
|
variant="link"
|
|
247
250
|
size="sm"
|
|
248
251
|
icon="i-lucide-x"
|
|
249
|
-
aria-label="
|
|
252
|
+
:aria-label="t('github.addService.clearFilter')"
|
|
250
253
|
@click="repoSearch = ''"
|
|
251
254
|
/>
|
|
252
255
|
</template>
|
|
@@ -254,11 +257,16 @@ function done() {
|
|
|
254
257
|
<USelect
|
|
255
258
|
v-model="selectedRepoId"
|
|
256
259
|
:items="filteredRepoItems"
|
|
257
|
-
placeholder="
|
|
260
|
+
:placeholder="t('github.addService.chooseRepository')"
|
|
258
261
|
class="w-full"
|
|
259
262
|
/>
|
|
260
263
|
<p class="text-xs text-slate-500">
|
|
261
|
-
|
|
264
|
+
{{
|
|
265
|
+
t('github.addService.showingCount', {
|
|
266
|
+
shown: filteredRepoItems.length,
|
|
267
|
+
total: repoItems.length,
|
|
268
|
+
})
|
|
269
|
+
}}
|
|
262
270
|
</p>
|
|
263
271
|
</div>
|
|
264
272
|
</UFormField>
|
|
@@ -267,8 +275,8 @@ function done() {
|
|
|
267
275
|
<div v-if="selectedRepoId !== undefined" class="space-y-3">
|
|
268
276
|
<USwitch
|
|
269
277
|
:model-value="isMonorepo"
|
|
270
|
-
label="
|
|
271
|
-
description="
|
|
278
|
+
:label="t('github.addService.monorepoLabel')"
|
|
279
|
+
:description="t('github.addService.monorepoDescription')"
|
|
272
280
|
@update:model-value="toggleMonorepo"
|
|
273
281
|
/>
|
|
274
282
|
|
|
@@ -277,8 +285,7 @@ function done() {
|
|
|
277
285
|
class="rounded-md border border-slate-700/60 bg-slate-900/40 p-3"
|
|
278
286
|
>
|
|
279
287
|
<p class="mb-2 text-xs text-slate-400">
|
|
280
|
-
|
|
281
|
-
working on this service will run within that subdirectory.
|
|
288
|
+
{{ t('github.addService.monorepoBrowseHint') }}
|
|
282
289
|
</p>
|
|
283
290
|
<RepoTreeBrowser
|
|
284
291
|
v-model="selectedDirectory"
|
|
@@ -287,10 +294,10 @@ function done() {
|
|
|
287
294
|
/>
|
|
288
295
|
<p class="mt-2 truncate text-xs text-slate-400">
|
|
289
296
|
<template v-if="selectedDirectory">
|
|
290
|
-
|
|
297
|
+
{{ t('github.addService.serviceDirectory') }}
|
|
291
298
|
<code class="text-slate-200">{{ selectedDirectory }}</code>
|
|
292
299
|
</template>
|
|
293
|
-
<template v-else>
|
|
300
|
+
<template v-else>{{ t('github.addService.noDirectorySelected') }}</template>
|
|
294
301
|
</p>
|
|
295
302
|
</div>
|
|
296
303
|
</div>
|
|
@@ -304,7 +311,7 @@ function done() {
|
|
|
304
311
|
class="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-emerald-400"
|
|
305
312
|
>
|
|
306
313
|
<UIcon name="i-lucide-check" class="h-3.5 w-3.5" />
|
|
307
|
-
{{ configuredBlock.title }}
|
|
314
|
+
{{ t('github.addService.addedConfigure', { title: configuredBlock.title }) }}
|
|
308
315
|
</div>
|
|
309
316
|
<ServiceTestConfig
|
|
310
317
|
:block="configuredBlock"
|
|
@@ -321,10 +328,10 @@ function done() {
|
|
|
321
328
|
size="sm"
|
|
322
329
|
icon="i-lucide-shield-check"
|
|
323
330
|
trailing-icon="i-lucide-external-link"
|
|
324
|
-
title="
|
|
331
|
+
:title="t('github.addService.grantAccessTitle')"
|
|
325
332
|
@click="openManageInstall"
|
|
326
333
|
>
|
|
327
|
-
|
|
334
|
+
{{ t('github.addService.grantAccess') }}
|
|
328
335
|
</UButton>
|
|
329
336
|
<UButton
|
|
330
337
|
color="neutral"
|
|
@@ -334,13 +341,13 @@ function done() {
|
|
|
334
341
|
:loading="github.loadingAvailable"
|
|
335
342
|
@click="github.loadAvailableRepos()"
|
|
336
343
|
>
|
|
337
|
-
|
|
344
|
+
{{ t('github.addService.refreshList') }}
|
|
338
345
|
</UButton>
|
|
339
346
|
</div>
|
|
340
347
|
|
|
341
348
|
<div class="flex justify-end gap-2">
|
|
342
349
|
<UButton v-if="configuredBlock" color="neutral" variant="soft" size="sm" @click="done">
|
|
343
|
-
|
|
350
|
+
{{ t('github.addService.done') }}
|
|
344
351
|
</UButton>
|
|
345
352
|
<UButton
|
|
346
353
|
v-if="!configuredBlock || isMonorepo"
|
|
@@ -350,7 +357,11 @@ function done() {
|
|
|
350
357
|
:disabled="!canAdd"
|
|
351
358
|
@click="add"
|
|
352
359
|
>
|
|
353
|
-
{{
|
|
360
|
+
{{
|
|
361
|
+
configuredBlock && isMonorepo
|
|
362
|
+
? t('github.addService.addAnother')
|
|
363
|
+
: t('github.addService.add')
|
|
364
|
+
}}
|
|
354
365
|
</UButton>
|
|
355
366
|
</div>
|
|
356
367
|
</template>
|