@swiss-ai-hub/web 0.319.0 → 0.320.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.
@@ -21,36 +21,14 @@
21
21
  </a>
22
22
  <div class="flex flex-col gap-2 text-center">
23
23
  <h2 class="text-xl text-white">
24
- {{ t('auth.login.welcome', { companyName }) }}
24
+ <slot name="heading" />
25
25
  </h2>
26
26
  <p class="text-surface-400">
27
- {{ t('auth.login.pleaseLogin') }}
27
+ <slot name="message" />
28
28
  </p>
29
29
  </div>
30
30
  <div class="flex flex-col gap-3">
31
- <ProgressSpinner
32
- v-if="isLoading"
33
- class="!h-8 !w-8"
34
- />
35
- <template v-else>
36
- <Button
37
- v-for="idp in authProviders ?? []"
38
- :key="idp.alias"
39
- :label="t('auth.login.loginWith', { provider: idp.display_name })"
40
- :icon="`pi ${idp.icon}`"
41
- icon-pos="right"
42
- class="!bg-white !text-black"
43
- @click="login(idp.alias || undefined)"
44
- />
45
- <Button
46
- v-if="(authProviders?.length ?? 0) === 0"
47
- :label="t('auth.login.title')"
48
- icon="pi pi-sign-in"
49
- icon-pos="right"
50
- class="!bg-white !text-black"
51
- @click="login()"
52
- />
53
- </template>
31
+ <slot />
54
32
  </div>
55
33
  </div>
56
34
  <a
@@ -68,13 +46,5 @@
68
46
  <script setup lang="ts">
69
47
  import logo from '@core/assets/images/logo.png'
70
48
 
71
- definePageMeta({
72
- layout: 'anonymous',
73
- })
74
-
75
49
  const { t } = useI18n()
76
- const { login } = useAuth()
77
- const { authProviders, isLoading } = useAuthProviders()
78
-
79
- const companyName = 'bbv Software Services AG'
80
50
  </script>
@@ -0,0 +1,208 @@
1
+ <template>
2
+ <div class="flex flex-col gap-4">
3
+ <div class="grid grid-cols-2 gap-3 sm:grid-cols-5">
4
+ <div
5
+ v-for="position in POSITIONS"
6
+ :key="position.key"
7
+ >
8
+ <label
9
+ :for="`cron-${position.key}`"
10
+ class="mb-1 block text-sm font-medium"
11
+ >
12
+ {{ t(`lib.cron.${position.key}.label`) }}
13
+ </label>
14
+ <InputText
15
+ :id="`cron-${position.key}`"
16
+ :model-value="schedule[position.key]"
17
+ :aria-label="t(`lib.cron.${position.key}.label`)"
18
+ :invalid="Boolean(errors[position.key])"
19
+ :aria-invalid="Boolean(errors[position.key])"
20
+ class="w-full font-mono"
21
+ @update:model-value="(value) => updatePosition(position.key, value ?? '')"
22
+ />
23
+ <small
24
+ v-if="errors[position.key]"
25
+ class="mt-1 block text-red-500"
26
+ >
27
+ {{ errors[position.key] }}
28
+ </small>
29
+ <small
30
+ v-else
31
+ class="mt-1 block text-surface-500"
32
+ >{{ position.range }}</small>
33
+ </div>
34
+ </div>
35
+
36
+ <div>
37
+ <label
38
+ for="cron-timezone"
39
+ class="mb-1 block text-sm font-medium"
40
+ >
41
+ {{ t('lib.cron.timezone.label') }}
42
+ </label>
43
+ <Select
44
+ v-model="timezone"
45
+ :aria-label="t('lib.cron.timezone.label')"
46
+ input-id="cron-timezone"
47
+ :options="timezoneOptions"
48
+ :placeholder="timezonePlaceholder ?? t('lib.cron.timezone.placeholder')"
49
+ :filter="filter"
50
+ class="w-full"
51
+ />
52
+ <small class="mt-1 block text-surface-500">{{ t('lib.cron.timezone.help') }}</small>
53
+ </div>
54
+
55
+ <small class="text-surface-500">
56
+ {{ t('lib.cron.expression') }} <code class="font-mono">{{ expression }}</code>
57
+ </small>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ /**
63
+ * Editor for a backend `CronSchedule`: the five cron positions plus an IANA timezone.
64
+ *
65
+ * The emitted value is the schedule object in the backend's own field names, so it round-trips through
66
+ * `config_data` untouched — the scheduler reads those keys directly. Positions are kept separate rather than
67
+ * joined into one "0 12 * * *" string because that is how `CronSchedule` stores them, and flattening here would
68
+ * make every consumer parse it back apart.
69
+ *
70
+ * Deliberately has no presets and no plain-language summary — that is #1581, which extends this component rather
71
+ * than replacing it.
72
+ */
73
+
74
+ import { createMessage } from '@formkit/core'
75
+
76
+ import type { FormKitNode } from '@formkit/core'
77
+
78
+ interface CronSchedule {
79
+ minute: string
80
+ hour: string
81
+ day_of_month: string
82
+ month: string
83
+ day_of_week: string
84
+ timezone: string
85
+ }
86
+
87
+ type CronPosition = Exclude<keyof CronSchedule, 'timezone'>
88
+
89
+ interface CronInputProps {
90
+ context: {
91
+ node: FormKitNode
92
+ value?: CronSchedule | null
93
+ attrs: Record<string, unknown>
94
+ timezonePlaceholder?: string
95
+ filter?: boolean
96
+ }
97
+ }
98
+
99
+ const props = defineProps<CronInputProps>()
100
+ const { t } = useI18n()
101
+
102
+ const POSITIONS: { key: CronPosition, range: string, min: number, max: number }[] = [
103
+ { key: 'minute', range: '0-59', min: 0, max: 59 },
104
+ { key: 'hour', range: '0-23', min: 0, max: 23 },
105
+ { key: 'day_of_month', range: '1-31', min: 1, max: 31 },
106
+ { key: 'month', range: '1-12', min: 1, max: 12 },
107
+ { key: 'day_of_week', range: '0-6', min: 0, max: 6 },
108
+ ]
109
+
110
+ // Deliberately permissive: croniter accepts names (MON-FRI, JAN), steps, lists and the day-of-week specials, and a
111
+ // stricter pattern here would reject schedules the backend is happy to run. This catches the two things that are
112
+ // wrong in every cron dialect — a blank position and a stray character — and leaves grammar to the server, which
113
+ // now rejects a malformed cron on save rather than storing it.
114
+ const ALLOWED_CHARACTERS = /^[0-9A-Za-z*,\-/?#]+$/
115
+
116
+ // Blocking so the surrounding form cannot be submitted while a position is empty or malformed. Without it the only
117
+ // feedback is a 400 from the save endpoint, and before that endpoint validated, a blank position was stored and
118
+ // silently broke every run of the profile — not only its scheduled ones.
119
+ const ERROR_MESSAGE_KEY = 'cronPositions'
120
+
121
+ // Shown in the inputs the moment the field is enabled, never applied behind the user's back. `CronSchedule`
122
+ // requires all five positions and has no defaults, so leaving them blank would save a schedule the scheduler then
123
+ // skips with only a log line to say why. Hourly is the least surprising visible starting point — `* * * * *` would
124
+ // commit an admin to a run every minute for merely ticking the enable box.
125
+ const HOURLY: CronSchedule = {
126
+ minute: '0',
127
+ hour: '*',
128
+ day_of_month: '*',
129
+ month: '*',
130
+ day_of_week: '*',
131
+ timezone: resolveBrowserTimezone(),
132
+ }
133
+
134
+ const timezonePlaceholder = computed(() => props.context.timezonePlaceholder)
135
+ const filter = computed(() => props.context.filter ?? true)
136
+
137
+ const schedule = computed<CronSchedule>(() => props.context.value ?? HOURLY)
138
+
139
+ const errors = computed<Partial<Record<CronPosition, string>>>(() =>
140
+ Object.fromEntries(
141
+ POSITIONS.map(position => [position.key, positionError(position)]).filter(([, message]) => message),
142
+ ),
143
+ )
144
+
145
+ const expression = computed(() =>
146
+ POSITIONS.map(position => schedule.value[position.key] || '?').join(' '),
147
+ )
148
+
149
+ const timezone = computed({
150
+ get: () => schedule.value.timezone,
151
+ set: (value: string) => emitValue({ ...schedule.value, timezone: value }),
152
+ })
153
+
154
+ const timezoneOptions = computed<string[]>(() => {
155
+ // Not every engine implements supportedValuesOf; without it the admin can still keep the resolved zone.
156
+ const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf
157
+ return supported ? supported('timeZone') : [schedule.value.timezone, 'UTC']
158
+ })
159
+
160
+ function resolveBrowserTimezone(): string {
161
+ try {
162
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
163
+ }
164
+ catch {
165
+ return 'UTC'
166
+ }
167
+ }
168
+
169
+ function positionError(position: { key: CronPosition, min: number, max: number }): string | undefined {
170
+ const value = schedule.value[position.key]
171
+ if (!value) return t('lib.cron.errors.required')
172
+ if (!ALLOWED_CHARACTERS.test(value)) return t('lib.cron.errors.invalid')
173
+
174
+ // Only plain integers are range-checked. Anything with a step, range or name is left to the server, whose
175
+ // croniter knows the dialect far better than a regex here could.
176
+ const outOfRange = value
177
+ .split(',')
178
+ .some(term => /^\d+$/.test(term) && (Number(term) < position.min || Number(term) > position.max))
179
+
180
+ return outOfRange ? t('lib.cron.errors.range', { min: position.min, max: position.max }) : undefined
181
+ }
182
+
183
+ function updatePosition(position: CronPosition, value: string) {
184
+ emitValue({ ...schedule.value, [position]: value.trim() })
185
+ }
186
+
187
+ // Always emits every key: a partial schedule passes the generated submission schema (which cannot carry
188
+ // CronSchedule's croniter and timezone validators) and is then rejected by the save endpoint.
189
+ function emitValue(value: CronSchedule) {
190
+ props.context.node.input({ ...value })
191
+ }
192
+
193
+ watch(errors, (current) => {
194
+ props.context.node.store.remove(ERROR_MESSAGE_KEY)
195
+ const first = Object.values(current)[0]
196
+ if (!first) return
197
+
198
+ props.context.node.store.set(
199
+ createMessage({ blocking: true, key: ERROR_MESSAGE_KEY, type: 'error', value: first, visible: true }),
200
+ )
201
+ }, { immediate: true })
202
+
203
+ // Seed the stored value so enabling the field yields a complete, valid schedule even if the admin saves without
204
+ // touching an input — what the inputs show and what gets saved must not diverge.
205
+ onMounted(() => {
206
+ if (!props.context.value) emitValue(HOURLY)
207
+ })
208
+ </script>
@@ -140,7 +140,7 @@ const { tenantId } = useTenant()
140
140
  const { getDocumentSourceUrl } = useDocumentUrl()
141
141
  const { deleteDocument, isDeleting } = useDeleteDocument()
142
142
  const { deleteDocuments, isDeleting: isBatchDeleting } = useDeleteDocuments()
143
- const { isScheduled, schedule, unschedule } = useScheduledDeletions(
143
+ const { isScheduled, scheduledAt, schedule, unschedule } = useScheduledDeletions(
144
144
  () => route.params.db as string,
145
145
  () => route.params.namespace as string,
146
146
  )
@@ -163,18 +163,24 @@ const checkedDocuments = ref<DocumentDto[]>([])
163
163
 
164
164
  const formatted = (datestr: string) => useDateFormat(new Date(datestr), 'DD.MM.YYYY')
165
165
 
166
- const isDocumentDeleting = (document: DocumentDto) => isScheduled(document.id) && document.is_ingested
166
+ const isDocumentDeleting = (document: DocumentDto) => isScheduled(document.id)
167
167
 
168
- // Processing documents are mid-ingestion; removing their source now would race the pipeline.
169
- const isDocumentDeletable = (document: DocumentDto) => document.is_ingested && !isDocumentDeleting(document)
168
+ // Deleting is safe at any stage of ingestion: the API only removes the source file, and the pipeline
169
+ // drops the document's partition before the remove job touches any store, which aborts an in-flight
170
+ // run before it writes. So the only reason to withhold the action is a deletion already in flight.
171
+ const isDocumentDeletable = (document: DocumentDto) => !isDocumentDeleting(document)
170
172
 
171
- // Ids derive from the source URI, so re-uploading a just-deleted file reuses its id. When that id
172
- // reappears as a placeholder, the scheduled entry is stale and must be cleared.
173
+ // Ids derive from the source URI, so re-uploading a just-deleted file reuses its id and would inherit
174
+ // its "Deleting" badge. An upload rewrites the document, so an updated_at newer than the moment we
175
+ // scheduled the deletion means this row is the replacement, not the one still on its way out.
173
176
  watch(
174
177
  () => props.documents,
175
178
  (documents) => {
176
179
  const reUploadedIds = (documents ?? [])
177
- .filter(document => !document.is_ingested && isScheduled(document.id))
180
+ .filter((document) => {
181
+ const requestedAt = scheduledAt(document.id)
182
+ return requestedAt != null && new Date(document.updated_at).getTime() > requestedAt
183
+ })
178
184
  .map(document => document.id)
179
185
  if (reUploadedIds.length > 0) {
180
186
  unschedule(reUploadedIds)
@@ -200,15 +206,20 @@ const onSelectAllChange = ({ checked }: { checked: boolean }) => {
200
206
 
201
207
  const handleRowClick = (event: DataTableRowClickEvent) => {
202
208
  const document = event.data as DocumentDto
203
- if (document.is_ingested && !isDocumentDeleting(document)) {
209
+ if (!isDocumentDeleting(document)) {
204
210
  emit('selected', document)
205
211
  }
206
212
  }
207
213
 
214
+ // Only a deletion in flight makes a row inert. A document still being ingested stays dimmed but usable —
215
+ // otherwise a failed ingestion leaves a row that can be neither opened nor removed.
208
216
  const getRowClass = (data: DocumentDto) => {
209
- if (!data.is_ingested || isDocumentDeleting(data)) {
217
+ if (isDocumentDeleting(data)) {
210
218
  return 'opacity-50 cursor-not-allowed pointer-events-none'
211
219
  }
220
+ if (!data.is_ingested) {
221
+ return 'opacity-50'
222
+ }
212
223
  return data.id === route.params.document_id ? 'bg-surface-100 dark:bg-surface-800' : ''
213
224
  }
214
225
 
@@ -3,18 +3,29 @@ 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, which reports the channel tag (e.g.
7
- // `latest`) the running image was pulled as. When both match a single number is
6
+ // version is read from the health endpoint. When both agree a single number is
8
7
  // shown, otherwise both are shown as `UI / API`.
9
8
  //
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.
9
+ // A release is promoted by retagging the exact `-rc.N` build that was tested, so
10
+ // the baked UI version keeps its release-candidate suffix (e.g. `v0.317.0-rc.3`)
11
+ // even though the artifact IS the final release. A container cannot read its own
12
+ // image tag, so the release has to be inferred from what the API reports, and the
13
+ // two deployment flavours report it differently: docker-compose pins the channel
14
+ // tag (`latest`), while Helm pins an exact `vX.Y.Z` per instance and leaves the
15
+ // API on its package metadata (`0.319.0`). Both are release signals; on either
16
+ // one the suffix is dropped so a promoted build shows its release version.
17
+ //
18
+ // Versions are compared with the leading `v` normalised away, because one side is
19
+ // a git tag (`v0.319.0`) and the other Python package metadata (`0.319.0`) — the
20
+ // same version, and rendering it as `UI / API` would read as skew.
21
+ //
22
+ // Every other case is genuinely a candidate or a rolling build (another channel
23
+ // such as `staging`, an API pinned to an explicit `-rc.N`, a nightly), so the
24
+ // suffix stays to keep the build identifiable.
16
25
  const RELEASE_CHANNEL = 'latest'
17
- const toReleaseVersion = (version: string): string => version.replace(/-rc\.\d+$/, '')
26
+ const RC_SUFFIX = /-rc\.\d+$/
27
+ const toReleaseVersion = (version: string): string => version.replace(RC_SUFFIX, '')
28
+ const toComparable = (version: string): string => version.replace(/^v/, '')
18
29
 
19
30
  export const useAppVersion = defineQuery(() => {
20
31
  const bakedUiVersion = useRuntimeConfig().public.appVersion as string
@@ -28,13 +39,21 @@ export const useAppVersion = defineQuery(() => {
28
39
  },
29
40
  })
30
41
 
42
+ // An API version that still carries `-rc.N` can never equal the stripped UI
43
+ // version, so a candidate deployment fails this check without a separate guard.
44
+ const isReleaseDeployment = computed(() => {
45
+ const api = apiVersion.value
46
+ if (!api) return false
47
+ return api === RELEASE_CHANNEL || toComparable(api) === toComparable(toReleaseVersion(bakedUiVersion))
48
+ })
49
+
31
50
  const uiVersion = computed(() =>
32
- apiVersion.value === RELEASE_CHANNEL ? toReleaseVersion(bakedUiVersion) : bakedUiVersion,
51
+ isReleaseDeployment.value ? toReleaseVersion(bakedUiVersion) : bakedUiVersion,
33
52
  )
34
53
 
35
54
  const versionDisplay = computed(() => {
36
55
  const api = apiVersion.value
37
- if (!api || api === uiVersion.value) return uiVersion.value
56
+ if (!api || toComparable(api) === toComparable(uiVersion.value)) return uiVersion.value
38
57
  return `${uiVersion.value} / ${api}`
39
58
  })
40
59
 
@@ -2,11 +2,15 @@ import { useStorage } from '@vueuse/core'
2
2
 
3
3
  import type { MaybeRefOrGetter } from 'vue'
4
4
 
5
- // Deletion is eventual (the pipeline cleans the stores minutes after the API returns 202), so the
6
- // list keeps returning a deleted document for a while. We persist scheduled ids in localStorage —
7
- // surviving page refreshes — to drive a "Deleting" badge, and expire them after a TTL so a failed
8
- // cleanup eventually re-surfaces the row instead of hiding it forever.
9
- const TTL_MS = 30 * 60 * 1000
5
+ // Deletion is eventual (the pipeline cleans the stores well after the API returns), so the list keeps
6
+ // returning a deleted document for a while. We persist scheduled ids in localStorage — surviving page
7
+ // refreshes — to drive a "Deleting" badge, and expire them after a TTL so a failed cleanup eventually
8
+ // re-surfaces the row instead of hiding it forever.
9
+ //
10
+ // The TTL has to cover a Dagster backlog, not just a slow run: the cleanup waits on a NATS sensor tick,
11
+ // then the observe job's turn in the queue, then a run-status sensor tick, then the remove job's turn.
12
+ // A busy instance can push that out for hours, so a day is the honest bound.
13
+ const TTL_MS = 24 * 60 * 60 * 1000
10
14
 
11
15
  const store = useStorage<Record<string, number>>('aihub:scheduled-deletions', {})
12
16
 
@@ -15,9 +19,13 @@ function entryKey(database: string, namespace: string, documentId: string): stri
15
19
  }
16
20
 
17
21
  export function useScheduledDeletions(database: MaybeRefOrGetter<string>, namespace: MaybeRefOrGetter<string>) {
22
+ function scheduledAt(documentId: string): number | undefined {
23
+ const at = store.value[entryKey(toValue(database), toValue(namespace), documentId)]
24
+ return at != null && Date.now() - at < TTL_MS ? at : undefined
25
+ }
26
+
18
27
  function isScheduled(documentId: string): boolean {
19
- const scheduledAt = store.value[entryKey(toValue(database), toValue(namespace), documentId)]
20
- return scheduledAt != null && Date.now() - scheduledAt < TTL_MS
28
+ return scheduledAt(documentId) != null
21
29
  }
22
30
 
23
31
  function schedule(documentIds: string[]): void {
@@ -44,5 +52,5 @@ export function useScheduledDeletions(database: MaybeRefOrGetter<string>, namesp
44
52
  }
45
53
  }
46
54
 
47
- return { isScheduled, schedule, unschedule }
55
+ return { isScheduled, scheduledAt, schedule, unschedule }
48
56
  }
package/formkit.config.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import AgentSelector from '@core/components/FormKit/AgentSelector.vue'
2
2
  import ChipsInput from '@core/components/FormKit/ChipsInput.vue'
3
+ import CronInput from '@core/components/FormKit/CronInput.vue'
3
4
  import IconSelector from '@core/components/FormKit/IconSelector.vue'
4
5
  import KnowledgeDatabaseSelector from '@core/components/FormKit/KnowledgeDatabaseSelector.vue'
5
6
  import LocaleInput from '@core/components/FormKit/LocaleInput.vue'
@@ -11,7 +12,7 @@ import { createInput } from '@formkit/vue'
11
12
  import { primeInputs } from '@sfxcode/formkit-primevue'
12
13
 
13
14
  import type { FormKitNode } from '@formkit/core'
14
- import type { DefaultConfigOptions } from '@formkit/vue'
15
+ import type { DefaultConfigOptions, PluginConfigs } from '@formkit/vue'
15
16
 
16
17
  const LOCALES = ['de', 'en', 'fr', 'it'] as const
17
18
 
@@ -31,6 +32,21 @@ function localeRequired(node: FormKitNode): boolean {
31
32
  // create form: precisely the case this rule exists to catch.
32
33
  localeRequired.skipEmpty = false
33
34
 
35
+ /**
36
+ * Passes when the value is listed in the sibling field named by `address`, or when that list is
37
+ * empty — backend allow-lists treat empty as unrestricted. Reading the sibling through `node.at()`
38
+ * registers it as a validation dependency, so FormKit re-runs this rule when the list itself
39
+ * changes, not only when this field does.
40
+ *
41
+ * Advisory only: it exists so an admin sees the conflict in the form. The backend never depends on
42
+ * it — a value outside the allow-list is rejected where it is actually used.
43
+ */
44
+ const memberOf: PluginConfigs['rules'][string] = (node, address: string) => {
45
+ const allowed = node.at(address)?.value
46
+ if (!Array.isArray(allowed) || allowed.length === 0) return true
47
+ return allowed.includes(node.value)
48
+ }
49
+
34
50
  const localeRequiredMessages = {
35
51
  de: 'Mindestens eine Sprache muss ausgefüllt sein.',
36
52
  en: 'At least one language must be filled in.',
@@ -38,10 +54,22 @@ const localeRequiredMessages = {
38
54
  it: 'Almeno una lingua deve essere compilata.',
39
55
  }
40
56
 
57
+ const memberOfMessages = {
58
+ de: 'Dieser Wert steht nicht in der Liste der erlaubten Werte.',
59
+ en: 'This value is not in the allowed list.',
60
+ fr: 'Cette valeur ne figure pas dans la liste des valeurs autorisées.',
61
+ it: 'Questo valore non è presente nell\'elenco dei valori consentiti.',
62
+ }
63
+
41
64
  const config: DefaultConfigOptions = {
42
- rules: { localeRequired },
65
+ rules: { localeRequired, memberOf },
43
66
  messages: Object.fromEntries(
44
- LOCALES.map(locale => [locale, { validation: { localeRequired: localeRequiredMessages[locale] } }]),
67
+ LOCALES.map(locale => [locale, {
68
+ validation: {
69
+ localeRequired: localeRequiredMessages[locale],
70
+ memberOf: memberOfMessages[locale],
71
+ },
72
+ }]),
45
73
  ),
46
74
  inputs: {
47
75
  ...primeInputs,
@@ -51,6 +79,9 @@ const config: DefaultConfigOptions = {
51
79
  chipsInput: createInput(ChipsInput, {
52
80
  props: ['placeholder'],
53
81
  }),
82
+ cronInput: createInput(CronInput, {
83
+ props: ['timezonePlaceholder', 'filter'],
84
+ }),
54
85
  knowledgeDatabaseSelector: createInput(KnowledgeDatabaseSelector, {
55
86
  props: ['placeholder', 'filter'],
56
87
  }),
@@ -557,7 +557,9 @@ auth:
557
557
  description: Bitte melden Sie sich an, um auf die Anwendung zuzugreifen
558
558
  tagline: Erleben Sie die Zukunft der künstlichen Intelligenz
559
559
  welcome: Willkommen im AI Hub von {companyName}
560
+ welcomeProvider: Willkommen im AI Hub — Anmeldung mit {provider}
560
561
  pleaseLogin: Bitte melden Sie sich an
562
+ pleaseLoginWith: Bitte melden Sie sich mit {provider} an
561
563
  loginWith: Anmelden mit {provider}
562
564
  customSolutions: Massgeschneiderte KI-Lösungen für Ihr Unternehmen
563
565
  logoAlt: Logo des Unternehmens
@@ -795,6 +797,28 @@ form:
795
797
  nicht wie erwartet. Um unerwartetes Verhalten zu vermeiden, deaktivieren Sie
796
798
  sie bitte vorübergehend, bis eine Korrektur veröffentlicht wird.
797
799
  lib:
800
+ cron:
801
+ errors:
802
+ required: Jede Position ist erforderlich — eine leere ergibt keinen gültigen
803
+ Zeitplan.
804
+ invalid: Hier sind nur Ziffern und die Zeichen * , - / ? erlaubt.
805
+ range: Werte müssen zwischen {min} und {max} liegen.
806
+ expression: 'Resultierender Cron-Ausdruck:'
807
+ minute:
808
+ label: Minute
809
+ hour:
810
+ label: Stunde
811
+ day_of_month:
812
+ label: Tag des Monats
813
+ month:
814
+ label: Monat
815
+ day_of_week:
816
+ label: Wochentag
817
+ timezone:
818
+ label: Zeitzone
819
+ placeholder: Zeitzone auswählen
820
+ help: Die Cron-Positionen werden in dieser Zeitzone interpretiert, damit ein
821
+ täglicher Zeitplan über Zeitumstellungen hinweg dieselbe Uhrzeit behält.
798
822
  vectorStore:
799
823
  label: Vektorspeicher
800
824
  help: Wissensdatenbank und Namensräume für die Suche auswählen
@@ -548,7 +548,9 @@ auth:
548
548
  description: Please log in to access the application
549
549
  tagline: Experience the future of artificial intelligence
550
550
  welcome: Welcome to the AI Hub of {companyName}
551
+ welcomeProvider: Welcome to the AI Hub — sign in with {provider}
551
552
  pleaseLogin: Please log in
553
+ pleaseLoginWith: Please log in with {provider}
552
554
  loginWith: Login with {provider}
553
555
  customSolutions: Custom AI solutions for your company
554
556
  logoAlt: Logo of company
@@ -782,6 +784,27 @@ form:
782
784
  org_memory_performance: This feature may not work as expected in some cases. To
783
785
  avoid unexpected behavior, please temporarily disable it until a fix is released.
784
786
  lib:
787
+ cron:
788
+ errors:
789
+ required: Every position is required — a blank one is not a valid schedule.
790
+ invalid: Only digits and the characters * , - / ? are allowed here.
791
+ range: Values must be between {min} and {max}.
792
+ expression: 'Resulting cron expression:'
793
+ minute:
794
+ label: Minute
795
+ hour:
796
+ label: Hour
797
+ day_of_month:
798
+ label: Day of month
799
+ month:
800
+ label: Month
801
+ day_of_week:
802
+ label: Day of week
803
+ timezone:
804
+ label: Timezone
805
+ placeholder: Select a timezone
806
+ help: The cron positions are interpreted in this timezone, so a daily schedule
807
+ keeps the same wall-clock time across daylight-saving changes.
785
808
  vectorStore:
786
809
  label: Vector Store
787
810
  help: Select the knowledge database and namespaces to search
@@ -556,7 +556,9 @@ auth:
556
556
  description: Veuillez vous connecter pour accéder à l'application
557
557
  tagline: Découvrez l'avenir de l'intelligence artificielle
558
558
  welcome: Bienvenue sur le AI Hub de {companyName}
559
+ welcomeProvider: Bienvenue sur le AI Hub — connexion avec {provider}
559
560
  pleaseLogin: Veuillez vous connecter
561
+ pleaseLoginWith: Veuillez vous connecter avec {provider}
560
562
  loginWith: Se connecter avec {provider}
561
563
  customSolutions: Solutions d'IA personnalisées pour votre entreprise
562
564
  logoAlt: Logo de l'entreprise
@@ -795,6 +797,28 @@ form:
795
797
  dans certains cas. Pour éviter tout comportement inattendu, veuillez la désactiver
796
798
  temporairement jusqu'à la publication d'un correctif.
797
799
  lib:
800
+ cron:
801
+ errors:
802
+ required: Chaque position est requise — une position vide ne constitue pas une
803
+ planification valide.
804
+ invalid: Seuls les chiffres et les caractères * , - / ? sont autorisés ici.
805
+ range: Les valeurs doivent être comprises entre {min} et {max}.
806
+ expression: 'Expression cron résultante :'
807
+ minute:
808
+ label: Minute
809
+ hour:
810
+ label: Heure
811
+ day_of_month:
812
+ label: Jour du mois
813
+ month:
814
+ label: Mois
815
+ day_of_week:
816
+ label: Jour de la semaine
817
+ timezone:
818
+ label: Fuseau horaire
819
+ placeholder: Sélectionner un fuseau horaire
820
+ help: Les positions cron sont interprétées dans ce fuseau horaire, afin qu'une
821
+ planification quotidienne conserve la même heure lors des changements d'heure.
798
822
  vectorStore:
799
823
  label: Stockage vectoriel
800
824
  help: Sélectionner la base de connaissances et les espaces de noms à rechercher
@@ -554,7 +554,9 @@ auth:
554
554
  description: Effettua il login per accedere all'applicazione
555
555
  tagline: Sperimenta il futuro dell'intelligenza artificiale
556
556
  welcome: Benvenuti nell'AI Hub di {companyName}
557
+ welcomeProvider: Benvenuti nell'AI Hub — accesso con {provider}
557
558
  pleaseLogin: Effettua il login
559
+ pleaseLoginWith: Effettua il login con {provider}
558
560
  loginWith: Accedi con {provider}
559
561
  customSolutions: Soluzioni AI personalizzate per la tua azienda
560
562
  logoAlt: Logo dell'azienda
@@ -791,6 +793,28 @@ form:
791
793
  in alcuni casi. Per evitare comportamenti inattesi, disattivala temporaneamente
792
794
  fino al rilascio di una correzione.
793
795
  lib:
796
+ cron:
797
+ errors:
798
+ required: Ogni posizione è obbligatoria — una vuota non è una pianificazione
799
+ valida.
800
+ invalid: Qui sono ammessi solo cifre e i caratteri * , - / ? .
801
+ range: I valori devono essere compresi tra {min} e {max}.
802
+ expression: 'Espressione cron risultante:'
803
+ minute:
804
+ label: Minuto
805
+ hour:
806
+ label: Ora
807
+ day_of_month:
808
+ label: Giorno del mese
809
+ month:
810
+ label: Mese
811
+ day_of_week:
812
+ label: Giorno della settimana
813
+ timezone:
814
+ label: Fuso orario
815
+ placeholder: Seleziona un fuso orario
816
+ help: Le posizioni cron sono interpretate in questo fuso orario, così una pianificazione
817
+ giornaliera mantiene la stessa ora durante i cambi di orario.
794
818
  vectorStore:
795
819
  label: Archivio vettoriale
796
820
  help: Seleziona il database di conoscenza e gli spazi dei nomi da cercare