Download latest release: [Releases Β· aleks-asa/aaasaasa-web-client (github.com)]( Download URL (https://stajic.de/aaasaasa-ai-web-client/releases/)
Aaasaasa AI Web Client is a desktop (Electron) + web (Nuxt 4 SPA) client for AI workflows: chat, local functions, RAG search, and PDF/HTML ingestion. Focus: speed, local control, and integration with vector search.
This repository covers the Web Client. For the complete system, it is recommended to also use:
aaasaasadβ local/remote daemon (REST/WS) for/chat,/memorize,/search, load-balancing (vLLM sessionsβ¦), etc.aaasaasa-cliβ CLI tool (document ingest, export/import, collection management).- Qdrant β local vector database (RAG/topβK) for document & query search, analytics, profiling.
- DuckDB β local integrated database (sessions, messages, metrics).
Renderer is a pure SPA (SSR disabled). DuckDB and Qdrant are not imported from Nuxt β everything goes via Electron IPC.
- π₯οΈ Electron + Nuxt 4 (SPA): fast startup, isolated Chromium profile, DevTools.
- π Preload bridge:
contextIsolation,nodeIntegration=false,sandbox=false(desktop app). - ποΈ DuckDB (NodeβAPI): local SQL/analytics; CSV/Parquet ingest handled in the main process.
- π Qdrant: RAG & semantic search (REST client in main process).
- βοΈ Config via JSON:
aaasaasa.config.json(dev: root; prod: copied intoresources/). - ποΈ Workspaces & UI: theme (dark/light/system), accent color, font size.
- π LLM provider layer:
aaasaasa-cli, OpenAI, Ollama, HF (via config). - π‘οΈ Security modes: relaxed CSP for desktop; optional crossβorigin isolation for WASM needs.
- π§ Packaging: Linux AppImage + .deb; Windows .exe; macOS .dmg (planned).
ββββββββββββββββββββββββ
β Electron (main) β β .env (dev) + aaasaasa.config.json (prod/resources)
β - IPC handlers β
β - DuckDB (node-api) β
β - Qdrant REST β
β - fetch/cheerio β
β - security (CSP) β
βββββββββββ²βββββββββββββ
β IPC (preload exposes window.aaasaasa)
βββββββββββ΄βββββββββββββ
β Nuxt 4 SPA renderer β
β - UI (Tailwind/shadcn)β
β - Chat store / RAG UI β
βββββββββββ²βββββββββββββ
β HTTP/WS (optional)
βββββββββββ΄ββββββββββββββ
β aaasaasad (daemon) β
β - /chat, /memorize β
β - sessions, search β
ββββββββββββββββββββββββββ
Additionally: Qdrant (vectors) β’ DuckDB (local records)
- Node.js 20+ (24+ recommended)
- pnpm
- Qdrant running on
http://localhost:6333(or set in config) - Linux for building (AppImage/deb). Windows/macOS builds supported via electronβbuilder.
-
AppImage: make executable & run
chmod +x ./Aaasaasa\ Web\ Client-*.AppImage ./Aaasaasa\ Web\ Client-*.AppImage
If AppImage refuses to start, install FUSE:
sudo apt-get update && sudo apt-get install -y libfuse2 || true
-
.deb (Debian/Ubuntu):
sudo dpkg -i Aaasaasa_Web_Client-*.deb || sudo apt-get -f install -y
- Download and run the
Aaasaasa Web Client-*-Setup.exe. - If SmartScreen warns, choose More info β Run anyway (until code signing is added).
.dmgcoming soon. (For local dev you can still run Electron viapnpm.)
- Clone the repo and place
aaasaasa.config.jsonin root (example below). - (Optional)
.envin root (dev only), e.g.:
NODE_ENV=development
DEV_SERVER_URL=http://localhost:3000- Start dev:
./bootstrap.shThe script cleans build artifacts, loads root .env, starts Nuxt dev (app/) and Electron (electron/main.cjs).
Dev mode reads
.envfrom the repo root, not from packaged paths.
./bootstrap-prod.shThis will:
- Run Nuxt static build β
app/.output/public - Copy renderer β
electron/renderer/(packed into app.asar) - Copy
aaasaasa.config.json(root βresources/) - Run
electron-builder(AppImage + deb)
In production,
.envis not used; values come fromaaasaasa.config.json.
aaasaasa-web-client/
ββ aaasaasa.config.json # single source of truth (root)
ββ app/ # Nuxt 4 (SPA)
β ββ assets/css/tailwind.css
β ββ plugins/*.client.ts
β ββ pages/index.vue
β ββ nuxt.config.ts # ssr:false; app.baseURL ('/' for app://); buildAssetsDir '_nuxt/'
ββ electron/
β ββ main.cjs # window + IPC + DuckDB/Qdrant init
β ββ preload.cjs # exposes window.aaasaasa (readonly)
β ββ web.cjs # fetch/http + cheerio helpers
β ββ vector.cjs # Qdrant REST adapter
β ββ security.cjs # CSP/permissions (relaxed desktop mode)
β ββ renderer/ # (prod) Nuxt output (packed in asar)
ββ bootstrap.sh
ββ bootstrap-prod.sh
ββ README.md
Example:
{
"ui": { "appTitle": "Aaasaasa AI Tool by Aleks" },
"apiBase": "http://localhost:3000",
"wsBase": "ws://localhost:3000",
"duckdb": {
"path": "./duckdb/aaasaasa.duckdb",
"threads": "auto",
"memoryLimit": "12GB"
},
"rag": {
"vectorStore": {
"url": "http://localhost:6333",
"collection": "aaasaasa",
"size": 384
}
},
"security": {
"allowedOrigins": [
"http://localhost:3000",
"http://localhost:6333",
"https://api.openai.com",
"https://cdn.jsdelivr.net",
"https://unpkg.com"
]
},
"ai": {
"provider": "openai",
"openai": { "baseURL": "https://api.openai.com/v1", "model": "gpt-4o-mini", "apiKey": null },
"ollama": { "baseURL": "http://localhost:11434" },
"hf": { "inferenceURL": "", "apiKey": null }
},
"features": { "streaming": true, "rollingSummary": true, "telemetry": false }
}Policy: Dev reads .env + config from root; Prod copies config into resources/ and does not use .env.
// app/nuxt.config.ts
export default defineNuxtConfig({
ssr: false,
app: {
// For app:// loader keep '/'; for file:// use './'
baseURL: '/',
buildAssetsDir: '_nuxt/'
},
nitro: { preset: 'static' }
});Electron main (snippet):
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true } }
]);
protocol.registerFileProtocol('app', (request, callback) => {
const u = new URL(request.url); // app://-/_nuxt/entry.js
const clean = decodeURI(u.pathname).replace(/^\/+/, '');
const base = path.join(__dirname, 'renderer');
let target = path.normalize(path.join(base, clean));
if (!target.startsWith(base)) return callback({ error: -6 });
fs.stat(target, (err, st) => {
if (!err && st.isDirectory()) target = path.join(target, 'index.html');
else if (err && !path.extname(clean)) target = path.join(base, 'index.html');
callback({ path: target });
});
});
// In prod ALWAYS use a real host component, e.g. 'app://-/index.html'
win.loadURL('app://-/index.html');If you load
app://index.html, the host becomesindex.htmland/_nuxt/*resolves toapp://index.html/_nuxt/*β 404. Useapp://-/index.htmlorapp:///index.html.
db.cjs (essentials):
let instance = null, connection = null, currentPath = null;
async function init(cfg = {}) {
const { DuckDBInstance, DuckDBConnection } = require('@duckdb/node-api');
const target = cfg.path || ':memory:';
if (connection && currentPath === target) return connection; // idempotent
if (connection) await closeDuck();
instance = await DuckDBInstance.create(target);
connection = await DuckDBConnection.create(instance);
currentPath = target;
const threads = Number(cfg.threads) || require('os').cpus().length || 1;
await connection.run(`PRAGMA threads = ${threads}`);
if (cfg.memoryLimit) await connection.run(`PRAGMA memory_limit = '${cfg.memoryLimit}'`);
return connection;
}Main process (single init + single instance lock):
const gotLock = app.requestSingleInstanceLock();
if (!gotLock) { app.quit(); return; }
app.whenReady().then(async () => {
const raw = loadConfigRaw();
const cfg = normalizeConfig(raw);
await duckdb.ensurePath(cfg.duckdb?.path);
await duckdb.init(cfg.duckdb); // single init
createWindow();
});
app.on('before-quit', () => duckdb.closeDuck().catch(() => {}));Tips to avoid Conflicting lock is held β¦:
- Do not start dev & packaged app at the same time on the same DB file.
- Use different DB paths per mode (e.g. dev
:memory:or separateuserData/dev). - Initialize DB once and reuse the connection.
window.aaasaasa: {
env: { isDev: boolean, versions: Record<string,string> },
config: { get(): Promise<any> },
db: { sql(query: string, params?: any[]): Promise<any> },
vector: {
upsert(points: Array<{ id?: string|number, vector: number[], payload?: any }>, opts?: any): Promise<any>,
search(vector: number[], opts?: { top?: number, limit?: number, score_threshold?: number }): Promise<any>
},
chat?: { open(): Promise<{ ok: true }> }
}Examples (Nuxt):
const rows = await (globalThis as any).aaasaasa.db.sql('SELECT 42 AS answer');
const hits = await (globalThis as any).aaasaasa.vector.search(new Array(384).fill(0), { top: 3 });A simple dev runner that starts Nuxt (app) and then Electron (main). It also waits for the dev server to be ready.
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export NODE_ENV=development
export DEV_SERVER_URL="${DEV_SERVER_URL:-http://localhost:3000}"
# 1) install deps (root, app, electron)
pnpm install
pnpm -C "$ROOT/app" install
pnpm -C "$ROOT/electron" install
# 2) start Nuxt dev (background)
(
cd "$ROOT/app"
pnpm dev &
) >/dev/null 2>&1 &
# 3) wait for dev server
printf "Waiting for %s " "$DEV_SERVER_URL"
for i in {1..60}; do
if curl -fsS "$DEV_SERVER_URL" >/dev/null 2>&1; then echo "β"; break; fi
printf "."; sleep 1
done
# 4) run Electron main
cd "$ROOT/electron"
pnpm exec electron .Build Nuxt (static), ensure renderer is present, then build Electron packages.
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# clean previous renderer
rm -rf "$ROOT/electron/renderer" || true
# 1) Nuxt static build
cd "$ROOT/app"
pnpm install
pnpm generate
# 2) (fallback copy) If your beforePack already copies, this is harmless
mkdir -p "$ROOT/electron/renderer"
rsync -a --delete ./.output/public/ "$ROOT/electron/renderer/" || true
# 3) Electron build
cd "$ROOT/electron"
pnpm install
pnpm exec electron-builder- Blank page / 404 for
/_nuxt/*β ensureapp://-/index.htmland protocol handler above. - DB
not initializedβ callduckdb.init(...)before first IPCdb:sql. Could not set lock on file β¦β another process holds the DB; ensure single instance and distinct dev/prod DB paths.- DevTools not opening β call on
ready-to-showand/or useCtrl/Cmd+Shift+Ishortcut.
- Separate READMEs for
electron/(IPC/preload/security) andapp/(UI/store/plugins) - Rolling summary util (stable chat > 300 messages)
- Qdrant topβK cache & chunking ingest
- PDF generator and optional Python bridge
- CI (lint/test/build) and release scripts
TBD (temporarily: All rights reserved / or MIT at your decision).
DuckDB and Qdrant are under their respective licenses.
