@cat-factory/app 0.7.4 → 0.9.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.
@@ -18,6 +18,7 @@ const board = useBoardStore()
18
18
  const documents = useDocumentsStore()
19
19
  const tasks = useTasksStore()
20
20
  const mergePresets = useMergePresetsStore()
21
+ const modelPresets = useModelPresetsStore()
21
22
  const pipelines = usePipelinesStore()
22
23
  const agentConfig = useAgentConfigStore()
23
24
  const toast = useToast()
@@ -94,6 +95,7 @@ const recurringFrameId = computed(() => {
94
95
  // Run configuration picked up front. Empty string = use the default (workspace
95
96
  // default merge preset / no pinned pipeline).
96
97
  const mergePresetId = ref('')
98
+ const modelPresetId = ref('')
97
99
  const pipelineId = ref('')
98
100
 
99
101
  const presetMenu = computed(() => [
@@ -121,6 +123,32 @@ const selectedPresetLabel = computed(() => {
121
123
  return mergePresets.presets.find((p) => p.id === mergePresetId.value)?.name ?? 'Workspace default'
122
124
  })
123
125
 
126
+ // Model preset: which model each agent runs on. Empty = workspace default preset.
127
+ const modelPresetMenu = computed(() => [
128
+ [
129
+ {
130
+ label: modelPresets.defaultPreset
131
+ ? `Default (${modelPresets.defaultPreset.name})`
132
+ : 'Workspace default',
133
+ icon: 'i-lucide-rotate-ccw',
134
+ onSelect: () => (modelPresetId.value = ''),
135
+ },
136
+ ...modelPresets.presets.map((p) => ({
137
+ label: p.name,
138
+ icon: 'i-lucide-cpu',
139
+ onSelect: () => (modelPresetId.value = p.id),
140
+ })),
141
+ ],
142
+ ])
143
+ const selectedModelPresetLabel = computed(() => {
144
+ if (!modelPresetId.value) {
145
+ return modelPresets.defaultPreset
146
+ ? `Default (${modelPresets.defaultPreset.name})`
147
+ : 'Workspace default'
148
+ }
149
+ return modelPresets.presets.find((p) => p.id === modelPresetId.value)?.name ?? 'Workspace default'
150
+ })
151
+
124
152
  const pipelineMenu = computed(() => [
125
153
  [
126
154
  {
@@ -172,6 +200,7 @@ watch(open, (isOpen) => {
172
200
  timeboxHours.value = undefined
173
201
  docKind.value = ''
174
202
  mergePresetId.value = ''
203
+ modelPresetId.value = ''
175
204
  pipelineId.value = ''
176
205
  agentConfigValues.value = {}
177
206
  pendingContext.value = []
@@ -208,6 +237,7 @@ async function add() {
208
237
  taskType: taskType.value as CreateTaskType,
209
238
  ...(typeFields ? { taskTypeFields: typeFields } : {}),
210
239
  ...(mergePresetId.value ? { mergePresetId: mergePresetId.value } : {}),
240
+ ...(modelPresetId.value ? { modelPresetId: modelPresetId.value } : {}),
211
241
  ...(pipelineId.value ? { pipelineId: pipelineId.value } : {}),
212
242
  ...(Object.keys(agentConfigValues.value).length
213
243
  ? { agentConfig: agentConfigValues.value }
@@ -381,6 +411,21 @@ async function add() {
381
411
  </UButton>
382
412
  </UDropdownMenu>
383
413
  </UFormField>
414
+
415
+ <UFormField label="Model preset">
416
+ <UDropdownMenu :items="modelPresetMenu" class="w-full">
417
+ <UButton
418
+ color="neutral"
419
+ variant="subtle"
420
+ size="sm"
421
+ icon="i-lucide-cpu"
422
+ trailing-icon="i-lucide-chevron-down"
423
+ class="w-full justify-between"
424
+ >
425
+ {{ selectedModelPresetLabel }}
426
+ </UButton>
427
+ </UDropdownMenu>
428
+ </UFormField>
384
429
  </div>
385
430
 
386
431
  <div v-if="configDescriptors.length" class="space-y-3">
@@ -1,8 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { useBoardFlow } from '~/composables/useBoardFlow'
3
3
  import NotificationsInbox from '~/components/layout/NotificationsInbox.vue'
4
- import VendorCredentialsModal from '~/components/providers/VendorCredentialsModal.vue'
5
- import PersonalCredentialModal from '~/components/providers/PersonalCredentialModal.vue'
6
4
 
7
5
  const ui = useUiStore()
8
6
  const board = useBoardStore()
@@ -127,20 +125,6 @@ const decisionItems = computed(() =>
127
125
  <!-- human-actionable notifications (merge review, pipeline complete, CI failed) -->
128
126
  <NotificationsInbox />
129
127
 
130
- <!-- LLM vendor subscriptions (Claude Code / Codex token pool) -->
131
- <UButton
132
- color="neutral"
133
- variant="ghost"
134
- size="sm"
135
- icon="i-lucide-key-round"
136
- title="Connect LLM vendor subscriptions (Claude Code / Codex)"
137
- @click="ui.openVendorCredentials()"
138
- >
139
- Vendors
140
- </UButton>
141
- <VendorCredentialsModal />
142
- <PersonalCredentialModal />
143
-
144
128
  <!-- spend safeguard usage -->
145
129
  <UButton
146
130
  v-if="showSpend"
@@ -184,12 +184,12 @@ const commands = computed<Command[]>(() => {
184
184
  run: () => ui.openWorkspaceSettings(),
185
185
  })
186
186
  list.push({
187
- id: 'model-defaults',
188
- label: 'Default models for agents',
187
+ id: 'model-configuration',
188
+ label: 'Model Configuration',
189
189
  group: 'Workspace',
190
190
  icon: 'i-lucide-cpu',
191
- keywords: 'model llm routing agent kind default',
192
- run: () => ui.openModelDefaults(),
191
+ keywords: 'model llm routing agent kind default preset configuration',
192
+ run: () => ui.openModelConfig(),
193
193
  })
194
194
  list.push({
195
195
  id: 'service-fragment-defaults',
@@ -291,9 +291,9 @@ watch(
291
291
  size="sm"
292
292
  icon="i-lucide-cpu"
293
293
  class="justify-start"
294
- @click="ui.openModelDefaults()"
294
+ @click="ui.openModelConfig()"
295
295
  >
296
- Default models
296
+ Model Configuration
297
297
  </UButton>
298
298
  <UButton
299
299
  block
@@ -306,6 +306,18 @@ watch(
306
306
  >
307
307
  Default service best practices
308
308
  </UButton>
309
+ <UButton
310
+ block
311
+ color="primary"
312
+ variant="soft"
313
+ size="sm"
314
+ icon="i-lucide-key-round"
315
+ class="justify-start"
316
+ title="Connect LLM vendor subscriptions + provider API keys"
317
+ @click="ui.openVendorCredentials()"
318
+ >
319
+ Vendors &amp; keys
320
+ </UButton>
309
321
  <UButton
310
322
  block
311
323
  color="primary"
@@ -317,6 +329,17 @@ watch(
317
329
  >
318
330
  My local runners
319
331
  </UButton>
332
+ <UButton
333
+ block
334
+ color="primary"
335
+ variant="soft"
336
+ size="sm"
337
+ icon="i-lucide-waypoints"
338
+ class="justify-start"
339
+ @click="ui.openOpenRouter()"
340
+ >
341
+ OpenRouter models
342
+ </UButton>
320
343
  </div>
321
344
  </section>
322
345
 
@@ -7,6 +7,7 @@ const props = defineProps<{ block: Block }>()
7
7
 
8
8
  const board = useBoardStore()
9
9
  const mergePresets = useMergePresetsStore()
10
+ const modelPresets = useModelPresetsStore()
10
11
  const pipelines = usePipelinesStore()
11
12
  const accounts = useAccountsStore()
12
13
  const tracker = useTrackerStore()
@@ -66,6 +67,32 @@ function setPreset(id: string) {
66
67
  board.updateBlock(props.block.id, { mergePresetId: id })
67
68
  }
68
69
 
70
+ // ---- model preset ----------------------------------------------------------
71
+ // Which model preset decides the model each agent step runs on. None selected → the
72
+ // workspace default preset. Changing it affects only steps that haven't started yet
73
+ // (a running step keeps the model it was dispatched with). A model pinned directly on
74
+ // the task still overrides the preset.
75
+ const selectedModelPreset = computed(() => modelPresets.resolve(props.block.modelPresetId))
76
+ const modelPresetMenu = computed(() => [
77
+ [
78
+ {
79
+ label: modelPresets.defaultPreset
80
+ ? `Default (${modelPresets.defaultPreset.name})`
81
+ : 'Workspace default',
82
+ icon: 'i-lucide-rotate-ccw',
83
+ onSelect: () => setModelPreset(''),
84
+ },
85
+ ...modelPresets.presets.map((p) => ({
86
+ label: p.name,
87
+ icon: 'i-lucide-cpu',
88
+ onSelect: () => setModelPreset(p.id),
89
+ })),
90
+ ],
91
+ ])
92
+ function setModelPreset(id: string) {
93
+ board.updateBlock(props.block.id, { modelPresetId: id })
94
+ }
95
+
69
96
  // ---- pipeline --------------------------------------------------------------
70
97
  // The pipeline this task's Run controls default to. None selected → the user picks
71
98
  // at run time (the board falls back to the first defined pipeline).
@@ -189,6 +216,38 @@ const resolveOnMergeLabel = computed(() =>
189
216
  </div>
190
217
  </div>
191
218
 
219
+ <!-- model preset -->
220
+ <div>
221
+ <div class="mb-1 flex items-center justify-between">
222
+ <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
223
+ Model preset
224
+ </span>
225
+ <UDropdownMenu :items="modelPresetMenu">
226
+ <UButton
227
+ size="xs"
228
+ variant="ghost"
229
+ color="neutral"
230
+ icon="i-lucide-cpu"
231
+ trailing-icon="i-lucide-chevron-down"
232
+ />
233
+ </UDropdownMenu>
234
+ </div>
235
+ <div v-if="selectedModelPreset" class="text-[11px] text-slate-400">
236
+ <span class="text-slate-300">{{ selectedModelPreset.name }}</span>
237
+ — base {{ selectedModelPreset.baseModelId
238
+ }}<span v-if="Object.keys(selectedModelPreset.overrides).length">
239
+ , {{ Object.keys(selectedModelPreset.overrides).length }} override(s)</span
240
+ >.
241
+ <span v-if="!block.modelPresetId" class="text-slate-500">(workspace default)</span>
242
+ </div>
243
+ <div v-else class="text-[11px] text-slate-500">
244
+ No preset configured — agents run on the deployment's default routing.
245
+ </div>
246
+ <p class="mt-1 text-[11px] text-slate-500">
247
+ Changing this affects only steps that haven't started yet.
248
+ </p>
249
+ </div>
250
+
192
251
  <!-- issue-tracker writeback overrides -->
193
252
  <div>
194
253
  <div class="mb-1 flex items-center justify-between">
@@ -234,8 +234,8 @@ async function clone(p: Pipeline) {
234
234
  variant="soft"
235
235
  size="xs"
236
236
  icon="i-lucide-cpu"
237
- title="Pick which model each agent kind runs on"
238
- @click="ui.openModelDefaults()"
237
+ title="Manage model presets (which model each agent runs on)"
238
+ @click="ui.openModelConfig()"
239
239
  >
240
240
  Configure models
241
241
  </UButton>