Introduction
Visiyon AI is a self-hosted AI assistant platform. It runs on your own hardware, keeps your data on your own infrastructure, and gives every user in your organization a private, branded chat interface backed by local language models.
What Visiyon AI is
Visiyon AI is a full-stack chat platform: a Next.js frontend, a Fastify API, a Postgres database with pgvector for retrieval, and Ollama for running open models. Everything ships as a single Docker Compose stack, fronted by Nginx.
It is built for teams who want the experience of a hosted assistant — chat history, document upload, voice, web search, tool calling, image generation — without sending data to a third-party API.
Core stack
| Layer | Technology |
|---|---|
| Frontend | Next.js 15 · React 19 · TypeScript · Tailwind |
| Backend | Fastify · TypeScript |
| Database | PostgreSQL + Prisma + pgvector |
| Cache / sessions | Redis |
| Models | Ollama (GLM-4, Granite, Llama, Qwen, Gemma, DeepSeek, Mistral, Phi, vision models) |
| Reverse proxy | Nginx |
| Orchestration | Docker Compose |
Requirements
Visiyon AI runs anywhere Docker runs, but it is built and tested against a specific baseline:
- OS: Ubuntu Server 22.04 (any Linux host with Docker also works)
- Docker: Docker Engine + the Compose plugin
- RAM: 8 GB+ recommended to run 8–9B parameter models on CPU
- GPU (optional but recommended): an NVIDIA GPU with
nvidia-container-toolkitinstalled makes inference, transcription, and image generation dramatically faster
None of the above is a hard requirement to boot the stack — it will run CPU-only — but model responses and TTS/STT will be noticeably slower without a GPU.
Installation
1. Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
2. Get the project onto your server
scp -r visiyon-ai your-user@your-server:/opt/visiyon-ai
ssh your-user@your-server
cd /opt/visiyon-ai
3. Configure environment
cp .env.example .env
nano .env # set POSTGRES_PASSWORD and JWT_SECRET to real random values
Generate a strong secret quickly with:
openssl rand -hex 32
4. Build and start everything
docker compose up -d --build
This starts, in dependency order: Postgres, Redis, Ollama, the backend (which pushes the Prisma schema to Postgres on boot), the frontend, and Nginx on port 80.
Check that everything is healthy:
docker compose ps
docker compose logs -f backend
5. Pull models
docker exec -it visiyon-ollama ollama pull glm4:9b
docker exec -it visiyon-ollama ollama pull granite4:8b
# add any others: llama3.1, qwen2.5, gemma2, deepseek-r1, mistral, phi3, llava (vision)
# Required for RAG (document upload / vector search):
docker exec -it visiyon-ollama ollama pull nomic-embed-text
Pulled models appear in the chat model dropdown automatically — no restart needed.
6. Open it
Visit your server's IP or domain. Register an account — the first account created becomes an admin automatically. The admin panel lives at /admin.
Configuration reference
All configuration lives in .env at the project root. Copy .env.example to .env and fill it in.
Required
| Variable | Purpose |
|---|---|
POSTGRES_PASSWORD | Database password — generate with openssl rand -hex 32 |
JWT_SECRET | Signs session tokens — generate the same way |
NEXT_PUBLIC_API_URL | Public URL the frontend uses to reach the API |
FRONTEND_URL | Public URL of the app itself |
Ollama
| Variable | Purpose |
|---|---|
OLLAMA_URL | Single Ollama instance, e.g. http://host.docker.internal:11434 |
OLLAMA_URLS | Optional comma-separated list for multi-GPU load balancing |
SUPPORT_MODEL | Pins which local model answers the built-in support chat; auto-picks otherwise |
SSO / OIDC
Leave these blank to configure SSO from Admin → Settings → Login providers instead (recommended — persists in the database, no redeploy needed):
| Variable | Purpose |
|---|---|
OIDC_ISSUER_URL | Provider's issuer URL |
OIDC_CLIENT_ID | App registration client ID |
OIDC_CLIENT_SECRET | App registration client secret |
OIDC_PROVIDER_NAME | Display name on the login button |
OIDC_REDIRECT_URI | Callback URL registered with the provider |
Other optional variables
| Variable | Purpose |
|---|---|
COMPOSE_GPU_OVERRIDE | GPU stats override for Admin → System |
ELEVENLABS_API_KEY | Only needed if ElevenLabs is the selected TTS provider |
MUSIC_GEN_URL / MUSIC_GEN_API_KEY / MUSIC_GEN_CALLBACK_URL | Optional music generation provider |
.env value. The admin panel is generally easier — changes apply without a redeploy.
Local development
You can run the backend and frontend outside Docker for faster iteration. You'll still need Postgres, Redis and Ollama reachable (locally or remote).
Backend
cd backend
npm install
npx prisma generate
DATABASE_URL=postgresql://visiyon:visiyon@localhost:5432/visiyon \
REDIS_URL=redis://localhost:6379 \
OLLAMA_URL=http://localhost:11434 \
JWT_SECRET=dev_secret \
npm run dev
Frontend
cd frontend
npm install
NEXT_PUBLIC_API_URL=http://localhost:4000 npm run dev
Project structure
visiyon-ai/
├── docker-compose.yml
├── .env.example
├── nginx/nginx.conf
├── backend/ # Fastify API: auth, chats, models, admin
│ ├── prisma/schema.prisma
│ └── src/
└── frontend/ # Next.js 15 App Router
├── app/ # landing page, /chat, /login, /register, /admin
├── components/
└── lib/ # API client + zustand chat store
Accounts & sessions
Visiyon AI uses JWT-based sessions. Anyone can register unless registration is restricted at the network level; the very first account ever created becomes an admin automatically — every account after that is a regular user until an admin promotes them.
- Session tokens expire after
JWT_EXPIRES_IN(default 7 days) - The frontend auto-logs-out and redirects to
/loginon a 401 - Login, register, and password-reset-request are rate-limited (5–10 attempts / 15 min) to slow down brute-forcing
Password reset
Password reset sends a real email via SMTP (nodemailer — works with any SMTP server, including free local ones like Mailpit). If SMTP isn't configured, the reset token is returned directly in the API response instead, so the flow still works end-to-end in local development.
API keys
Users can issue and revoke personal API keys from Settings. A vis_-prefixed key can be sent as a Bearer token on any route, exactly like a JWT.
Sign in with Microsoft (SSO / OIDC)
Visiyon AI supports an optional “Continue with <provider>” button against any standard OpenID Connect provider — Keycloak, Authentik, Auth0, Azure AD / Microsoft Entra ID, Google Workspace, Okta, and others. The button is automatically hidden when no provider is configured.
When the configured provider name starts with Microsoft or Azure, the login screen automatically renders a branded white “Continue with Microsoft” button with the four-color Microsoft logo. Any other provider name falls back to a plain outlined button.
Configuring Microsoft Entra ID
Fill these in from Admin → Settings → Login providers (recommended) or as OIDC_* environment variables:
Issuer URL: https://login.microsoftonline.com/<tenant-id>/v2.0
Client ID: <app registration client id>
Client secret: <app registration client secret>
Provider name: Microsoft
Redirect URI: https://<your-webui-url>/api/auth/sso/callback
- Create an app registration under Azure Portal → App registrations
- Add the redirect URI above as a Web platform callback
- Grant the
openid,email, andprofiledelegated permissions
Permission groups
Admins can create groups with an allowlist of Ollama models and assign users to them. A user with no group — or a group with an empty allowlist — keeps unrestricted access to every installed model.
Use this to, for example, give a finance team access only to a specific compliance-approved model, while engineering keeps access to everything.
Chatting
The core chat experience streams token-by-token over Server-Sent Events, proxied through Nginx with buffering disabled so responses appear live as the model generates them.
Stop / regenerate
Interrupt a reply mid-generation, or regenerate the last response with one click.
Multi-chat
Run several conversations in parallel, switch between them from the sidebar.
Search, rename, pin, delete
Standard chat management, all from the sidebar.
Auto-titling
New chats are titled automatically from their first exchange.
Model detection
Every model pulled into Ollama is detected automatically — GLM-4, Granite, Llama, Qwen, Gemma, DeepSeek, Mistral, Phi, and vision models — with no hardcoding required on the Visiyon side. Pull a model with ollama pull and it appears in the dropdown.
Rendering
Replies render Markdown, syntax-highlighted code blocks with a copy button, Mermaid diagrams, and LaTeX via KaTeX.
Folders & organization
Create, rename, and delete folders from the sidebar. Move any chat into or out of a folder from that chat's hover menu. Deleting a folder keeps its chats — they're simply unfiled, never deleted.
Prompt library
Save reusable system-prompt presets. Presets can be personal, or — for admins — shared with everyone on the platform. Apply a preset to a chat in one click; it persists on that chat going forward.
Playground
A standalone page for testing any installed model with free sliders for temperature, top-p, and context window — independent of chat history. Nothing typed or generated in the Playground is saved server-side.
Sharing a chat
Generate a public, read-only link for any chat at /share/:id. Anyone with the link can view the conversation without an account. Revoke the link at any time to cut off access immediately.
Document chat (RAG)
Attach a PDF, DOCX, TXT, MD, or CSV file to any chat and the model will answer using its contents, with clickable source citations.
How it works
- Click the paperclip icon in the message box and upload a document
- The backend extracts the text, splits it into roughly 1,200-character overlapping chunks, embeds each chunk with
nomic-embed-text, and stores the vectors in Postgres viapgvector - Status moves
PENDING → PROCESSING → READY(orFAILEDwith a reason) — the panel polls and updates live - Click Attach on a ready document to link it to the current chat
- From then on, every message in that chat first retrieves the 5 most relevant chunks (cosine similarity) and feeds them to the model as context automatically
Documents live in your personal library and can be attached to multiple chats. Deleting a document removes its chunks and detaches it everywhere it was attached.
nomic-embed-text embedding model pulled into Ollama — see Installation, step 5.
Web search
Toggle live web search per message, backed by a bundled SearXNG instance.
- Click the globe icon in the message box to enable web search for your next message
- On send, the backend queries SearXNG for your message text and pulls back the top 5 results
- Results are formatted as a numbered source list and injected as a system message ahead of the conversation — the model is asked to answer using them and cite
[n]where relevant
Web search is off by default and only applies per-message. If SearXNG is unreachable, the request still succeeds — it just skips the search context and answers from the model's own knowledge.
docker compose up before web search will return results. Its own web UI is reachable at /searxng/ if you want to tune engines directly — see searxng/settings.yml.
Speech in and out
Speak your message using self-hosted Whisper transcription, and hear replies read back with real-time streaming text-to-speech that starts talking while the reply is still generating — and can be interrupted mid-sentence (barge-in).
TTS engines
Pick the engine from Admin → Settings → Voice:
| Engine | Notes |
|---|---|
| Piper | Bundled, fastest, somewhat robotic |
| Kokoro | Bundled, natural, CPU-friendly — docker compose --profile tts-kokoro up -d |
| Coqui XTTS-v2 | Bundled, most natural local option, supports voice cloning, GPU recommended — docker compose --profile tts-coqui up -d |
| ElevenLabs | Cloud API, most human-sounding, requires ELEVENLABS_API_KEY |
Tools / function calling
Attach tools to a chat and the model can call them mid-conversation, in a multi-round loop — call a tool, get the result, answer — bounded to avoid infinite loops.
Built-in tools
calculator— powered by mathjscurrent_datetime
Custom HTTP tools
Admins can register additional HTTP tools against any external API, with parameter validation before the call goes out.
Tool calls and their results are persisted as TOOL messages and shown in the UI with a “Used <tool>” indicator.
Image generation
Optional, multi-provider, configured from Admin → Settings → Image generation. Off by default and hidden entirely until configured. Generated images drop into chat history like any other reply.
| Provider | Notes |
|---|---|
| Self-hosted | Runs AUTOMATIC1111/stable-diffusion-webui on your own GPU via the bundled sd-wrapper service — validated on 2x Tesla P100 |
| Custom | Points IMAGE_GEN_URL at anything speaking the OpenAI images API shape (LocalAI, ComfyUI's OpenAI-compatible wrapper, fal.ai) |
| OpenAI | Uses OpenAI's own API directly, with its own key field |
| Stability AI | Uses Stability AI's own API directly, with its own key field |
Running the self-hosted stack
docker compose --profile selfhosted-images up -d sd-webui sd-wrapper
First boot downloads a base Stable Diffusion checkpoint into the sd_models_data volume, which can take a while. Once sd-webui is healthy, set the provider to Self-hosted in the admin panel — the wrapper URL defaults to http://sd-wrapper:8000 in-cluster, nothing else to configure.
Optional env vars: SD_MODEL (force a specific checkpoint filename) and WRAPPER_API_KEY (require a bearer token on the wrapper's own endpoint — useful if you expose it beyond the internal network).
Pipelines (moderation)
Admin-defined keyword/regex rules that run on every message:
- PRE rules can block a message before it reaches the model
- POST rules flag the reply for review
No external moderation API is required — everything runs against your own rule set.
Admin dashboard
Reachable at /admin for any admin account. Covers:
- User list and role management
- Group management with per-group model access
- Server / Ollama / SearXNG health
- Model list, with pull/delete via the API
- Event log
Event log
A database-backed log of authentication failures, SSO errors, tool/document processing failures, and pipeline blocks or flags — filterable by level and source directly in the admin panel.
Analytics
A monitoring tab in the admin panel covering:
- Messages and token usage (prompt/completion, straight from Ollama) over a 7/30/90-day window
- A per-day usage chart
- A per-model breakdown, with an optional cost estimate — configure
MODEL_COST_PER_1Mto attach a reference $/1M-token figure. Self-hosted Ollama has no real per-token cost, so this is opt-in and for reference only - A per-user table: messages, tokens, and last active time
Built-in support chat
A “Need help?” widget in the bottom-right corner of any logged-in page, answering questions about how to use Visiyon itself — where a setting lives, what a feature does, why a model isn't showing up.
It runs on the deployment's own local Ollama — no external API, no extra cost — and picks whichever chat-capable model is already installed unless SUPPORT_MODEL is set.
backend/src/lib/support-knowledge.ts. Update it whenever you rename or add a major feature, or the widget's answers will drift out of date.
Updating
Manual (always works)
git pull # or copy new files over
docker compose up -d --build
From the admin panel
Admin → Updates can check for new releases and apply them with one click, so people running a fork of the platform don't need shell access to stay current.
- It compares the running
APP_VERSIONagainst the latest GitHub release of the repo set inUPDATE_REPO(e.g.UPDATE_REPO=yourname/your-forkin.env). Leave it unset and the Updates page just reports that updates aren't configured — nothing else changes - Applying an update is handled by a small
updatersidecar container that has the host's Docker socket and the repo checkout mounted in. It runsgit fetch+git reset --hard origin/<branch>+docker compose up -d --build, streaming progress back to the admin panel.UPDATE_BRANCHcontrols which branch it pulls (defaultmain) - Only that sidecar gets Docker/host access — the main backend never does. To disable self-service updates entirely, comment the
updaterservice out ofdocker-compose.ymland leaveUPDATE_REPOunset
Bump APP_VERSION in your environment each time you cut a new release, so the version shown in the sidebar and the update check stay accurate.
Enabling GPU stats
The Admin → System resources panel needs the GPU attached to the backend container specifically — by default it's only attached to Ollama/sd-webui. If the backend runs on the same GPU box:
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --build
Troubleshooting, in order
1. Confirm the host sees the GPU
nvidia-smi -L
If this fails, install/configure the NVIDIA driver on the host before anything below will work.
2. Confirm GPU passthrough into Docker works at all
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
If this fails, the host's NVIDIA Container Toolkit isn't wired up to the Docker daemon yet:
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
3. Confirm the GPU override actually reaches the backend service
docker compose -f docker-compose.yml -f docker-compose.gpu.yml config | grep -B2 -A8 "runtime\|reservations"
You should see both a runtime: nvidia line and a deploy.resources.reservations.devices block under backend. If you see neither, re-copy docker-compose.gpu.yml from the repo and retry.
4. "executable file not found in $PATH" inside the container
This is almost always the backend's base image, not the GPU passthrough itself. The NVIDIA Container Toolkit injects nvidia-smi and driver libraries in a way built against glibc — it does not reliably work on musl-based images like node:*-alpine. This project's backend/Dockerfile uses node:20-bookworm-slim (Debian, glibc) specifically for this reason.
If the Dockerfile has been customized to an Alpine base, GPU stats will not work regardless of compose configuration — switch back to a Debian/Ubuntu-based Node image and rebuild without cache:
docker compose -f docker-compose.yml -f docker-compose.gpu.yml build --no-cache backend
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d backend
docker exec -it visiyon-backend nvidia-smi
Once nvidia-smi works inside the container, the admin panel picks it up automatically on its next poll — no further restart needed.
FAQ
Does any user data leave my server?
No, by default. All inference runs against your own Ollama instance. Data only leaves your infrastructure if you deliberately configure a cloud provider — ElevenLabs for voice, or OpenAI/Stability AI for image generation.
Can I run this without a GPU?
Yes. Everything runs CPU-only; expect slower model responses, transcription, and image generation.
What happens if I don't configure image generation or SSO?
Both are hidden entirely from the UI until configured — nothing broken or half-visible.
Where do I report a bug or request a feature?
Use the Visiyon Community forum.