Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2f39247
Add ansible setup to quickly deploy ohb chatbot
Mar 19, 2026
21400a0
Switch to global constants instead of injected env vars. Add typing f…
Mar 19, 2026
a441b7c
refactor/simplify configuration so that only 1 source of truth and 1 …
Mar 19, 2026
5c1c051
allow disabling of footer, sidebar logos, personas, chat imports; fix…
Mar 19, 2026
8acf761
allow hiding settings
Mar 19, 2026
8927392
hide the other 3 persona and import buttons
Mar 19, 2026
a01158c
add in-chat model selector
Mar 19, 2026
2c7e85a
minor changes/fixes: update sample config, set fixed node version, ch…
Apr 2, 2026
b79dbf2
fix usage of old config; new deployment pipeline, including backend
Apr 2, 2026
0e3267a
allow whitelist/blacklist for models
Apr 2, 2026
4266a21
add arcana handling; allow hiding tour
Apr 10, 2026
8e61417
fix filtering bug
Apr 10, 2026
e41c95a
Add preliminary MPG theming
Apr 10, 2026
d36446b
explicitly restart chatai express backend (e.g. if only secrets change)
Apr 15, 2026
32aa441
allow separating between gateway and saia in express backend
Apr 17, 2026
c59d3ef
debug: log request
May 8, 2026
1846fb5
pass email header to gateway
May 8, 2026
f3f5364
add email header in second place
May 8, 2026
37d0310
Add empty space in conversation so scrolling down works better
May 8, 2026
8f51093
fix empty space being ignored
May 8, 2026
f9f6c70
nai
May 8, 2026
72ff331
add loading indicator; add conversation import button
May 10, 2026
1b0befb
fix scroll down behavior
May 10, 2026
159d9d6
fix model selector position (bit hacky)
May 10, 2026
d3449f6
Merge origin/main into mpg-themable-interface
May 31, 2026
c11932f
Add doxc export support
Jun 16, 2026
363ba10
Add doxc export support
Jun 16, 2026
f5756e7
differentiate between arcanas and real thinking in exports
Jun 17, 2026
f571a4d
Merge branch 'mpg-themable-interface' of github.com:gwdg/chat-ai into…
Jun 17, 2026
eaacc81
Add help button to footer
Jul 2, 2026
7e2b2f1
Add disclaimers
Jul 2, 2026
1feb45e
make the option for thinking blocks always visible in exports
Jul 2, 2026
1a99b16
Merge branch 'fix-regenerated-does-not-apply-current-settings' into m…
glor Jul 3, 2026
f3d60e1
add logs for referces in json format
Jul 3, 2026
cc4c113
improve reference rendering for mpg
Jul 8, 2026
cc7ae2f
improved new references render
Jul 8, 2026
7a56762
Update OHB Info pdf; Update banner+infotext for OHB
Jul 10, 2026
873087c
Merge branch 'mpg-themable-interface' of github.com:gwdg/chat-ai into…
Jul 10, 2026
8606bba
Updated disclaimer information.
Twalord Jul 10, 2026
0e2a884
Updated help pdf.
Twalord Jul 28, 2026
e0509eb
Updated with the actual new help pdf file.
Twalord Jul 28, 2026
65be93c
Update MPGHeader.tsx
Twalord Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions back/service.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const configPath = path.resolve(CONFIG_LOCATION);
// Default configuration if config file is missing or invalid
let port = 8081;
let apiEndpoint = "https://chat-ai.academiccloud.de/v1";
let gatewayEndpoint = apiEndpoint;
let apiKey = "";
let serviceName = "Chat AI Dev";

Expand All @@ -34,6 +35,9 @@ try {
);
}
apiEndpoint = config.apiEndpoint;
if(config.gatewayEndpoint) {
gatewayEndpoint = config.gatewayEndpoint;
}
apiKey = config.apiKey;
serviceName = config.serviceName;
} catch (error) {
Expand Down Expand Up @@ -219,6 +223,11 @@ app.post("/chat/completions", async (req, res) => {
const mcp_servers = req.body["mcp-servers"] || null;
const inference_id = req.headers["inference-id"];
const uid = req.headers["oidc_claim_uid"];
console.log("Incoming /chat/completions request", {
method: req.method,
path: req.path,
headers: req.headers,
});
if (!Array.isArray(messages)) {
return res.status(422).json({ error: "Invalid messages provided" });
}
Expand Down Expand Up @@ -307,7 +316,7 @@ app.post("/chat/completions", async (req, res) => {
}
}

const openai = new OpenAI({baseURL : apiEndpoint, apiKey: apiKey ? apiKey : inference_id});
const openai = new OpenAI({baseURL : gatewayEndpoint, apiKey: apiKey ? apiKey : inference_id});

// Temporary workaround as middleware doesn't support timeout yet
if (params.arcana || params.model.includes("rag") || params.model.includes("sauerkraut")) delete params.timeout;
Expand All @@ -316,6 +325,7 @@ app.post("/chat/completions", async (req, res) => {
const headers = {
"inference-service": inference_service,
"inference-portal": serviceName,
"x-user-email": req.headers['oidc_claim_email'],
"user": uid
};

Expand Down Expand Up @@ -365,13 +375,14 @@ app.post("/chat/completions", async (req, res) => {
});
}
// Couldn't extract error message, so try without openai library
const response = await fetch(apiEndpoint + "/chat/completions", {
const response = await fetch(gatewayEndpoint + "/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${ apiKey ? apiKey : inference_id }`,
"Content-Type": "application/json",
"inference-service": inference_service,
"inference-portal": serviceName,
"x-user-email": req.headers['oidc_claim_email'],
"inference-id": inference_id
},
body: JSON.stringify(params)
Expand Down
233 changes: 233 additions & 0 deletions deploy/ansible/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
---
- name: Build and deploy frontend and backend
hosts: all
become: true
vars:
# Build variables
builds_root: /srv/builds
build_workspace: "{{ builds_root }}/{{ release_id }}"
frontend_source_dir: "{{ build_workspace }}/front"
backend_source_dir: "{{ build_workspace }}/back"

# Symlinks
frontend_current_symlink: /srv/frontend-current
backend_current_symlink: /srv/backend-current

# Secrets
frontend_secret_path: "/srv/secrets/front.ts"
backend_secret_path: "/srv/secrets/back.json"

# General variables
backend_service_name: chat-ai-backend
keep_releases: 5
prune_start_index: "{{ (keep_releases | int) + 1 }}"

pre_tasks:
- name: Ensure required variables are present
ansible.builtin.assert:
that:
- git_repo_url is defined
- git_repo_url | length > 0
- git_ref is defined
- git_ref | length > 0
fail_msg: "Set git_repo_url and git_ref when running the playbook"

- name: Set default release_id if not provided
ansible.builtin.set_fact:
release_id: "{{ ansible_date_time.epoch }}"
when: release_id is not defined

- name: Install prerequisite packages on Debian/Ubuntu
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
- git
state: present
update_cache: true
when: ansible_os_family == "Debian"

- name: Add NodeSource apt key for Node.js 25
ansible.builtin.shell: |
set -e
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
chmod a+r /etc/apt/keyrings/nodesource.gpg
args:
creates: /etc/apt/keyrings/nodesource.gpg
when: ansible_os_family == "Debian"

- name: Add NodeSource repository for Node.js 25
ansible.builtin.apt_repository:
repo: "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_25.x nodistro main"
filename: nodesource
state: present
when: ansible_os_family == "Debian"

- name: Install Node.js 25 on Debian/Ubuntu
ansible.builtin.apt:
name:
- nodejs
state: present
update_cache: true
when: ansible_os_family == "Debian"

- name: Ensure frontend config exists on server
ansible.builtin.stat:
path: "{{ frontend_secret_path }}"
register: frontend_secret

- name: Fail if frontend config is missing
ansible.builtin.assert:
that:
- frontend_secret.stat.exists
fail_msg: "Expected real frontend config at {{ frontend_secret_path }}"

- name: Ensure backend config exists on server
ansible.builtin.stat:
path: "{{ backend_secret_path }}"
register: backend_secret

- name: Fail if backend config is missing
ansible.builtin.assert:
that:
- backend_secret.stat.exists
fail_msg: "Expected real backend config at {{ backend_secret_path }}"

tasks:
- name: Ensure builds directory exists
ansible.builtin.file:
path: "{{ builds_root }}"
state: directory
mode: "0755"

- name: Checkout repository for this release
ansible.builtin.git:
repo: "{{ git_repo_url }}"
dest: "{{ build_workspace }}"
version: "{{ git_ref }}"
force: true

- name: Ensure secrets directory exists in workspace
ansible.builtin.file:
path: "{{ build_workspace }}/secrets"
state: directory
mode: "0755"

- name: Copy real frontend config into build workspace
ansible.builtin.copy:
src: "{{ frontend_secret_path }}"
dest: "{{ build_workspace }}/secrets/front.ts"
remote_src: true
mode: "0600"

- name: Copy real backend config into build workspace
ansible.builtin.copy:
src: "{{ backend_secret_path }}"
dest: "{{ build_workspace }}/secrets/back.json"
remote_src: true
mode: "0600"

# Frontend build
- name: Install frontend dependencies
ansible.builtin.command: npm install
args:
chdir: "{{ frontend_source_dir }}"

- name: Build frontend with real config
ansible.builtin.command: npm run build
args:
chdir: "{{ frontend_source_dir }}"

# Backend build (npm install for dependencies)
- name: Install backend dependencies
ansible.builtin.command: npm install
args:
chdir: "{{ backend_source_dir }}"

# Prepare backend release directory
- name: Ensure backend build directory exists
ansible.builtin.file:
path: "{{ backend_source_dir }}/dist"
state: directory
mode: "0755"

- name: Create backend distribution with necessary files
ansible.builtin.shell: |
cp "{{ backend_source_dir }}/service.mjs" "{{ backend_source_dir }}/dist/"
cp "{{ backend_source_dir }}/package.json" "{{ backend_source_dir }}/dist/"
cp "{{ backend_source_dir }}/package-lock.json" "{{ backend_source_dir }}/dist/" 2>/dev/null || true
cp -r "{{ backend_source_dir }}/node_modules" "{{ backend_source_dir }}/dist/" || true

- name: Create backend config symlink in dist directory
ansible.builtin.file:
src: "{{ backend_secret_path }}"
dest: "{{ backend_source_dir }}/dist/back.json"
state: link
force: true

# Atomic symlink switches
- name: Atomically switch frontend current symlink
ansible.builtin.shell: |
ln -sfn "{{ frontend_source_dir }}/dist" "{{ builds_root }}/.frontend_tmp"
mv -Tf "{{ builds_root }}/.frontend_tmp" "{{ frontend_current_symlink }}"

- name: Atomically switch backend current symlink
ansible.builtin.shell: |
ln -sfn "{{ backend_source_dir }}/dist" "{{ builds_root }}/.backend_tmp"
mv -Tf "{{ builds_root }}/.backend_tmp" "{{ backend_current_symlink }}"

# Backend service management
- name: Create backend systemd service file
ansible.builtin.copy:
content: |
[Unit]
Description=Chat AI Backend Service
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory={{ backend_current_symlink }}
ExecStart=/usr/bin/node {{ backend_current_symlink }}/service.mjs
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=chat-ai-backend
Environment="NODE_ENV=production"
Environment="CONFIG_LOCATION={{ backend_secret_path }}"

[Install]
WantedBy=multi-user.target
dest: "/etc/systemd/system/{{ backend_service_name }}.service"
mode: "0644"
notify: Restart backend service

- name: Enable backend service
ansible.builtin.systemd:
name: "{{ backend_service_name }}"
enabled: true
daemon_reload: true

- name: Gather service facts
ansible.builtin.service_facts:

- name: Restart backend service if it exists
ansible.builtin.systemd:
name: "{{ backend_service_name }}"
state: restarted
when: (backend_service_name ~ '.service') in ansible_facts.services

# Cleanup old builds
- name: Prune old builds and keep latest
ansible.builtin.shell: |
ls -1dt {{ builds_root }}/* 2>/dev/null | tail -n +{{ prune_start_index }} | xargs -r rm -rf

handlers:
- name: Restart backend service
ansible.builtin.systemd:
name: "{{ backend_service_name }}"
state: restarted
daemon_reload: true
Loading