diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 000000000..6320cd248 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +data \ No newline at end of file diff --git a/server/data/.gitkeep b/server/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/data/models/.gitkeep b/server/data/models/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/data/threads/.gitkeep b/server/data/threads/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/icons/icon.png b/server/icons/icon.png new file mode 100644 index 000000000..289f99ded Binary files /dev/null and b/server/icons/icon.png differ diff --git a/server/lib/.gitkeep b/server/lib/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/main.ts b/server/main.ts new file mode 100644 index 000000000..707c3fb4b --- /dev/null +++ b/server/main.ts @@ -0,0 +1,19 @@ +import fastify from 'fastify' +import dotenv from 'dotenv' +import v1API from './v1' +const server = fastify() + +dotenv.config() +server.register(v1API, {prefix: "/api/v1"}) + + +const JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || '1337') +const JAN_API_HOST = process.env.JAN_API_HOST || "0.0.0.0" + +server.listen({ + port: JAN_API_PORT, + host: JAN_API_HOST +}).then(() => { + console.log(`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`); +}) + diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 000000000..0ea41ca96 --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["main.ts", "v1"], + "ext": "ts, json", + "exec": "tsc && node ./build/main.js" +} \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 000000000..1fd06a482 --- /dev/null +++ b/server/package.json @@ -0,0 +1,32 @@ +{ + "name": "jan-server", + "version": "0.1.3", + "main": "./build/main.js", + "author": "Jan ", + "license": "AGPL-3.0", + "homepage": "https://jan.ai", + "description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.", + "build": "", + "scripts": { + "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", + "test:e2e": "playwright test --workers=1", + "dev": "nodemon .", + "build": "tsc" + }, + "dependencies": { + }, + "devDependencies": { + "@types/body-parser": "^1.19.5", + "@types/npmcli__arborist": "^5.6.4", + "@typescript-eslint/eslint-plugin": "^6.7.3", + "@typescript-eslint/parser": "^6.7.3", + "dotenv": "^16.3.1", + "eslint-plugin-react": "^7.33.2", + "fastify": "^4.24.3", + "nodemon": "^3.0.1", + "run-script-os": "^1.1.6" + }, + "installConfig": { + "hoistingLimits": "workspaces" + } +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 000000000..3363fdba6 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "noImplicitAny": true, + "sourceMap": true, + "strict": true, + "outDir": "./build", + "rootDir": "./", + "noEmitOnError": true, + "esModuleInterop": true, + "baseUrl": ".", + "allowJs": true, + "skipLibCheck": true, + "paths": { "*": ["node_modules/*"] }, + "typeRoots": ["node_modules/@types"] + }, + // "sourceMap": true, + + "include": ["./**/*.ts"], + "exclude": ["core", "build", "dist", "tests", "node_modules"] +} diff --git a/server/v1/assistants/index.ts b/server/v1/assistants/index.ts new file mode 100644 index 000000000..c722195d0 --- /dev/null +++ b/server/v1/assistants/index.ts @@ -0,0 +1,8 @@ +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers for assistants here + // app.get("/", controller) + // app.post("/", controller) +} +export default router; \ No newline at end of file diff --git a/server/v1/chat/index.ts b/server/v1/chat/index.ts new file mode 100644 index 000000000..cb5fbf120 --- /dev/null +++ b/server/v1/chat/index.ts @@ -0,0 +1,11 @@ +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers for here + // app.get("/", controller) + + app.post("/", (req, res) => { + req.body + }) +} +export default router; \ No newline at end of file diff --git a/server/v1/index.ts b/server/v1/index.ts new file mode 100644 index 000000000..89d73200b --- /dev/null +++ b/server/v1/index.ts @@ -0,0 +1,37 @@ +import assistantsAPI from './assistants' +import chatCompletionAPI from './chat' +import modelsAPI from './models' +import threadsAPI from './threads' + +import { FastifyInstance, FastifyPluginAsync } from 'fastify' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts) => { + app.register( + assistantsAPI, + { + prefix: "/assistants" + } + ) + + app.register( + chatCompletionAPI, + { + prefix: "/chat/completion" + } + ) + + app.register( + modelsAPI, + { + prefix: "/models" + } + ) + + app.register( + threadsAPI, + { + prefix: "/threads" + } + ) +} +export default router; \ No newline at end of file diff --git a/server/v1/models/downloadModel.ts b/server/v1/models/downloadModel.ts new file mode 100644 index 000000000..d564a2207 --- /dev/null +++ b/server/v1/models/downloadModel.ts @@ -0,0 +1,23 @@ +import { RouteHandlerMethod, FastifyRequest, FastifyReply } from 'fastify' +import { MODEL_FOLDER_PATH } from "./index" +import fs from 'fs/promises' + +const controller: RouteHandlerMethod = async (req: FastifyRequest, res: FastifyReply) => { + //TODO: download models impl + //Mirror logic from JanModelExtension.downloadModel? + let model = req.body.model; + + // Fetching logic + // const directoryPath = join(MODEL_FOLDER_PATH, model.id) + // await fs.mkdir(directoryPath) + + // const path = join(directoryPath, model.id) + // downloadFile(model.source_url, path) + // TODO: Different model downloader from different model vendor + + res.status(200).send({ + status: "Ok" + }) +} + +export default controller; \ No newline at end of file diff --git a/server/v1/models/index.ts b/server/v1/models/index.ts new file mode 100644 index 000000000..22c551300 --- /dev/null +++ b/server/v1/models/index.ts @@ -0,0 +1,61 @@ + +export const MODEL_FOLDER_PATH = "./data/models" +export const _modelMetadataFileName = 'model.json' + +import fs from 'fs/promises' +import { Model } from '@janhq/core' +import { join } from 'path' + +// map string => model object +let modelIndex = new Map(); +async function buildModelIndex(){ + let modelIds = await fs.readdir(MODEL_FOLDER_PATH); + // TODO: read modelFolders to get model info, mirror JanModelExtension? + try{ + for(let modelId in modelIds){ + let path = join(MODEL_FOLDER_PATH, modelId) + let fileData = await fs.readFile(join(path, _modelMetadataFileName)) + modelIndex.set(modelId, JSON.parse(fileData.toString("utf-8")) as Model) + } + } + catch(err){ + console.error("build model index failed. ", err); + } +} +buildModelIndex() + +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' +import downloadModelController from './downloadModel' +import { startModel, stopModel } from './modelOp' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers declaration here + + ///////////// CRUD //////////////// + // Model listing + app.get("/", async (req, res) => { + res.status(200).send( + modelIndex.values() + ) + }) + + // Retrieve model info + app.get("/:id", (req, res) => { + res.status(200).send( + modelIndex.get(req.params.id) + ) + }) + + // Delete model + app.delete("/:id", (req, res) => { + modelIndex.delete(req.params) + + // TODO: delete on disk + }) + + ///////////// Other ops //////////////// + app.post("/", downloadModelController) + app.put("/start", startModel) + app.put("/stop", stopModel) +} +export default router; \ No newline at end of file diff --git a/server/v1/models/modelOp.ts b/server/v1/models/modelOp.ts new file mode 100644 index 000000000..f2c7ffe75 --- /dev/null +++ b/server/v1/models/modelOp.ts @@ -0,0 +1,11 @@ +import {FastifyRequest, FastifyReply} from 'fastify' + +export async function startModel(req: FastifyRequest, res: FastifyReply): Promise { + + +} + +export async function stopModel(req: FastifyRequest, res: FastifyReply): Promise { + + +} \ No newline at end of file diff --git a/server/v1/threads/index.ts b/server/v1/threads/index.ts new file mode 100644 index 000000000..e63f9e8d8 --- /dev/null +++ b/server/v1/threads/index.ts @@ -0,0 +1,8 @@ +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers declaration here + + // app.get() +} +export default router; \ No newline at end of file