npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swiss-ai-hub/web

v0.296.2

Published

Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)

Readme

@swiss-ai-hub/web

The admin and management UI for Swiss AI Hub, published as a Nuxt 3 layer.

npm License


What is Swiss AI Hub?

Swiss AI Hub is an open-source, self-hosted AI platform for enterprises. One docker compose up starts ~30 integrated containers: an LLM gateway (LiteLLM), vector search (Milvus), data pipelines (Dagster), document parsing (MinerU), SSO (Keycloak), observability (Langfuse + OpenTelemetry), a chat UI (Open-WebUI), and more. You build custom agents, pipelines, and processes using the Python SDK; the platform provides the runtime.

What is this package?

This package is the admin and management UI -- one component of the larger platform. It is the interface where administrators configure agents, manage knowledge bases, monitor processes, inspect threads, assign roles, track costs, and build dashboards. It is not the chat UI (that's Open-WebUI) and not the backend API.

The admin UI is built with Nuxt 3, Vue 3, PrimeVue, and Tailwind CSS. It is published as a Nuxt layer -- a mechanism that lets you inherit an entire Nuxt application (pages, components, composables, plugins, config) and extend or override any part of it in your own project.


Should you use this package?

Probably not. Most deployments should use the pre-built Docker image, which ships the admin UI ready to go:

# docker-compose.yml
services:
  admin-ui:
    image: ghcr.io/bbvch-ai/aihub-core/web:latest
    ports:
      - "3333:80"

The Docker image works out of the box with zero frontend code. Configuration (OIDC provider, API endpoint, WebSocket URL) is handled through environment variables at runtime.

Use this npm package only if you need to extend the UI with your own code -- adding custom pages, overriding components, modifying translations, or changing the theme. This is an SDK for building a custom frontend on top of Swiss Swiss AI Hub, not a standalone app.

When this package makes sense

| Use case | Example | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Custom pages | Add an organization-specific dashboard, a domain-specific tool, or internal admin views that don't belong in the open-source project | | Branding | Override the PrimeVue theme, replace the logo, adjust colors to match corporate identity | | Translation overrides | Fix or extend translations, add a fifth language, change terminology to match your domain | | Component overrides | Replace a built-in component with your own implementation | | Custom plugins | Add organization-specific Nuxt plugins (analytics, feature flags, custom error tracking) | | Custom auth flow | Extend the OIDC middleware for provider-specific requirements |


How Nuxt layers work

If you're not familiar with Nuxt layers, here's the idea: a layer is a full Nuxt application that another Nuxt project can inherit from using extends in nuxt.config.ts. When you extend this layer, you get all of its pages, components, composables, plugins, middleware, layouts, and configuration -- merged into your project automatically. Nuxt resolves conflicts by giving your project priority: if you define a component with the same name and path as one in the layer, yours wins. Same for pages, composables, and config keys.

This means you don't fork the repo or copy files. You install the package, extend it, and only write the code for what you want to change or add.

For a deeper understanding, see the official Nuxt layers documentation and the layer authoring guide.


Installation

npm install @swiss-ai-hub/web
# or
pnpm add @swiss-ai-hub/web

Install the required peer dependencies:

npm install [email protected] [email protected]

Required dependency overrides

Do not skip this step. Without it your project ends up with two copies of Vue in node_modules, and the UI will either fail to build or render with broken behavior.

Nuxt and several of its modules depend on their own (newer) copy of Vue, while this layer is built and tested against an exact Vue version. With nothing forcing them to agree, your install resolves two different Vue versions -- and therefore two copies of @vue/runtime-core, the package that owns Vue's runtime identity. Two Vue runtimes means two separate reactivity systems: provide/inject stops working across the boundary, component instance checks fail, and you get cryptic errors like "Vue instance ... was created in a different application". The same single-instance requirement applies to PrimeVue, whose theme system relies on a global singleton (two instances → unstyled components).

The fix is to force the whole dependency tree onto one Vue version using your package manager's override mechanism. Add the vue override to your project's package.json — this is the one that actually breaks if omitted. Note the key differs per package manager (npm/pnpm use overrides, Yarn uses resolutions):

// npm
{ "overrides": { "vue": "3.5.17" } }
// Yarn
{ "resolutions": { "vue": "3.5.17" } }
// pnpm
{ "pnpm": { "overrides": { "vue": "3.5.17" } } }

Then reinstall from a clean state so the lockfile is regenerated, and confirm a single Vue instance:

rm -rf node_modules package-lock.json   # or yarn.lock / pnpm-lock.yaml
npm install
npm ls @vue/runtime-core                # must print exactly one version: 3.5.17

The same single-instance requirement applies to PrimeVue (its theme system is a global singleton); pinning the primevue peer to one version is enough — it does not need an override.

Optional — silence a vue-router peer warning. @vueuse/router declares a vue-router@^4 peer, but some transitive dependencies may pull in vue-router 5.x, which can surface a peer-range warning on install. It is harmless (the layer does not depend on that resolution), but if you want it gone, pin vue-router to 4.6.4 scoped to @vueuse/router so it never affects the router Nuxt itself uses:

// npm:  "overrides":  { "@vueuse/router": { "vue-router": "4.6.4" } }
// Yarn: "resolutions": { "@vueuse/router/vue-router": "4.6.4" }
// pnpm: "pnpm": { "overrides": { "@vueuse/router>vue-router": "4.6.4" } }

Quick start

1. Create a Nuxt project

npx nuxi init my-aihub-frontend
cd my-aihub-frontend
npm install

Then install this package, its peer dependencies, and -- importantly -- add the required dependency overrides. Skipping the overrides leaves two copies of Vue in your tree and the app will not work.

2. Extend the layer

Replace the generated nuxt.config.ts with:

// nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@swiss-ai-hub/web'],

  // These dev defaults match infra/docker-compose.dev.yml + `make run-api`.
  // For production, leave these declared (the keys must exist) but blank, and
  // inject values at runtime via /config.js -- see Runtime configuration below.
  runtimeConfig: {
    public: {
      env: 'dev',
      oidc: {
        clientId: 'aihub-frontend',
        authorityUrl: 'http://localhost:8180/realms/aihub',
      },
      webui: {
        url: 'http://localhost:8080',
      },
      ws: {
        endpoint: 'ws://localhost:8000/api/v1/active/events/ws',
      },
    },
  },

  // Proxy API requests to the backend during development.
  // In production, your reverse proxy (Traefik) handles this.
  nitro: {
    devProxy: {
      '/api/v1': {
        target: 'http://localhost:8000/api/v1',
        changeOrigin: true,
        ws: true,
      },
    },
  },
})

3. Start the platform

Make sure the Swiss AI Hub backend is running (either via docker compose up or locally with make run-api).

4. Run

npx nuxi dev

Open http://localhost:3000. You get the full admin UI -- agents, processes, threads, knowledge bases, models, roles, dashboards, and chat -- running locally and pointing at your platform instance. Log in with your Keycloak credentials (default: admin / admin).


What the layer provides

Everything the admin UI needs ships inside this package:

| Category | What you get | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | Pages | Full file-based routing under /service/ -- agents, processes, threads, knowledge, models, roles, dashboards, chat, costs, evaluations | | Components | ~170 Vue components organized by domain (Agent, Chat, Dashboard, Event, Navigation, Process, Thread, Workflow, ...) | | Composables | Pinia-Colada query/mutation wrappers for every API resource | | SDK client | Auto-generated TypeScript API client (HeyAPI) | | Layouts | default (authenticated) and anonymous layouts | | Middleware | OIDC auth guard on all routes | | Plugins | OIDC client, config loader, ApexCharts | | i18n | German, English, French, Italian (lazy-loaded YAML files) | | Theme | PrimeVue Aura-based theme with dark mode support | | FormKit config | Custom form inputs (agent selector, model select, knowledge database selector, icon selector, locale input, vector store input) |


Extending the UI

Add a custom page

Create a Vue file in pages/ and Nuxt merges it with the layer's routes. The layer's components and composables are auto-imported and available in your pages without any explicit imports:

<!-- pages/service/my-tool.vue -->
<template>
  <StructuralScreen>
    <StructuralColumn title="My Custom Tool">
      <p>This page lives only in your deployment, not in the open-source project.</p>
      <p>All layer components and composables are available here.</p>
    </StructuralColumn>
  </StructuralScreen>
</template>

StructuralScreen is the full-height scrollable container used by every page. StructuralColumn provides a titled panel with built-in loading states -- pass :loading="true" to show a progress bar and suppress content until data is ready. These are the two layout primitives that all admin UI pages are built on.

To add navigation for your page, you can extend the sidebar by overriding the navigation component (see Override a component below).

Override translations

The layer ships i18n files for German, English, French, and Italian in i18n/locales/. To override specific keys or add a new language, create matching locale files in your project:

my-project/
  i18n/
    locales/
      en.yaml   # keys here override the layer's en.yaml
      de.yaml
      pt.yaml   # add a new language
# i18n/locales/en.yaml -- only the keys you want to change
agent:
  title: "AI Assistants"  # override "Agents" with your preferred term
my_tool:
  title: "My Custom Tool"  # add keys for your custom pages

You also need to register new languages in your nuxt.config.ts:

export default defineNuxtConfig({
  extends: ['@swiss-ai-hub/web'],

  i18n: {
    locales: [
      { code: 'pt', file: 'pt.yaml', name: 'Portugues' },
    ],
  },
})

Override the theme

The admin UI uses PrimeVue's styled mode with a customized Aura preset. Theming works through design tokens -- semantic color values that PrimeVue components reference. You override tokens to change the look of every component at once.

To create your own theme, start from the Aura preset and customize the tokens you want to change:

// themes/my-theme.ts
import { definePreset } from '@primeuix/themes'
import Aura from '@primeuix/themes/aura'

const MyPreset = definePreset(Aura, {
  semantic: {
    // Change the primary color palette (used for buttons, selections, highlights)
    primary: {
      50: '#eff6ff',
      100: '#dbeafe',
      200: '#bfdbfe',
      300: '#93c5fd',
      400: '#60a5fa',
      500: '#3b82f6',
      600: '#2563eb',
      700: '#1d4ed8',
      800: '#1e40af',
      900: '#1e3a8a',
      950: '#172554',
    },
    // Override light/dark mode colors
    colorScheme: {
      light: {
        primary: {
          color: '#2563eb',
          inverseColor: '#ffffff',
          hoverColor: '#1d4ed8',
          activeColor: '#1e40af',
        },
      },
      dark: {
        primary: {
          color: '#60a5fa',
          inverseColor: '#172554',
          hoverColor: '#93c5fd',
          activeColor: '#bfdbfe',
        },
      },
    },
  },
})

export default {
  preset: MyPreset,
  options: {
    darkModeSelector: '.dark',  // must stay '.dark' to match the Tailwind dark mode config
  },
}

Then point your nuxt.config.ts at it:

// nuxt.config.ts
import { fileURLToPath } from 'url'

export default defineNuxtConfig({
  extends: ['@swiss-ai-hub/web'],

  primevue: {
    importTheme: {
      from: fileURLToPath(new URL('./themes/my-theme.ts', import.meta.url)),
    },
  },
})

For the full list of design tokens you can customize, see the PrimeVue theming documentation and the Aura preset reference.

Override a component

Nuxt's layer system resolves components by name and directory path. If you place a component with the same name in the same directory structure, your version takes priority over the layer's:

my-project/
  components/
    Navigation/
      Logo.vue          # replaces the layer's Navigation/Logo.vue
    Agent/
      Card.vue          # replaces the layer's Agent/Card.vue

This works for any component in the layer. You can inspect the layer's component directory structure in the source repository to find the exact names and paths.


Building for production

npx nuxi generate

This produces a fully static SPA in .output/public/ (the layer is client-only -- ssr: false). Because the output is static, there is no Node server at runtime to read environment variables. Instead, the layer ships a small runtime-config mechanism (see Runtime configuration) so you build one image and configure it per environment at container start -- including which backend API it talks to.

Dockerfile example

This mirrors how Swiss AI Hub ships the admin UI: build the static site, serve it with nginx, and generate /config.js from a template at startup so the same image works in any environment.

# 1. Build the static SPA. ENV must be unset (or anything other than 'dev') so
#    the layer emits the <script src="/config.js"> tag that loads runtime config.
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npx nuxi generate

# 2. Serve with nginx; render /config.js from env vars on container start.
FROM nginx:alpine
RUN apk add --no-cache gettext   # provides envsubst
COPY --from=build /app/.output/public /usr/share/nginx/html
COPY config.template.js /usr/share/nginx/html/config.template.js
CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/config.template.js > /usr/share/nginx/html/config.js && nginx -g 'daemon off;'"]

nginx must serve /config.js uncached and never fall back to index.html for it -- declare it before the SPA catch-all:

location = /config.js {
  add_header Cache-Control "no-store" always;
  default_type application/javascript;
  try_files $uri =404;
}
location / { try_files $uri /index.html; }

Runtime configuration

The layer reads all runtime configuration from runtimeConfig.public, populated in two phases:

  1. Build-time defaults -- whatever you put in runtimeConfig.public in your nuxt.config.ts. Used directly in dev, baked into the static build.
  2. Runtime overrides (production) -- a window.__AIHUB_CONFIG__ object loaded synchronously from /config.js before the app boots. A plugin shipped in this layer (plugins/0.runtime-config.client.ts) maps it into runtimeConfig.public. This is how a single static build is configured per environment without rebuilding.

/config.js is rendered by envsubst from your config.template.js at container start (see the Dockerfile above). The layer injects the <script src="/config.js"> tag automatically for any non-dev build, and the mapping plugin runs in every app that extends the layer -- so you only supply the template:

// config.template.js -- envsubst replaces ${...} at container start.
// Include only the keys your deployment needs; unset ones resolve to defaults.
window.__AIHUB_CONFIG__ = {
  API_BASE_URL: '${API_BASE_URL}',
  OAUTH_CLIENT_ID: '${OAUTH_CLIENT_ID}',
  OAUTH_AUTHORITY_URL: '${OAUTH_AUTHORITY_URL}',
  WEBUI_URL: '${WEBUI_URL}',
  WS_ENDPOINT: '${WS_ENDPOINT}',
}

| runtimeConfig.public key | window.__AIHUB_CONFIG__ key | Default | Description | | -------------------------- | ----------------------------- | ----------------------- | ------------------------------------------------------------------ | | apiBaseUrl | API_BASE_URL | /api/v1 (same origin) | Backend API base URL -- see below | | oidc.clientId | OAUTH_CLIENT_ID | -- | OIDC client ID (Keycloak) | | oidc.authorityUrl | OAUTH_AUTHORITY_URL | -- | OIDC authority / realm URL | | webui.url | WEBUI_URL | -- | Open-WebUI URL (chat link) | | ws.endpoint | WS_ENDPOINT | -- | WebSocket endpoint for real-time agent events | | env | -- | -- | Set to dev locally (uses build-time values, skips /config.js) |

Declare the groups you want populated. The mapping plugin only writes into config groups your app already declared in runtimeConfig.public (e.g. oidc: {}, webui: {}, ws: {}); in production set their build-time values to empty strings and let /config.js fill them. apiBaseUrl is the exception -- it is always applied when API_BASE_URL is present.

Pointing at your backend API

The admin UI talks to the Swiss AI Hub backend through a single base URL, runtimeConfig.public.apiBaseUrl. It defaults to the same origin as the UI (/api/v1) -- the simplest setup: put the UI and the API behind one reverse proxy (what the platform's Traefik does) and no API configuration is needed at all.

If your UI is served from a different origin than the API, set the base URL explicitly:

  • Dev / build-time -- set it in nuxt.config.ts, or (recommended for local dev) keep /api/v1 and proxy it with Nitro:

    export default defineNuxtConfig({
      extends: ['@swiss-ai-hub/web'],
      // Option A: point straight at the API origin
      runtimeConfig: { public: { apiBaseUrl: 'https://aihub.example.com/api/v1' } },
      // Option B (same-origin dev): keep /api/v1 and proxy it
      nitro: { devProxy: { '/api/v1': { target: 'http://localhost:8000/api/v1', changeOrigin: true, ws: true } } },
    })
  • Production (static image) -- inject API_BASE_URL at container start via config.template.js. One image, any backend:

    docker run -e API_BASE_URL=https://aihub.example.com/api/v1 my-aihub-frontend

Cross-origin caveats. A different API origin must allow your UI's origin via CORS, and your Keycloak client must list it as an allowed web/redirect origin. Same-origin (/api/v1 behind one proxy) avoids both. Do not call client.setConfig({ baseURL: ... }) in your own app.vue unless you deliberately want to hard-override this mechanism.

Peer dependencies

| Package | Version | Why | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | | primevue | 4.5.5 | UI component library -- must be a single instance to avoid theme-singleton conflicts (see overrides) | | vue | 3.5.17 | Framework runtime -- must be a single instance; enforce with the required overrides |

These exact versions are what the layer is built and published against. They are declared as peerDependencies, so your project must provide them, and the overrides above ensure every transitive dependency resolves to the same single copy.

Tech stack

| Category | Technologies | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Framework | Nuxt 3, Vue 3 (Composition API), TypeScript | | UI | PrimeVue, Tailwind CSS, FormKit | | State | Pinia-Colada (query/mutation caching) | | Auth | oidc-client-ts (OpenID Connect) | | API | HeyAPI (auto-generated TypeScript SDK) | | Visualization | VueFlow (workflow graphs), ApexCharts (dashboards), Sigma.js (knowledge graphs) | | Utilities | VueUse, lodash-es, date-fns | | i18n | @nuxtjs/i18n (4 languages, lazy-loaded YAML) |

License

Copyright (C) 2024-2026 bbv Software Services AG.

AGPL-3.0-or-later — see packages/web/LICENSE. For the full per-package matrix (root Apache-2.0 and AGPL packages), see LICENSES.md.


Part of Swiss AI Hub. Built in Switzerland by bbv Software Services.