@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.
@@ -6,20 +6,42 @@ let lastSyncedTenant: string | null = null
6
6
 
7
7
  const REDIRECT_KEY = 'aihub_redirect_after_login'
8
8
 
9
+ const normalize = (path: string) => (path.endsWith('/') ? path.slice(0, -1) : path)
10
+
11
+ const stripLocale = (path: string, localeCodes: string[]): string => {
12
+ const prefix = localeCodes.find(code => path === `/${code}` || path.startsWith(`/${code}/`))
13
+ return prefix ? path.slice(prefix.length + 1) : path
14
+ }
15
+
16
+ /**
17
+ * The whole login subtree is anonymous, not just the login page itself:
18
+ * per-tenant login links live at `/auth/login/<idp-alias>`. No
19
+ * authenticated-only page may ever be nested below that path.
20
+ *
21
+ * The locale prefix is optional. This middleware runs before @nuxtjs/i18n's
22
+ * `locale-changing` middleware (Nuxt orders file-based global middleware ahead
23
+ * of plugin-registered ones), so bouncing a hand-distributed link that dropped
24
+ * `/en/` would strip the tenant before i18n ever restores the prefix.
25
+ */
26
+ const isAnonymousPath = (path: string, localeCodes: string[]): boolean => {
27
+ const unprefixedPath = stripLocale(normalize(path), localeCodes)
28
+
29
+ return ['/auth/login', '/auth/callback', '/auth/renew'].includes(unprefixedPath)
30
+ || unprefixedPath.startsWith('/auth/login/')
31
+ }
32
+
33
+ const rememberRedirect = (fullPath: string, isAuthPath: boolean) => {
34
+ if (import.meta.client && fullPath !== '/' && !isAuthPath) {
35
+ sessionStorage.setItem(REDIRECT_KEY, fullPath)
36
+ }
37
+ }
38
+
9
39
  export default defineNuxtRouteMiddleware(async (to) => {
10
40
  const { $auth, $i18n } = useNuxtApp()
11
41
  const locale = $i18n.locale.value
42
+ const localeCodes = $i18n.locales.value.map(entry => entry.code)
12
43
 
13
- const noAuthPaths = [
14
- `/${locale}/auth/login`,
15
- `/${locale}/auth/callback`,
16
- `/${locale}/auth/renew`,
17
- ]
18
-
19
- // No auth check for public paths (normalize trailing slashes on both sides)
20
- const normalize = (p: string) => (p.endsWith('/') ? p.slice(0, -1) : p)
21
- const normalizedPath = normalize(to.path)
22
- if (noAuthPaths.some(p => normalize(p) === normalizedPath)) {
44
+ if (isAnonymousPath(to.path, localeCodes)) {
23
45
  return
24
46
  }
25
47
 
@@ -29,9 +51,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
29
51
  const isAuthPath = to.path.includes('/auth/')
30
52
 
31
53
  if (!user) {
32
- if (import.meta.client && to.fullPath !== '/' && !isAuthPath) {
33
- sessionStorage.setItem(REDIRECT_KEY, to.fullPath)
34
- }
54
+ rememberRedirect(to.fullPath, isAuthPath)
35
55
  return navigateTo(`/${locale}/auth/login`)
36
56
  }
37
57
 
@@ -41,9 +61,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
41
61
  }
42
62
  catch {
43
63
  await $auth.removeUser()
44
- if (import.meta.client && to.fullPath !== '/' && !isAuthPath) {
45
- sessionStorage.setItem(REDIRECT_KEY, to.fullPath)
46
- }
64
+ rememberRedirect(to.fullPath, isAuthPath)
47
65
  return navigateTo(`/${locale}/auth/login`)
48
66
  }
49
67
  }
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.319.0",
6
+ "version": "0.320.0",
7
7
  "description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
8
8
  "main": "./nuxt.config.ts",
9
9
  "repository": {
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <AuthLoginPanel>
3
+ <template #heading>
4
+ <template v-if="provider">
5
+ {{ t('auth.login.welcomeProvider', { provider: provider.display_name }) }}
6
+ </template>
7
+ </template>
8
+ <template #message>
9
+ <template v-if="provider">
10
+ {{ t('auth.login.pleaseLoginWith', { provider: provider.display_name }) }}
11
+ </template>
12
+ </template>
13
+
14
+ <Button
15
+ v-if="provider"
16
+ :label="t('auth.login.loginWith', { provider: provider.display_name })"
17
+ :icon="`pi ${provider.icon}`"
18
+ icon-pos="right"
19
+ class="!bg-white !text-black"
20
+ @click="login(provider.alias)"
21
+ />
22
+ <ProgressSpinner
23
+ v-else
24
+ class="!h-8 !w-8"
25
+ />
26
+ </AuthLoginPanel>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ definePageMeta({
31
+ layout: 'anonymous',
32
+ })
33
+
34
+ const { t, locale } = useI18n()
35
+ const route = useRoute()
36
+ const { login } = useAuth()
37
+ const { authProviders, isLoading } = useAuthProviders()
38
+
39
+ /**
40
+ * The empty alias belongs to the synthetic "Keycloak" entry, which has no
41
+ * kc_idp_hint and must therefore not be addressable through a tenant link.
42
+ */
43
+ const requestedAlias = computed(() => String(route.params.idp ?? ''))
44
+ const provider = computed(() =>
45
+ requestedAlias.value
46
+ ? authProviders.value?.find(candidate => candidate.alias === requestedAlias.value)
47
+ : undefined,
48
+ )
49
+
50
+ // Only decide once the query settled — an unknown, disabled or hidden alias
51
+ // (and a failed provider request) falls back to the all-providers page.
52
+ watch([isLoading, provider], ([providersLoading, matchedProvider]) => {
53
+ if (!providersLoading && !matchedProvider) {
54
+ navigateTo(`/${locale.value}/auth/login`, { replace: true })
55
+ }
56
+ }, { immediate: true })
57
+ </script>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <AuthLoginPanel>
3
+ <template #heading>
4
+ {{ t('auth.login.welcome', { companyName }) }}
5
+ </template>
6
+ <template #message>
7
+ {{ t('auth.login.pleaseLogin') }}
8
+ </template>
9
+
10
+ <ProgressSpinner
11
+ v-if="isLoading"
12
+ class="!h-8 !w-8"
13
+ />
14
+ <template v-else>
15
+ <Button
16
+ v-for="idp in authProviders ?? []"
17
+ :key="idp.alias"
18
+ :label="t('auth.login.loginWith', { provider: idp.display_name })"
19
+ :icon="`pi ${idp.icon}`"
20
+ icon-pos="right"
21
+ class="!bg-white !text-black"
22
+ @click="login(idp.alias || undefined)"
23
+ />
24
+ <Button
25
+ v-if="(authProviders?.length ?? 0) === 0"
26
+ :label="t('auth.login.title')"
27
+ icon="pi pi-sign-in"
28
+ icon-pos="right"
29
+ class="!bg-white !text-black"
30
+ @click="login()"
31
+ />
32
+ </template>
33
+ </AuthLoginPanel>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ definePageMeta({
38
+ layout: 'anonymous',
39
+ })
40
+
41
+ const { t } = useI18n()
42
+ const { login } = useAuth()
43
+ const { authProviders, isLoading } = useAuthProviders()
44
+
45
+ const companyName = 'bbv Software Services AG'
46
+ </script>
@@ -197,9 +197,7 @@ export const createClient = (config: Config = {}): Client => {
197
197
  method,
198
198
  onRequest: undefined,
199
199
  serializedBody: getValidRequestBody(opts) as
200
- | BodyInit
201
- | null
202
- | undefined,
200
+ BodyInit | null | undefined,
203
201
  signal: unwrapRefs(opts.signal) as AbortSignal,
204
202
  url,
205
203
  });
@@ -33,14 +33,12 @@ export type QuerySerializer = (
33
33
 
34
34
  type WithRefs<TData> = {
35
35
  [K in keyof TData]: NonNullable<TData[K]> extends object
36
- ?
37
- | WithRefs<NonNullable<TData[K]>>
38
- | Ref<NonNullable<TData[K]>>
39
- | Extract<TData[K], null>
40
- :
41
- | NonNullable<TData[K]>
42
- | Ref<NonNullable<TData[K]>>
43
- | Extract<TData[K], null>;
36
+ ? | WithRefs<NonNullable<TData[K]>>
37
+ | Ref<NonNullable<TData[K]>>
38
+ | Extract<TData[K], null>
39
+ : | NonNullable<TData[K]>
40
+ | Ref<NonNullable<TData[K]>>
41
+ | Extract<TData[K], null>;
44
42
  };
45
43
 
46
44
  // copied from Nuxt
@@ -206,8 +204,4 @@ type FetchOptions<TData> = Omit<
206
204
  >;
207
205
 
208
206
  export type Composable =
209
- | "$fetch"
210
- | "useAsyncData"
211
- | "useFetch"
212
- | "useLazyAsyncData"
213
- | "useLazyFetch";
207
+ "$fetch" | "useAsyncData" | "useFetch" | "useLazyAsyncData" | "useLazyFetch";
@@ -195,8 +195,7 @@ export const setAuthParams = async ({
195
195
  options.query = {};
196
196
  }
197
197
  const queryValue = toValue(options.query) as
198
- | Record<string, unknown>
199
- | undefined;
198
+ Record<string, unknown> | undefined;
200
199
  if (queryValue) {
201
200
  queryValue[name] = token;
202
201
  }
@@ -4,12 +4,7 @@
4
4
  * JSON-friendly union that mirrors what Pinia Colada can hash.
5
5
  */
6
6
  export type JsonValue =
7
- | null
8
- | string
9
- | number
10
- | boolean
11
- | JsonValue[]
12
- | { [key: string]: JsonValue };
7
+ null | string | number | boolean | JsonValue[] | { [key: string]: JsonValue };
13
8
 
14
9
  /**
15
10
  * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
@@ -112,7 +112,9 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
112
112
  : false;
113
113
 
114
114
  export type OmitNever<T extends Record<string, unknown>> = {
115
- [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
116
- ? never
117
- : K]: T[K];
115
+ [
116
+ K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
117
+ ? never
118
+ : K
119
+ ]: T[K];
118
120
  };
@@ -50,6 +50,8 @@ export {
50
50
  getLitellmModel,
51
51
  getLitellmModels,
52
52
  getLitellmModelsByMode,
53
+ getLlmSpendByTenant,
54
+ getLlmSpendByUser,
53
55
  getLocale,
54
56
  getModels,
55
57
  getModelWithAssistants,
@@ -315,6 +317,8 @@ export {
315
317
  type CreateTranscriptionResponses,
316
318
  type CronInput,
317
319
  type CronInputWritable,
320
+ type CronStartEvent,
321
+ type CronStartEventWritable,
318
322
  type CustomOutput,
319
323
  type DashboardDto,
320
324
  type DashboardItemDto,
@@ -526,6 +530,16 @@ export {
526
530
  type GetLitellmModelsData,
527
531
  type GetLitellmModelsResponse,
528
532
  type GetLitellmModelsResponses,
533
+ type GetLlmSpendByTenantData,
534
+ type GetLlmSpendByTenantError,
535
+ type GetLlmSpendByTenantErrors,
536
+ type GetLlmSpendByTenantResponse,
537
+ type GetLlmSpendByTenantResponses,
538
+ type GetLlmSpendByUserData,
539
+ type GetLlmSpendByUserError,
540
+ type GetLlmSpendByUserErrors,
541
+ type GetLlmSpendByUserResponse,
542
+ type GetLlmSpendByUserResponses,
529
543
  type GetLocaleData,
530
544
  type GetLocaleResponse,
531
545
  type GetLocaleResponses,
@@ -733,6 +747,7 @@ export {
733
747
  type LlmCostEventWritable,
734
748
  type LlmEvent,
735
749
  type LlmEventWritable,
750
+ type LlmSpend,
736
751
  type LlmStopEvent,
737
752
  type LlmStopEventWritable,
738
753
  type LocaleInput,
@@ -741,8 +756,11 @@ export {
741
756
  type LocaleString,
742
757
  type Logprob,
743
758
  type MailAttachmentRef,
759
+ type MailBatchClassifiedEvent,
760
+ type MailBatchClassifiedEventWritable,
744
761
  type MailBatchDraftedEvent,
745
762
  type MailBatchDraftedEventWritable,
763
+ type MailClassificationRef,
746
764
  type MailFetchedEvent,
747
765
  type MailFetchedEventWritable,
748
766
  type MailMessageRef,
@@ -886,8 +904,6 @@ export {
886
904
  type RouterEventWritable,
887
905
  type RunStatistics,
888
906
  type RunStatisticsWritable,
889
- type ScheduledStartEvent,
890
- type ScheduledStartEventWritable,
891
907
  type SearchContextCostPerQueryDto,
892
908
  type SearchOrganizationMemoriesData,
893
909
  type SearchOrganizationMemoriesError,