Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

# user defined files
.env
docker-compose.override.yml
docker-compose.override.yml
2 changes: 2 additions & 0 deletions develop/dev.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ GRACEFUL_SHUTDOWN_DELAY_SECONDS=0
HISTORY_V1_HOST=history-v1
LISTEN_ADDRESS=0.0.0.0
MONGO_HOST=mongo
LLM_HOST=llm
MONGO_URL=mongodb://mongo/sharelatex?directConnection=true
NOTIFICATIONS_HOST=notifications
PROJECT_HISTORY_HOST=project-history
REALTIME_HOST=real-time
REDIS_HOST=redis
REDIS_URL=redis://redis:6379
SPELLING_HOST=spelling
WEBPACK_HOST=webpack
WEB_API_PASSWORD=overleaf
Expand Down
16 changes: 14 additions & 2 deletions develop/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ services:
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9236:9229"
- "127.0.0.1:9238:9229"
volumes:
- ../services/references/app:/overleaf/services/references/app
- ../services/references/config:/overleaf/services/references/config
Expand Down Expand Up @@ -123,8 +123,20 @@ services:
- ../services/real-time/app.js:/overleaf/services/real-time/app.js
- ../services/real-time/config:/overleaf/services/real-time/config

llm:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9241:9229"
volumes:
- ../services/llm/app:/overleaf/app
- ../services/llm/app.js:/overleaf/services/llm/app.js
- ../services/llm/config:/overleaf/services/llm/config


web:
command: ["node", "--watch", "app.js", "--watch-locales"]
command: ["node", "--watch", "app.mjs", "--watch-locales"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
Expand Down
17 changes: 16 additions & 1 deletion develop/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ services:
dockerfile: services/real-time/Dockerfile
env_file:
- dev.env
environment:
- SESSION_SECRET=helloworld

redis:
image: redis:5
Expand All @@ -140,6 +142,16 @@ services:
volumes:
- spelling-cache:/overleaf/services/spelling/cache

llm:
build:
context: ..
dockerfile: services/llm/Dockerfile
env_file:
- dev.env
depends_on:
- mongo
- redis

web:
build:
context: ..
Expand All @@ -153,7 +165,8 @@ services:
- EMAIL_CONFIRMATION_DISABLED=true
- NODE_ENV=development
- OVERLEAF_ALLOW_PUBLIC_ACCESS=true
command: ["node", "app.js"]
- SESSION_SECRET=helloworld
command: ["node", "app.mjs"]
volumes:
- sharelatex-data:/var/lib/overleaf
- web-data:/overleaf/services/web/data
Expand All @@ -172,8 +185,10 @@ services:
- project-history
- real-time
- spelling
- llm

webpack:
user: root
build:
context: ..
dockerfile: services/web/Dockerfile
Expand Down
12 changes: 10 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,15 @@ services:
OVERLEAF_APP_NAME: Overleaf Community Edition

OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex
# LLM service expects these generic URLs
MONGO_URL: mongodb://mongo/sharelatex?directConnection=true

# Same property, unfortunately with different names in
# different locations
OVERLEAF_REDIS_HOST: redis
REDIS_HOST: redis
# LLM service expects a single URL env
REDIS_URL: redis://redis:6379

ENABLED_LINKED_FILE_TYPES: 'project_file,project_output_file'

Expand Down Expand Up @@ -114,7 +118,7 @@ services:
container_name: mongo
command: '--replSet overleaf'
volumes:
- ~/mongo_data:/data/db
- mongo_data:/data/db
- ./server-ce/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js
environment:
MONGO_INITDB_DATABASE: sharelatex
Expand All @@ -133,7 +137,7 @@ services:
image: redis:6.2
container_name: redis
volumes:
- ~/redis_data:/data
- redis_data:/data

# ldap:
# restart: always
Expand All @@ -154,3 +158,7 @@ services:
# volumes:
# - /var/run/docker.sock:/tmp/docker.sock:ro
# - /home/overleaf/tmp:/etc/nginx/certs

volumes:
mongo_data:
redis_data:
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"services/history-v1",
"services/idp",
"services/latexqc",
"services/llm",
"services/notifications",
"services/project-history",
"services/real-time",
Expand Down
10 changes: 9 additions & 1 deletion server-ce/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Overleaf Community Edition (overleaf/overleaf)
# ---------------------------------------------

ARG OVERLEAF_BASE_TAG=ghcr.io/lcpu-club/sharelatex-base:latest
ARG OVERLEAF_BASE_TAG=sharelatex/sharelatex-base:latest
FROM $OVERLEAF_BASE_TAG

WORKDIR /overleaf
Expand Down Expand Up @@ -61,6 +61,14 @@ RUN chmod 600 /etc/cron.d/crontab-deletion
COPY server-ce/init_scripts/ /etc/my_init.d/
COPY server-ce/init_preshutdown_scripts/ /etc/my_init.pre_shutdown.d/

# Remove deprecated ShareLaTeX-branded paths to satisfy start-up checks
# The init script 000_check_for_old_bind_mounts_5.sh refuses to start if these exist
RUN rm -rf /etc/sharelatex /var/lib/sharelatex /var/log/sharelatex || true

# Ensure Overleaf log directory exists for runit services redirection
RUN mkdir -p /var/log/overleaf \
&& chown www-data:www-data /var/log/overleaf

# Copy app settings files
# -----------------------
COPY server-ce/config/settings.js /etc/overleaf/settings.js
Expand Down
1 change: 1 addition & 0 deletions server-ce/config/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export DOCUMENT_UPDATER_HOST=127.0.0.1
export DOCUPDATER_HOST=127.0.0.1
export FILESTORE_HOST=127.0.0.1
export HISTORY_V1_HOST=127.0.0.1
export LLM_HOST=127.0.0.1
export NOTIFICATIONS_HOST=127.0.0.1
export PROJECT_HISTORY_HOST=127.0.0.1
export REALTIME_HOST=127.0.0.1
Expand Down
30 changes: 30 additions & 0 deletions server-ce/runit/llm-overleaf/run
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Enable Node inspector when DEBUG_NODE=true (unique port for llm)
NODE_PARAMS=""
if [ "$DEBUG_NODE" == "true" ]; then
echo "running debug - llm"
NODE_PARAMS="--inspect=0.0.0.0:30120"
fi

# Load shared Overleaf environment (DB/Redis, config paths, etc.)
source /etc/overleaf/env.sh

# Propagate redis/mongo connection URLs expected by LLM if not explicitly set
if [ -z "$REDIS_URL" ] && [ -n "$REDIS_HOST" ]; then
export REDIS_URL="redis://$REDIS_HOST:${REDIS_PORT:-6379}"
fi
if [ -z "$MONGO_URL" ] && [ -n "$OVERLEAF_MONGO_URL" ]; then
# Use Overleaf's mongo URL; append replicaSet if not present and env hints it's enabled
case "$OVERLEAF_MONGO_URL" in
*replicaSet=*) export MONGO_URL="$OVERLEAF_MONGO_URL" ;;
*) export MONGO_URL="${OVERLEAF_MONGO_URL}?replicaSet=${MONGO_REPLICA_SET_NAME:-overleaf}" ;;
esac
fi

# Bind service listen address (default 0.0.0.0 if not set elsewhere)
export LISTEN_ADDRESS=${LISTEN_ADDRESS:-0.0.0.0}

# Start the LLM service as www-data; logs to /var/log/overleaf/llm.log
exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /overleaf/services/llm/app.js >> /var/log/overleaf/llm.log 2>&1

22 changes: 22 additions & 0 deletions services/llm/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:20.19.5 AS base

WORKDIR /overleaf/services/llm


FROM base AS app

COPY package.json package-lock.json /overleaf/
COPY services/llm/package.json /overleaf/services/llm/
COPY libraries/ /overleaf/libraries/
COPY patches/ /overleaf/patches/


RUN cd /overleaf && npm install

COPY services/llm/ /overleaf/services/llm/

FROM app
USER node
CMD ["node", "--expose-gc", "app.js"]


27 changes: 27 additions & 0 deletions services/llm/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// src/app.js
import express from 'express';
import connectDB from './config/db.js';
import keysRoutes from './app/routes/keys.routes.js';
import llmRoutes from './app/routes/llm.routes.js';
import cookieParser from 'cookie-parser';
import settings from '@overleaf/settings'

const app = express();

app.use(cookieParser());

app.use(express.json());

// connect to database
await connectDB();

// register routes
app.use('/api/v1/llm', keysRoutes);

app.use('/api/v1/llm', llmRoutes);

app.listen(settings.PORT, settings.LISTEN_ADDRESS, () => {
console.log(`server running on ${settings.PORT}`);
});


80 changes: 80 additions & 0 deletions services/llm/app/controllers/keys.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { KeysService } from '../services/keys.service.js';
import {getUserIdentifier} from '../utils/common.js';
export class KeysController {
constructor() {
this.keysService = new KeysService();
}

async saveKey(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const { name, baseUrl, apiKey } = req.body;
await this.keysService.saveApiKey(userIdentifier, name, baseUrl, apiKey);
res.status(200).json({ success: true });
} catch (error) {
res.status(400).json({ success: false, data: error.message });
}
}

async deleteKey(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const { name } = req.body;
const result = await this.keysService.deleteApiKey(userIdentifier, name);
res.status(200).json({ success: true, data: result });
} catch (error) {
console.log(error.message)
res.status(400).json({ success: false, data: error.message });
}
}

async getLlmInfo(req, res) {

try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const result = await this.keysService.getLlmInfo(userIdentifier);
res.status(200).json({ success: true, data: result });
} catch (error) {
res.status(400).json({ success: false, data: error.message });
}
}
async getUsingLlm(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const result = await this.keysService.getUsingLlm(userIdentifier);
res.status(200).json({ success: true, data: result });
} catch (error) {
res.status(400).json({ success: false, data: error.message });
}
}


async updateUsingLlm(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const { usingLlm } = req.body;
await this.keysService.updateUsingLlm(userIdentifier, usingLlm);
res.status(200).json({ success: true });
} catch (error) {
res.status(400).json({ success: false, data: error.message });
}
}
async updateUsingModel(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
const { name, chatOrCompletion, newModel } = req.body;
await this.keysService.updateUsingModel(userIdentifier, name, chatOrCompletion, newModel);
res.status(200).json({ success: true });
} catch (error) {
res.status(400).json({ success: false, data: error.message });
}
}
}


53 changes: 53 additions & 0 deletions services/llm/app/controllers/llm.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { LLMService } from "../services/llm.service.js";
import {getUserIdentifier} from '../utils/common.js';
export class LLMController {
constructor() {
this.llmService = new LLMService();
}
async chat(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
// console.log('llmcontroller:',userIdentifier)

const { ask, selection, filelist, outline, mode } = req.body;
//console.log('llmcontroller body:',ask, selection, filelist, outline, mode)
const content = await this.llmService.chat(
userIdentifier, ask, selection, filelist, outline, mode
);
res.status(200).json({
success: true,
data: content
});
} catch (error) {
res.status(400).json({ success: false, data:error.message });
}
}
/**
* completion
*/
async completion(req, res) {
try {
const sid = req.cookies['overleaf.sid'];
const userIdentifier = await getUserIdentifier(sid);
console.log('userIdentifier:', userIdentifier);
const { cursorOffset, leftContext, rightContext, language, maxLength, fileList, outline } = req.body;

const content = await this.llmService.completion(
userIdentifier, cursorOffset, leftContext, rightContext, language, maxLength, fileList, outline
);
//const content = "helo world";
console.log('completion content:', content);
res.status(200).json({
success: true,
data: content
});
} catch (err) {
console.error('completion error:', err);
res.status(400).json({ success: false, data:err.message });
}
}
}



Loading