Loading repository data…
Loading repository data…
Zaki-1052 / repository
A feature-rich portal to chat with GPT-4, Claude, Gemini, Mistral, & OpenAI Assistant APIs via a lightweight Node.js web app; supports customizable multimodality for voice, images, & files.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
GPTPortal is a self-hosted, local-first web app that gives you one clean chat interface in front of every major LLM provider at once — OpenAI, Anthropic, Google, xAI, DeepSeek, Groq, Moonshot, Mistral, the entire OpenRouter catalog, and any OpenAI-compatible endpoint you run yourself (Ollama, LM Studio, vLLM). You bring your own API keys, run it on your own machine with a single node server.js, and talk to any model from any provider without juggling five different web UIs.
It is deliberately lightweight. There is no build step, no bundler, no frontend framework, and no database. The backend is modular Node/Express; the frontend is hand-written HTML, CSS, and vanilla JavaScript. You can read the whole thing, change it, and restart it in seconds. That constraint is a feature — this is meant to be a tool you own and understand, not a black box you deploy and forget.
This document is long on purpose. It is meant to get a non-technical user from zero to a working portal, and to give a developer enough of the map to extend it confidently. Skim the Table of Contents and jump to what you need.
.env)It is:
.env file that is never committed. Your conversations are held in your browser session and, optionally, in files under public/uploads/. Nothing is sent anywhere except the actual API calls to whichever provider you chose for a given message.It is not:
Models and providers
Conversation
Generate: to produce an image inline.Workflow
.env from inside the browser.Interface
The point of the 2.0 rewrite was to make the codebase legible. Here is the whole map.
node server.js
│
▼
src/server/core/Application.js ← wires everything together, emits lifecycle events
├── MiddlewareManager ← Basic Auth, sessions, CORS, rate limiting, CSP, body parsing, static files
├── ServiceManager ← instantiates and holds the services below
└── RouteManager ← mounts every HTTP route onto Express
Services (src/server/services/**)
├── providers/providerFactory.js ← picks a handler for a given model id and dispatches to it
│ ├── openai/** ← Chat Completions + Responses API, images, audio, assistants
│ ├── claudeHandler.js ← Anthropic Messages API, adaptive thinking, web search, caching
│ ├── geminiHandler.js ← Google @google/genai SDK
│ ├── grok / deepseek / groq / kimi / mistral / openrouterHandler.js
│ ├── customEndpointHandler.js ← generic OpenAI-compatible handler for your own endpoints
│ └── endpointResolver.js ← turns `<prefix>/<model>` + config into a concrete call target
├── modelProviders/modelRegistry.js← unifies core + OpenRouter + custom models for the API/UI
├── costService / tokenService / contextWindowService / tokenCountService
├── promptCacheService ← Claude cache-control breakpoints
├── titleService / exportService / modelSyncService
└── shared/modelLoader.js ← loads and caches the model catalog
Frontend (public/**, no build step)
├── portal.html + chat.css ← app shell + the single design-token stylesheet
└── js/modules/*.js ← dynamicModelManager, modelUI, modelSearch, chatManager,
messageHandler, uiManager, portalInit, presets, compare
A few design decisions worth knowing up front, because they explain how everything else behaves:
public/js/data/models.json defines every core model. Routing, pricing, context windows, and capability flags are read from it. providerFactory consults each model's provider field first when deciding which handler to use, and only falls back to string-pattern matching for ids that aren't in the catalog (like OpenRouter's vendor/model ids). This means adding a model to an existing provider is usually a one-file change.handleRequest(payload) (non-streaming) and, where the provider supports it, async *streamCompletion(payload) that yields a small set of event types (thinking, text, usage, error). The rest of the system doesn't care which provider it's talking to.req.session.chat, not in module-level variables, so two browsers don't step on each other.window and coordinate through a handful of well-known objects (window.chatManager, window.dynamicModelManager, and so on). It is old-fashioned on purpose and easy to trace.If you want to add a provider or model, read Extending and the dedicated MODEL_UPDATE.md.
node --version.npm --version.If you already know your way around a terminal:
git clone https://github.com/Zaki-1052/GPTPortal.git
cd GPTPortal
npm install
cp .env.example .env # then edit .env: set USER_USERNAME, USER_PASSWORD, and at least one API key
node server.js
Open http://localhost:3000/portal, sign in with the username and password you set, pick a model, and start typing. That's the whole thing.
If you'd rather not touch a text editor, you can instead run node server.js first and visit http://localhost:3000/setup, which gives you a browser form to enter your keys and writes the .env for you.
This section assumes no prior experience. If you've done this kind of thing before, the Quick start above is all you need.
Go to nodejs.org and install the LTS build for your operating system. This also installs npm, the package manager the project uses. When it's done, open a terminal — Terminal on macOS, Command Prompt or PowerShell on Windows — and confirm it worked:
node --version
npm --version
Both should print a version nu