@swiss-ai-hub/web 0.317.0 → 0.318.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.
@@ -6,9 +6,7 @@
6
6
  >
7
7
  <div class="flex items-center justify-between gap-4">
8
8
  <div class="flex items-center justify-start gap-2">
9
- <div
10
- class="flex items-center justify-center rounded-full bg-white p-3 dark:bg-surface-900"
11
- >
9
+ <div class="flex items-center justify-center rounded-full bg-white p-3 dark:bg-surface-900">
12
10
  <Icon
13
11
  :name="agent.agent_config.icon"
14
12
  size="1.5em"
@@ -45,7 +43,7 @@
45
43
  />
46
44
  <Button
47
45
  v-tooltip.top="t('agent.export.button')"
48
- icon="pi pi-download"
46
+ icon="pi pi-file-export"
49
47
  severity="secondary"
50
48
  text
51
49
  rounded
@@ -54,6 +54,13 @@ import type { FormKitSchemaDefinition } from '@formkit/core'
54
54
 
55
55
  const { t } = useI18n()
56
56
 
57
+ // Fields with a known platform issue, keyed by the backend element id. Frontend-only on
58
+ // purpose: the notice is temporary and carries no config semantics, so it stays out of the
59
+ // agent's form schema. Drop the entry once the underlying issue is fixed.
60
+ const FIELD_WARNING_KEYS: Record<string, string> = {
61
+ org_memory: 'form.warnings.org_memory_performance',
62
+ }
63
+
57
64
  const props = defineProps<{
58
65
  form: FormkitElement[]
59
66
  initialData?: Record<string, unknown>
@@ -98,9 +105,15 @@ function replaceLabelVariables(label: string): string {
98
105
  })
99
106
  }
100
107
 
108
+ function fieldWarning(element: FormElement): string | undefined {
109
+ const warningKey = FIELD_WARNING_KEYS[element.id as string]
110
+ return warningKey ? t(warningKey) : undefined
111
+ }
112
+
101
113
  const schema = computed<FormKitSchemaDefinition>(() => {
102
114
  return buildFormKitSchema(props.form as FormElement[], {
103
115
  labelTransform: replaceLabelVariables,
116
+ fieldWarning,
104
117
  })
105
118
  })
106
119
 
@@ -138,6 +151,10 @@ async function submitHandler() {
138
151
  @apply pt-3 pb-1;
139
152
  }
140
153
 
154
+ .content :deep(.formkit-field-warning) {
155
+ @apply flex items-start gap-1.5 pb-1 text-xs text-amber-600 dark:text-amber-400;
156
+ }
157
+
141
158
  .content :deep(.formkit-group-fieldset) {
142
159
  @apply border border-surface-300 dark:border-surface-600 rounded-lg p-4 mb-4;
143
160
  }
@@ -36,11 +36,11 @@ const props = defineProps<TenantSelectProps>()
36
36
  const { t } = useI18n()
37
37
 
38
38
  const { tenants, tenantsAreLoading } = useTenantMemberships()
39
- const { activeTenant, activeTenantIsLoading } = useActiveTenantQuery()
39
+ const { tenantId } = useTenant()
40
40
 
41
41
  const placeholder = computed(() => props.context.placeholder)
42
42
  const filter = computed(() => props.context.filter ?? true)
43
- const isLoading = computed(() => tenantsAreLoading.value || activeTenantIsLoading.value)
43
+ const isLoading = computed(() => tenantsAreLoading.value)
44
44
 
45
45
  const selectedTenant = computed({
46
46
  get: () => props.context.value ?? null,
@@ -52,10 +52,10 @@ const selectedTenant = computed({
52
52
  // Pre-select the user's active tenant on a fresh field, while leaving an
53
53
  // already-configured value (editing an existing instance) untouched.
54
54
  watch(
55
- [activeTenant, () => props.context.value],
55
+ [tenantId, () => props.context.value],
56
56
  ([active, current]) => {
57
- if (!current && active?.id) {
58
- props.context.node.input(active.id)
57
+ if (!current && active) {
58
+ props.context.node.input(active)
59
59
  }
60
60
  },
61
61
  { immediate: true },
@@ -18,6 +18,7 @@
18
18
  <DataTable
19
19
  v-model:selection="checkedDocuments"
20
20
  :value="documents"
21
+ data-key="id"
21
22
  table-style="min-width: 50rem"
22
23
  :row-class="getRowClass"
23
24
  :sort-field="sortField ?? undefined"
@@ -185,11 +186,16 @@ watch(
185
186
  const deletableDocuments = computed(() => props.documents.filter(isDocumentDeletable))
186
187
 
187
188
  const allDeletableSelected = computed(
188
- () => deletableDocuments.value.length > 0 && checkedDocuments.value.length === deletableDocuments.value.length,
189
+ () => deletableDocuments.value.length > 0
190
+ && deletableDocuments.value.every(document => checkedDocuments.value.some(selected => selected.id === document.id)),
189
191
  )
190
192
 
191
193
  const onSelectAllChange = ({ checked }: { checked: boolean }) => {
192
- checkedDocuments.value = checked ? [...deletableDocuments.value] : []
194
+ const pageIds = new Set(deletableDocuments.value.map(document => document.id))
195
+ const selectionFromOtherPages = checkedDocuments.value.filter(selected => !pageIds.has(selected.id))
196
+ checkedDocuments.value = checked
197
+ ? [...selectionFromOtherPages, ...deletableDocuments.value]
198
+ : selectionFromOtherPages
193
199
  }
194
200
 
195
201
  const handleRowClick = (event: DataTableRowClickEvent) => {
@@ -3,10 +3,21 @@ import { minutesToMilliseconds } from 'date-fns'
3
3
 
4
4
  // Surfaces the running version of both services. The UI service version is baked
5
5
  // into the static bundle at build time (runtimeConfig.public.appVersion); the API
6
- // version is read from the health endpoint. When both match a single number is
6
+ // version is read from the health endpoint, which reports the channel tag (e.g.
7
+ // `latest`) the running image was pulled as. When both match a single number is
7
8
  // shown, otherwise both are shown as `UI / API`.
9
+ //
10
+ // A release is promoted onto the `latest` channel by retagging the exact `-rc.N`
11
+ // build that was tested, so the baked version keeps its release-candidate suffix
12
+ // (e.g. `v0.317.0-rc.3`) even though the artifact IS the final release. Strip that
13
+ // suffix only on the `latest` channel, so a promoted build shows its release
14
+ // version (`v0.317.0`). On any other channel the build is genuinely a candidate or
15
+ // rolling build, so keep the suffix to stay identifiable.
16
+ const RELEASE_CHANNEL = 'latest'
17
+ const toReleaseVersion = (version: string): string => version.replace(/-rc\.\d+$/, '')
18
+
8
19
  export const useAppVersion = defineQuery(() => {
9
- const uiVersion = useRuntimeConfig().public.appVersion as string
20
+ const bakedUiVersion = useRuntimeConfig().public.appVersion as string
10
21
 
11
22
  const { data: apiVersion } = useQuery<string>({
12
23
  key: () => ['app-version', 'api'],
@@ -17,10 +28,14 @@ export const useAppVersion = defineQuery(() => {
17
28
  },
18
29
  })
19
30
 
31
+ const uiVersion = computed(() =>
32
+ apiVersion.value === RELEASE_CHANNEL ? toReleaseVersion(bakedUiVersion) : bakedUiVersion,
33
+ )
34
+
20
35
  const versionDisplay = computed(() => {
21
36
  const api = apiVersion.value
22
- if (!api || api === uiVersion) return uiVersion
23
- return `${uiVersion} / ${api}`
37
+ if (!api || api === uiVersion.value) return uiVersion.value
38
+ return `${uiVersion.value} / ${api}`
24
39
  })
25
40
 
26
41
  return { uiVersion, apiVersion, versionDisplay }
@@ -132,6 +132,43 @@ export interface TransformOptions {
132
132
  locale?: string
133
133
  labelTransform?: (label: string) => string
134
134
  optionsResolver?: (element: FormElement) => unknown[] | undefined
135
+ // Resolves a frontend-only warning for an element. The backend form schema carries no
136
+ // warning of its own, so notices about known platform issues are attached here.
137
+ fieldWarning?: (element: FormElement) => string | undefined
138
+ }
139
+
140
+ /**
141
+ * Warning notice rendered next to a field, styled like a `Message severity="warn"
142
+ * variant="simple"` (see `.formkit-field-warning` in FormKit/DynamicConfiguration.vue).
143
+ * A plain `$el` node rather than `$cmp: 'Message'`: PrimeVue components are auto-imported
144
+ * per component, so FormKitSchema cannot resolve them by name.
145
+ */
146
+ function buildFieldWarningNode(element: FormElement, text: string): FormKitSchemaNode {
147
+ const base = (element.id as string | undefined) ?? (element.name as string)
148
+ return {
149
+ $el: 'div',
150
+ key: `${base}__warning`,
151
+ attrs: { class: 'formkit-field-warning' },
152
+ children: [
153
+ { $el: 'i', attrs: { class: 'pi pi-exclamation-triangle' } },
154
+ { $el: 'span', children: text },
155
+ ],
156
+ } as unknown as FormKitSchemaNode
157
+ }
158
+
159
+ /**
160
+ * Places the warning directly under the "Enable X" toggle for a nullable element — the
161
+ * toggle stays mounted when the section is switched off, so the warning remains readable
162
+ * before the user opts in. Non-nullable elements get it above their own node.
163
+ */
164
+ function withFieldWarning(
165
+ nodes: FormKitSchemaNode | FormKitSchemaNode[],
166
+ warning: FormKitSchemaNode | undefined,
167
+ afterToggle: boolean,
168
+ ): FormKitSchemaNode | FormKitSchemaNode[] {
169
+ if (!warning) return nodes
170
+ const nodeArray = Array.isArray(nodes) ? nodes : [nodes]
171
+ return afterToggle ? [nodeArray[0], warning, ...nodeArray.slice(1)] : [warning, ...nodeArray]
135
172
  }
136
173
 
137
174
  function createGroupNode(
@@ -311,7 +348,11 @@ function combineConditions(toggleCondition: string, existing: string | undefined
311
348
  return `$: ${toggleCondition.slice(1)} && (${existing.slice(1)})`
312
349
  }
313
350
 
314
- function buildNullableToggleNode(element: FormElement, label: string | undefined): Record<string, unknown> {
351
+ function buildNullableToggleNode(
352
+ element: FormElement,
353
+ label: string | undefined,
354
+ help: string | undefined,
355
+ ): Record<string, unknown> {
315
356
  const fieldName = element.name as string
316
357
  const toggleId = nullableToggleId(element)
317
358
  return {
@@ -320,17 +361,24 @@ function buildNullableToggleNode(element: FormElement, label: string | undefined
320
361
  id: toggleId,
321
362
  key: toggleId,
322
363
  label: label ? `Enable ${label}` : 'Enable',
364
+ ...(help ? { help } : {}),
323
365
  binary: true,
324
366
  }
325
367
  }
326
368
 
369
+ /**
370
+ * `help` is only supplied for groups: a group's own node renders no help, so the toggle is the
371
+ * only place to hang it. Nullable leaves already render their help on the input itself — passing
372
+ * it here too would print the same sentence twice.
373
+ */
327
374
  function applyNullableToggle(
328
375
  element: FormElement,
329
376
  baseNode: FormKitSchemaNode | FormKitSchemaNode[],
330
377
  label: string | undefined,
378
+ help?: string,
331
379
  ): FormKitSchemaNode[] {
332
380
  const nodeArray = Array.isArray(baseNode) ? baseNode : [baseNode]
333
- return [buildNullableToggleNode(element, label) as FormKitSchemaNode, ...nodeArray]
381
+ return [buildNullableToggleNode(element, label, help) as FormKitSchemaNode, ...nodeArray]
334
382
  }
335
383
 
336
384
  function gateElement(element: FormElement, toggleCondition: string): FormElement {
@@ -351,7 +399,7 @@ export function transformElementToSchema(
351
399
  const formkitType = getFormkitType(element)
352
400
  if (formkitType === 'repeater') return []
353
401
 
354
- const { locale = 'en', labelTransform, optionsResolver } = options
402
+ const { locale = 'en', labelTransform, optionsResolver, fieldWarning } = options
355
403
 
356
404
  const children = (element.children as FormElement[] || []).flatMap(
357
405
  child => transformElementToSchema(child, options),
@@ -365,20 +413,26 @@ export function transformElementToSchema(
365
413
  const isNullable = element.nullable === true
366
414
  const toggleCondition = isNullable ? `$get(${nullableToggleId(element)}).value` : undefined
367
415
 
416
+ const warningText = fieldWarning?.(element)
417
+ const warningNode = warningText ? buildFieldWarningNode(element, warningText) : undefined
418
+
368
419
  if (formkitType === 'group') {
369
420
  const gatedElement = isNullable ? gateElement(element, toggleCondition!) : element
370
421
  const groupNode = createGroupNode(gatedElement, children, label)
371
- return isNullable ? applyNullableToggle(element, groupNode, label) : groupNode
422
+ if (!isNullable) return withFieldWarning(groupNode, warningNode, false)
423
+ const toggledNodes = applyNullableToggle(element, groupNode, label, getLocalizedString(element.help, locale))
424
+ return withFieldWarning(toggledNodes, warningNode, true)
372
425
  }
373
426
 
374
427
  const cleanNode = buildNodeProperties(element, formkitType, label, locale, optionsResolver)
375
428
  if (children.length > 0) cleanNode.children = children
376
429
  if (isNullable) {
377
430
  cleanNode.if = combineConditions(toggleCondition!, element.if as string | undefined)
378
- return applyNullableToggle(element, cleanNode as FormKitSchemaNode, label)
431
+ const toggledNodes = applyNullableToggle(element, cleanNode as FormKitSchemaNode, label)
432
+ return withFieldWarning(toggledNodes, warningNode, true)
379
433
  }
380
434
 
381
- return cleanNode as FormKitSchemaNode
435
+ return withFieldWarning(cleanNode as FormKitSchemaNode, warningNode, false)
382
436
  }
383
437
 
384
438
  function buildLeafNodeForRepeater(
@@ -427,7 +481,8 @@ export function transformElementForRepeater(
427
481
  if (formkitType === 'group') {
428
482
  const gatedElement = isNullable ? gateElement(element, toggleCondition!) : element
429
483
  const groupNode = createGroupNode(gatedElement, children, label)
430
- return isNullable ? applyNullableToggle(element, groupNode, label) : groupNode
484
+ if (!isNullable) return groupNode
485
+ return applyNullableToggle(element, groupNode, label, getLocalizedString(element.help, locale))
431
486
  }
432
487
 
433
488
  const cleanNode = buildLeafNodeForRepeater(element, formkitType, label, locale, children)
@@ -790,6 +790,10 @@ process:
790
790
  form:
791
791
  locale_input:
792
792
  translate_tooltip: In alle Sprachen übersetzen
793
+ warnings:
794
+ org_memory_performance: Diese Funktion funktioniert in manchen Fällen möglicherweise
795
+ nicht wie erwartet. Um unerwartetes Verhalten zu vermeiden, deaktivieren Sie
796
+ sie bitte vorübergehend, bis eine Korrektur veröffentlicht wird.
793
797
  lib:
794
798
  vectorStore:
795
799
  label: Vektorspeicher
@@ -778,6 +778,9 @@ process:
778
778
  form:
779
779
  locale_input:
780
780
  translate_tooltip: Translate to all languages
781
+ warnings:
782
+ org_memory_performance: This feature may not work as expected in some cases. To
783
+ avoid unexpected behavior, please temporarily disable it until a fix is released.
781
784
  lib:
782
785
  vectorStore:
783
786
  label: Vector Store
@@ -790,6 +790,10 @@ process:
790
790
  form:
791
791
  locale_input:
792
792
  translate_tooltip: Traduire dans toutes les langues
793
+ warnings:
794
+ org_memory_performance: Cette fonctionnalité peut ne pas fonctionner comme prévu
795
+ dans certains cas. Pour éviter tout comportement inattendu, veuillez la désactiver
796
+ temporairement jusqu'à la publication d'un correctif.
793
797
  lib:
794
798
  vectorStore:
795
799
  label: Stockage vectoriel
@@ -786,6 +786,10 @@ process:
786
786
  form:
787
787
  locale_input:
788
788
  translate_tooltip: Traduci in tutte le lingue
789
+ warnings:
790
+ org_memory_performance: Questa funzionalità potrebbe non funzionare come previsto
791
+ in alcuni casi. Per evitare comportamenti inattesi, disattivala temporaneamente
792
+ fino al rilascio di una correzione.
789
793
  lib:
790
794
  vectorStore:
791
795
  label: Archivio vettoriale
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "license": "AGPL-3.0-or-later",
4
4
  "author": "bbv Software Services AG (https://www.bbv.ch)",
5
5
  "type": "module",
6
- "version": "0.317.0",
6
+ "version": "0.318.0",
7
7
  "description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
8
8
  "main": "./nuxt.config.ts",
9
9
  "repository": {
@@ -43,7 +43,6 @@
43
43
  "dependencies": {
44
44
  "@formkit/i18n": "^1.7.2",
45
45
  "@formkit/nuxt": "^2.0.0",
46
- "@hey-api/nuxt": "^0.2.1",
47
46
  "@nuxt/fonts": "^0.14.0",
48
47
  "@nuxt/icon": "^1.15.0",
49
48
  "@nuxtjs/i18n": "10.3.0",
@@ -11,7 +11,7 @@
11
11
  {{ t('agent.configuration.description') }}
12
12
  </p>
13
13
  <Button
14
- icon="pi pi-download"
14
+ icon="pi pi-file-export"
15
15
  severity="secondary"
16
16
  :label="t('agent.export.button')"
17
17
  class="shrink-0"
@@ -17,7 +17,7 @@
17
17
  />
18
18
  <div class="flex items-center gap-4">
19
19
  <Button
20
- icon="pi pi-upload"
20
+ icon="pi pi-file-import"
21
21
  severity="secondary"
22
22
  :label="t('agent.import.button')"
23
23
  @click="triggerImport"
@@ -8476,6 +8476,22 @@ export const GroupSchema = {
8476
8476
  title: "Label",
8477
8477
  description: "Optional label displayed above the group",
8478
8478
  },
8479
+ help: {
8480
+ anyOf: [
8481
+ {
8482
+ $ref: "#/components/schemas/LocaleString",
8483
+ },
8484
+ {
8485
+ type: "string",
8486
+ },
8487
+ {
8488
+ type: "null",
8489
+ },
8490
+ ],
8491
+ title: "Help",
8492
+ description:
8493
+ "Optional explanatory text rendered on the group's enable toggle",
8494
+ },
8479
8495
  children: {
8480
8496
  items: {
8481
8497
  oneOf: [
@@ -21211,10 +21227,9 @@ export const TextToSpeechRequestSchema = {
21211
21227
  properties: {
21212
21228
  model: {
21213
21229
  type: "string",
21214
- enum: ["tts-1", "tts-1-hd"],
21230
+ minLength: 1,
21215
21231
  title: "Model",
21216
- description:
21217
- "The TTS model to use. Available options: 'tts-1' or 'tts-1-hd'.",
21232
+ description: "The TTS model to use, e.g. 'speech/<model-name>'.",
21218
21233
  },
21219
21234
  input: {
21220
21235
  type: "string",
@@ -28058,6 +28073,22 @@ export const GroupWritableSchema = {
28058
28073
  title: "Label",
28059
28074
  description: "Optional label displayed above the group",
28060
28075
  },
28076
+ help: {
28077
+ anyOf: [
28078
+ {
28079
+ $ref: "#/components/schemas/LocaleString",
28080
+ },
28081
+ {
28082
+ type: "string",
28083
+ },
28084
+ {
28085
+ type: "null",
28086
+ },
28087
+ ],
28088
+ title: "Help",
28089
+ description:
28090
+ "Optional explanatory text rendered on the group's enable toggle",
28091
+ },
28061
28092
  children: {
28062
28093
  items: {
28063
28094
  oneOf: [
@@ -5917,6 +5917,12 @@ export type Group = {
5917
5917
  * Optional label displayed above the group
5918
5918
  */
5919
5919
  label?: LocaleString | string | null;
5920
+ /**
5921
+ * Help
5922
+ *
5923
+ * Optional explanatory text rendered on the group's enable toggle
5924
+ */
5925
+ help?: LocaleString | string | null;
5920
5926
  /**
5921
5927
  * Children
5922
5928
  *
@@ -14397,9 +14403,9 @@ export type TextToSpeechRequest = {
14397
14403
  /**
14398
14404
  * Model
14399
14405
  *
14400
- * The TTS model to use. Available options: 'tts-1' or 'tts-1-hd'.
14406
+ * The TTS model to use, e.g. 'speech/<model-name>'.
14401
14407
  */
14402
- model: "tts-1" | "tts-1-hd";
14408
+ model: string;
14403
14409
  /**
14404
14410
  * Input
14405
14411
  *
@@ -19069,6 +19075,12 @@ export type GroupWritable = {
19069
19075
  * Optional label displayed above the group
19070
19076
  */
19071
19077
  label?: LocaleString | string | null;
19078
+ /**
19079
+ * Help
19080
+ *
19081
+ * Optional explanatory text rendered on the group's enable toggle
19082
+ */
19083
+ help?: LocaleString | string | null;
19072
19084
  /**
19073
19085
  * Children
19074
19086
  *