Merge pull request #2131 from janhq/dev

Sync dev branch to docs branch
This commit is contained in:
Henry 2024-02-23 16:31:35 +09:00 committed by GitHub
commit ce516f83b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
369 changed files with 8766 additions and 4538 deletions

View File

@ -1,4 +1,4 @@
{
"name": "jan",
"image": "node:20"
}
"name": "jan",
"image": "node:20"
}

View File

@ -1,5 +1,6 @@
name: Jan Electron Linter & Test
on:
workflow_dispatch:
push:
branches:
- main

View File

@ -0,0 +1,40 @@
name: Jan Build Docker Nightly or Manual
on:
push:
branches:
- main
- feature/helmchart-and-ci-jan-server
paths-ignore:
- 'README.md'
- 'docs/**'
schedule:
- cron: '0 20 * * 1,2,3' # At 8 PM UTC on Monday, Tuesday, and Wednesday which is 3 AM UTC+7 Tuesday, Wednesday, and Thursday
workflow_dispatch:
jobs:
# Job create Update app version based on latest release tag with build number and save to output
get-update-version:
uses: ./.github/workflows/template-get-update-version.yml
build-cpu:
uses: ./.github/workflows/template-build-jan-server.yml
permissions:
packages: write
secrets: inherit
needs: [get-update-version]
with:
dockerfile_path: ./Dockerfile
docker_image_tag: "ghcr.io/janhq/jan-server:dev-cpu-latest,ghcr.io/janhq/jan-server:dev-cpu-${{ needs.get-update-version.outputs.new_version }}"
build-gpu:
uses: ./.github/workflows/template-build-jan-server.yml
permissions:
packages: write
secrets: inherit
needs: [get-update-version]
with:
dockerfile_path: ./Dockerfile.gpu
docker_image_tag: "ghcr.io/janhq/jan-server:dev-cuda-12.2-latest,ghcr.io/janhq/jan-server:dev-cuda-12.2-${{ needs.get-update-version.outputs.new_version }}"

30
.github/workflows/jan-server-build.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Jan Build Docker
on:
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+"]
jobs:
# Job create Update app version based on latest release tag with build number and save to output
get-update-version:
uses: ./.github/workflows/template-get-update-version.yml
build-cpu:
permissions:
packages: write
uses: ./.github/workflows/template-build-jan-server.yml
secrets: inherit
needs: [get-update-version]
with:
dockerfile_path: ./Dockerfile
docker_image_tag: "ghcr.io/janhq/jan-server:cpu-latest,ghcr.io/janhq/jan-server:cpu-${{ needs.get-update-version.outputs.new_version }}"
build-gpu:
permissions:
packages: write
uses: ./.github/workflows/template-build-jan-server.yml
secrets: inherit
needs: [get-update-version]
with:
dockerfile_path: ./Dockerfile.gpu
docker_image_tag: "ghcr.io/janhq/jan-server:cuda-12.2-latest,ghcr.io/janhq/jan-server:cuda-12.2-${{ needs.get-update-version.outputs.new_version }}"

View File

@ -0,0 +1,39 @@
name: build-jan-server
on:
workflow_call:
inputs:
dockerfile_path:
required: false
type: string
default: './Dockerfile'
docker_image_tag:
required: true
type: string
default: 'ghcr.io/janhq/jan-server:dev-latest'
jobs:
build:
runs-on: ubuntu-latest
env:
REGISTRY: ghcr.io
IMAGE_NAME: janhq/jan-server
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
file: ${{ inputs.dockerfile_path }}
push: true
tags: ${{ inputs.docker_image_tag }}

4
.gitignore vendored
View File

@ -5,6 +5,7 @@
error.log
node_modules
*.tgz
!charts/server/charts/*.tgz
yarn.lock
dist
build
@ -28,4 +29,5 @@ extensions/inference-nitro-extension/bin/*/*.exp
extensions/inference-nitro-extension/bin/*/*.lib
extensions/inference-nitro-extension/bin/saved-*
extensions/inference-nitro-extension/bin/*.tar.gz
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
extensions/inference-nitro-extension/bin/vulkaninfo

View File

@ -1,39 +1,61 @@
FROM node:20-bullseye AS base
FROM node:20-bookworm AS base
# 1. Install dependencies only when needed
FROM base AS deps
FROM base AS builder
# Install g++ 11
RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel && rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN yarn install
COPY . ./
RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \
jq --arg nitroVersion $NITRO_VERSION '(.scripts."downloadnitro:linux" | gsub("\\${NITRO_VERSION}"; $nitroVersion)) | gsub("\r"; "")' extensions/inference-nitro-extension/package.json > /tmp/newcommand.txt && export NEW_COMMAND=$(sed 's/^"//;s/"$//' /tmp/newcommand.txt) && jq --arg newCommand "$NEW_COMMAND" '.scripts."downloadnitro:linux" = $newCommand' extensions/inference-nitro-extension/package.json > /tmp/package.json && mv /tmp/package.json extensions/inference-nitro-extension/package.json
RUN make install-and-build
# # 2. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# This will do the trick, use the corresponding env file for each environment.
RUN yarn workspace server install
RUN yarn server:prod
# 3. Production image, copy all the files and run next
FROM base AS runner
# Install g++ 11
RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel && rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV NODE_ENV=production
# Copy the package.json and yarn.lock of root yarn space to leverage Docker cache
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules/
COPY --from=builder /app/yarn.lock ./yarn.lock
# RUN addgroup -g 1001 -S nodejs;
COPY --from=builder /app/server/build ./
# Copy the package.json, yarn.lock, and build output of server yarn space to leverage Docker cache
COPY --from=builder /app/core ./core/
COPY --from=builder /app/server ./server/
RUN cd core && yarn install && yarn run build
RUN yarn workspace @janhq/server install && yarn workspace @janhq/server build
COPY --from=builder /app/docs/openapi ./docs/openapi/
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/server/node_modules ./node_modules
COPY --from=builder /app/server/package.json ./package.json
# Copy pre-install dependencies
COPY --from=builder /app/pre-install ./pre-install/
EXPOSE 4000 3928
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
COPY --from=builder /app/uikit ./uikit/
COPY --from=builder /app/web ./web/
COPY --from=builder /app/models ./models/
ENV PORT 4000
ENV APPDATA /app/data
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
RUN yarn workspace jan-web install
CMD ["node", "main.js"]
RUN npm install -g serve@latest
EXPOSE 1337 3000 3928
ENV JAN_API_HOST 0.0.0.0
ENV JAN_API_PORT 1337
ENV API_BASE_URL http://localhost:1337
CMD ["sh", "-c", "export NODE_ENV=production && yarn workspace jan-web build && cd web && npx serve out & cd server && node build/main.js"]
# docker build -t jan .
# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 jan

88
Dockerfile.gpu Normal file
View File

@ -0,0 +1,88 @@
# Please change the base image to the appropriate CUDA version base on NVIDIA Driver Compatibility
# Run nvidia-smi to check the CUDA version and the corresponding driver version
# Then update the base image to the appropriate CUDA version refer https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda/tags
FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 AS base
# 1. Install dependencies only when needed
FROM base AS builder
# Install g++ 11
RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel curl gnupg make python3-dev && curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt install nodejs -y && rm -rf /var/lib/apt/lists/*
# Update alternatives for GCC and related tools
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 \
--slave /usr/bin/g++ g++ /usr/bin/g++-11 \
--slave /usr/bin/gcov gcov /usr/bin/gcov-11 \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-11 \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-11 && \
update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-11 110
RUN npm install -g yarn
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY . ./
RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \
jq --arg nitroVersion $NITRO_VERSION '(.scripts."downloadnitro:linux" | gsub("\\${NITRO_VERSION}"; $nitroVersion)) | gsub("\r"; "")' extensions/inference-nitro-extension/package.json > /tmp/newcommand.txt && export NEW_COMMAND=$(sed 's/^"//;s/"$//' /tmp/newcommand.txt) && jq --arg newCommand "$NEW_COMMAND" '.scripts."downloadnitro:linux" = $newCommand' extensions/inference-nitro-extension/package.json > /tmp/package.json && mv /tmp/package.json extensions/inference-nitro-extension/package.json
RUN make install-and-build
# # 2. Rebuild the source code only when needed
FROM base AS runner
# Install g++ 11
RUN apt update && apt install -y gcc-11 g++-11 cpp-11 jq xsel curl gnupg make python3-dev && curl -sL https://deb.nodesource.com/setup_20.x | bash - && apt-get install nodejs -y && rm -rf /var/lib/apt/lists/*
# Update alternatives for GCC and related tools
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 \
--slave /usr/bin/g++ g++ /usr/bin/g++-11 \
--slave /usr/bin/gcov gcov /usr/bin/gcov-11 \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-11 \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-11 && \
update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-11 110
RUN npm install -g yarn
WORKDIR /app
# Copy the package.json and yarn.lock of root yarn space to leverage Docker cache
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules/
COPY --from=builder /app/yarn.lock ./yarn.lock
# Copy the package.json, yarn.lock, and build output of server yarn space to leverage Docker cache
COPY --from=builder /app/core ./core/
COPY --from=builder /app/server ./server/
RUN cd core && yarn install && yarn run build
RUN yarn workspace @janhq/server install && yarn workspace @janhq/server build
COPY --from=builder /app/docs/openapi ./docs/openapi/
# Copy pre-install dependencies
COPY --from=builder /app/pre-install ./pre-install/
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
COPY --from=builder /app/uikit ./uikit/
COPY --from=builder /app/web ./web/
COPY --from=builder /app/models ./models/
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
RUN yarn workspace jan-web install
RUN npm install -g serve@latest
EXPOSE 1337 3000 3928
ENV LD_LIBRARY_PATH=/usr/local/cuda/targets/x86_64-linux/lib:/usr/local/cuda-12.0/compat${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
ENV JAN_API_HOST 0.0.0.0
ENV JAN_API_PORT 1337
ENV API_BASE_URL http://localhost:1337
CMD ["sh", "-c", "export NODE_ENV=production && yarn workspace jan-web build && cd web && npx serve out & cd server && node build/main.js"]
# pre-requisites: nvidia-docker
# docker build -t jan-gpu . -f Dockerfile.gpu
# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 --gpus all jan-gpu

View File

@ -24,9 +24,9 @@ endif
check-file-counts: install-and-build
ifeq ($(OS),Windows_NT)
powershell -Command "if ((Get-ChildItem -Path electron/pre-install -Filter *.tgz | Measure-Object | Select-Object -ExpandProperty Count) -ne (Get-ChildItem -Path extensions -Directory | Measure-Object | Select-Object -ExpandProperty Count)) { Write-Host 'Number of .tgz files in electron/pre-install does not match the number of subdirectories in extension'; exit 1 } else { Write-Host 'Extension build successful' }"
powershell -Command "if ((Get-ChildItem -Path pre-install -Filter *.tgz | Measure-Object | Select-Object -ExpandProperty Count) -ne (Get-ChildItem -Path extensions -Directory | Measure-Object | Select-Object -ExpandProperty Count)) { Write-Host 'Number of .tgz files in pre-install does not match the number of subdirectories in extension'; exit 1 } else { Write-Host 'Extension build successful' }"
else
@tgz_count=$$(find electron/pre-install -type f -name "*.tgz" | wc -l); dir_count=$$(find extensions -mindepth 1 -maxdepth 1 -type d | wc -l); if [ $$tgz_count -ne $$dir_count ]; then echo "Number of .tgz files in electron/pre-install ($$tgz_count) does not match the number of subdirectories in extension ($$dir_count)"; exit 1; else echo "Extension build successful"; fi
@tgz_count=$$(find pre-install -type f -name "*.tgz" | wc -l); dir_count=$$(find extensions -mindepth 1 -maxdepth 1 -type d | wc -l); if [ $$tgz_count -ne $$dir_count ]; then echo "Number of .tgz files in pre-install ($$tgz_count) does not match the number of subdirectories in extension ($$dir_count)"; exit 1; else echo "Extension build successful"; fi
endif
dev: check-file-counts
@ -52,18 +52,28 @@ build: check-file-counts
clean:
ifeq ($(OS),Windows_NT)
powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist -Recurse -Directory | Remove-Item -Recurse -Force"
powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist, build, out -Recurse -Directory | Remove-Item -Recurse -Force"
powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz"
powershell -Command "Remove-Item -Recurse -Force ./electron/pre-install/*.tgz"
rmdir /s /q "%USERPROFILE%\jan\extensions"
else ifeq ($(shell uname -s),Linux)
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
find . -name ".next" -type d -exec rm -rf '{}' +
find . -name "dist" -type d -exec rm -rf '{}' +
find . -name "build" -type d -exec rm -rf '{}' +
find . -name "out" -type d -exec rm -rf '{}' +
rm -rf ./pre-install/*.tgz
rm -rf ./electron/pre-install/*.tgz
rm -rf "~/jan/extensions"
rm -rf "~/.cache/jan*"
else
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
find . -name ".next" -type d -exec rm -rf '{}' +
find . -name "dist" -type d -exec rm -rf '{}' +
find . -name "build" -type d -exec rm -rf '{}' +
find . -name "out" -type d -exec rm -rf '{}' +
rm -rf ./pre-install/*.tgz
rm -rf ./electron/pre-install/*.tgz
rm -rf ~/jan/extensions
rm -rf ~/Library/Caches/jan*
endif

122
README.md
View File

@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
<tr style="text-align:center">
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.6-263.exe'>
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.6-285.exe'>
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
<b>jan.exe</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.6-263.dmg'>
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.6-285.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>Intel</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.6-263.dmg'>
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.6-285.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>M1/M2</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.6-263.deb'>
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.6-285.deb'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.deb</b>
</a>
</td>
<td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.6-263.AppImage'>
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.6-285.AppImage'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.AppImage</b>
</a>
@ -167,6 +167,7 @@ To reset your installation:
- Clear Application cache in `~/Library/Caches/jan`
## Requirements for running Jan
- MacOS: 13 or higher
- Windows:
- Windows 10 or higher
@ -194,17 +195,17 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
1. **Clone the repository and prepare:**
```bash
git clone https://github.com/janhq/jan
cd jan
git checkout -b DESIRED_BRANCH
```
```bash
git clone https://github.com/janhq/jan
cd jan
git checkout -b DESIRED_BRANCH
```
2. **Run development and use Jan Desktop**
```bash
make dev
```
```bash
make dev
```
This will start the development server and open the desktop app.
@ -218,6 +219,101 @@ make build
This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder.
### Docker mode
- Supported OS: Linux, WSL2 Docker
- Pre-requisites:
- Docker Engine and Docker Compose are required to run Jan in Docker mode. Follow the [instructions](https://docs.docker.com/engine/install/ubuntu/) below to get started with Docker Engine on Ubuntu.
```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh --dry-run
```
- If you intend to run Jan in GPU mode, you need to install `nvidia-driver` and `nvidia-docker2`. Follow the instruction [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) for installation.
- Run Jan in Docker mode
| Docker compose Profile | Description |
| ---------------------- | -------------------------------------------- |
| `cpu-fs` | Run Jan in CPU mode with default file system |
| `cpu-s3fs` | Run Jan in CPU mode with S3 file system |
| `gpu-fs` | Run Jan in GPU mode with default file system |
| `gpu-s3fs` | Run Jan in GPU mode with S3 file system |
| Environment Variable | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| `S3_BUCKET_NAME` | S3 bucket name - leave blank for default file system |
| `AWS_ACCESS_KEY_ID` | AWS access key ID - leave blank for default file system |
| `AWS_SECRET_ACCESS_KEY` | AWS secret access key - leave blank for default file system |
| `AWS_ENDPOINT` | AWS endpoint URL - leave blank for default file system |
| `AWS_REGION` | AWS region - leave blank for default file system |
| `API_BASE_URL` | Jan Server URL, please modify it as your public ip address or domain name default http://localhost:1377 |
- **Option 1**: Run Jan in CPU mode
```bash
# cpu mode with default file system
docker compose --profile cpu-fs up -d
# cpu mode with S3 file system
docker compose --profile cpu-s3fs up -d
```
- **Option 2**: Run Jan in GPU mode
- **Step 1**: Check CUDA compatibility with your NVIDIA driver by running `nvidia-smi` and check the CUDA version in the output
```bash
nvidia-smi
# Output
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 531.18 Driver Version: 531.18 CUDA Version: 12.1 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce RTX 4070 Ti WDDM | 00000000:01:00.0 On | N/A |
| 0% 44C P8 16W / 285W| 1481MiB / 12282MiB | 2% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
| 1 NVIDIA GeForce GTX 1660 Ti WDDM | 00000000:02:00.0 Off | N/A |
| 0% 49C P8 14W / 120W| 0MiB / 6144MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
| 2 NVIDIA GeForce GTX 1660 Ti WDDM | 00000000:05:00.0 Off | N/A |
| 29% 38C P8 11W / 120W| 0MiB / 6144MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
```
- **Step 2**: Visit [NVIDIA NGC Catalog ](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/cuda/tags) and find the smallest minor version of image tag that matches your CUDA version (e.g., 12.1 -> 12.1.0)
- **Step 3**: Update the `Dockerfile.gpu` line number 5 with the latest minor version of the image tag from step 2 (e.g. change `FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04 AS base` to `FROM nvidia/cuda:12.1.0-runtime-ubuntu22.04 AS base`)
- **Step 4**: Run command to start Jan in GPU mode
```bash
# GPU mode with default file system
docker compose --profile gpu up -d
# GPU mode with S3 file system
docker compose --profile gpu-s3fs up -d
```
This will start the web server and you can access Jan at `http://localhost:3000`.
> Note: RAG feature is not supported in Docker mode with s3fs yet.
## Acknowledgements
Jan builds on top of other open-source projects:

6
charts/server/Chart.lock Normal file
View File

@ -0,0 +1,6 @@
dependencies:
- name: common
repository: oci://ghcr.io/janhq/charts
version: 0.1.2
digest: sha256:35e98bde174130787755b0f8ea2359b7b6790d965a7157c2f7cabf1bc8c04471
generated: "2024-02-20T16:20:37.6530108+07:00"

10
charts/server/Chart.yaml Normal file
View File

@ -0,0 +1,10 @@
apiVersion: v2
name: jan-server
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: '1.0.0'
dependencies:
- name: common
version: 0.1.2 # common-chart-version
repository: oci://ghcr.io/janhq/charts

Binary file not shown.

View File

@ -0,0 +1,4 @@
{
"image-list": "server=ghcr.io/janhq/jan-server",
"platforms": "linux/amd64"
}

256
charts/server/values.yaml Normal file
View File

@ -0,0 +1,256 @@
common:
imageTag: v0.4.6-cpu
# DO NOT CHANGE THE LINE ABOVE. MAKE ALL CHANGES BELOW
# Global pvc for all workload
pvc:
enabled: false
name: 'janroot'
accessModes: 'ReadWriteOnce'
storageClassName: ''
capacity: '50Gi'
# Global image pull secret
imagePullSecrets: []
externalSecret:
create: false
name: ''
annotations: {}
nameOverride: 'jan-server'
fullnameOverride: 'jan-server'
serviceAccount:
create: true
annotations: {}
name: 'jan-server-service-account'
podDisruptionBudget:
create: false
minAvailable: 1
workloads:
- name: server
image:
repository: ghcr.io/janhq/jan-server
pullPolicy: Always
command: ['/bin/sh', '-c']
args: ['cd server && node build/main.js']
replicaCount: 1
ports:
containerPort: 1337
strategy:
canary:
steps:
- setWeight: 50
- pause: { duration: 1m }
ingress:
enabled: true
className: 'nginx'
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: '100m'
nginx.ingress.kubernetes.io/proxy-read-timeout: '1800'
nginx.ingress.kubernetes.io/proxy-send-timeout: '1800'
# cert-manager.io/cluster-issuer: 'jan-ai-dns01-cluster-issuer'
# nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
nginx.ingress.kubernetes.io/backend-protocol: HTTP
hosts:
- host: server.local
paths:
- path: /
pathType: Prefix
tls:
[]
# - hosts:
# - server-dev.jan.ai
# secretName: jan-server-prod-tls-v2
instrumentation:
enabled: false
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
service:
extenalLabel: {}
type: ClusterIP
port: 1337
targetPort: 1337
# If you want to use GPU, please uncomment the following lines and change imageTag to the one with GPU support
resources:
# limits:
# nvidia.com/gpu: 1
requests:
cpu: 2000m
memory: 8192M
# If you want to use pv, please uncomment the following lines and enable pvc.enabled
volumes:
[]
# - name: janroot
# persistentVolumeClaim:
# claimName: janroot
volumeMounts:
[]
# - name: janroot
# mountPath: /app/server/build/jan
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET_NAME, AWS_ENDPOINT, AWS_REGION should mount as a secret env instead of plain text here
# Change API_BASE_URL to your server's public domain
env:
- name: API_BASE_URL
value: 'http://server.local'
lifecycle: {}
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 3
targetCPUUtilizationPercentage: 95
targetMemoryUtilizationPercentage: 95
kedaScaling:
enabled: false # ignore if autoscaling.enable = true
cooldownPeriod: 30
pollingInterval: 2
minReplicas: 1
maxReplicas: 5
metricName: celery_queue_length
query: celery_queue_length{queue_name="myqueue"} # change queue_name here
serverAddress: http://prometheus-prod-kube-prome-prometheus.monitoring.svc:9090
threshold: '3'
nodeSelector: {}
tolerations: []
podSecurityGroup:
enabled: false
securitygroupid: []
# Reloader Option
reloader: 'false'
vpa:
enabled: false
- name: web
image:
repository: ghcr.io/janhq/jan-server
pullPolicy: Always
command: ['/bin/sh', '-c']
args:
[
'export NODE_ENV=production && yarn workspace jan-web build && cd web && npx serve out',
]
replicaCount: 1
ports:
containerPort: 3000
strategy:
canary:
steps:
- setWeight: 50
- pause: { duration: 1m }
ingress:
enabled: true
className: 'nginx'
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: '100m'
nginx.ingress.kubernetes.io/proxy-read-timeout: '1800'
nginx.ingress.kubernetes.io/proxy-send-timeout: '1800'
# cert-manager.io/cluster-issuer: 'jan-ai-dns01-cluster-issuer'
# nginx.ingress.kubernetes.io/force-ssl-redirect: 'true'
nginx.ingress.kubernetes.io/backend-protocol: HTTP
hosts:
- host: web.local
paths:
- path: /
pathType: Prefix
tls:
[]
# - hosts:
# - server-dev.jan.ai
# secretName: jan-server-prod-tls-v2
instrumentation:
enabled: false
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
service:
extenalLabel: {}
type: ClusterIP
port: 3000
targetPort: 3000
resources:
limits:
cpu: 1000m
memory: 2048M
requests:
cpu: 50m
memory: 500M
volumes:
[]
# - name: janroot
# persistentVolumeClaim:
# claimName: janroot
volumeMounts:
[]
# - name: janroot
# mountPath: /app/server/build/jan
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET_NAME, AWS_ENDPOINT, AWS_REGION should mount as a secret env instead of plain text here
# Change API_BASE_URL to your server's public domain
env:
- name: API_BASE_URL
value: 'http://server.local'
lifecycle: {}
autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 95
targetMemoryUtilizationPercentage: 95
kedaScaling:
enabled: false # ignore if autoscaling.enable = true
cooldownPeriod: 30
pollingInterval: 2
minReplicas: 1
maxReplicas: 5
metricName: celery_queue_length
query: celery_queue_length{queue_name="myqueue"} # change queue_name here
serverAddress: http://prometheus-prod-kube-prome-prometheus.monitoring.svc:9090
threshold: '3'
nodeSelector: {}
tolerations: []
podSecurityGroup:
enabled: false
securitygroupid: []
# Reloader Option
reloader: 'false'
vpa:
enabled: false

View File

@ -4,4 +4,4 @@ module.exports = {
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
},
}
}

View File

@ -57,6 +57,7 @@
"rollup-plugin-typescript2": "^0.36.0",
"ts-jest": "^26.1.1",
"tslib": "^2.6.2",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"rimraf": "^3.0.2"
}
}

View File

@ -54,7 +54,8 @@ export default [
'url',
'http',
'os',
'util'
'util',
'child_process',
],
watch: {
include: 'src/node/**',

View File

@ -1,15 +1,22 @@
/**
* Native Route APIs
* @description Enum of all the routes exposed by the app
*/
export enum NativeRoute {
openExternalUrl = 'openExternalUrl',
openAppDirectory = 'openAppDirectory',
openFileExplore = 'openFileExplorer',
selectDirectory = 'selectDirectory',
relaunch = 'relaunch',
}
/**
* App Route APIs
* @description Enum of all the routes exposed by the app
*/
export enum AppRoute {
openExternalUrl = 'openExternalUrl',
openAppDirectory = 'openAppDirectory',
openFileExplore = 'openFileExplorer',
selectDirectory = 'selectDirectory',
getAppConfigurations = 'getAppConfigurations',
updateAppConfiguration = 'updateAppConfiguration',
relaunch = 'relaunch',
joinPath = 'joinPath',
isSubdirectory = 'isSubdirectory',
baseName = 'baseName',
@ -30,6 +37,7 @@ export enum DownloadRoute {
downloadFile = 'downloadFile',
pauseDownload = 'pauseDownload',
resumeDownload = 'resumeDownload',
getDownloadProgress = 'getDownloadProgress',
}
export enum DownloadEvent {
@ -68,6 +76,10 @@ export enum FileManagerRoute {
export type ApiFunction = (...args: any[]) => any
export type NativeRouteFunctions = {
[K in NativeRoute]: ApiFunction
}
export type AppRouteFunctions = {
[K in AppRoute]: ApiFunction
}
@ -96,7 +108,8 @@ export type FileManagerRouteFunctions = {
[K in FileManagerRoute]: ApiFunction
}
export type APIFunctions = AppRouteFunctions &
export type APIFunctions = NativeRouteFunctions &
AppRouteFunctions &
AppEventFunctions &
DownloadRouteFunctions &
DownloadEventFunctions &
@ -104,11 +117,13 @@ export type APIFunctions = AppRouteFunctions &
FileSystemRouteFunctions &
FileManagerRoute
export const APIRoutes = [
export const CoreRoutes = [
...Object.values(AppRoute),
...Object.values(DownloadRoute),
...Object.values(ExtensionRoute),
...Object.values(FileSystemRoute),
...Object.values(FileManagerRoute),
]
export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)]
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]

View File

@ -1,13 +1,13 @@
export enum ExtensionTypeEnum {
Assistant = "assistant",
Conversational = "conversational",
Inference = "inference",
Model = "model",
SystemMonitoring = "systemMonitoring",
Assistant = 'assistant',
Conversational = 'conversational',
Inference = 'inference',
Model = 'model',
SystemMonitoring = 'systemMonitoring',
}
export interface ExtensionType {
type(): ExtensionTypeEnum | undefined;
type(): ExtensionTypeEnum | undefined
}
/**
* Represents a base extension.
@ -20,16 +20,16 @@ export abstract class BaseExtension implements ExtensionType {
* Undefined means its not extending any known extension by the application.
*/
type(): ExtensionTypeEnum | undefined {
return undefined;
return undefined
}
/**
* Called when the extension is loaded.
* Any initialization logic for the extension should be put here.
*/
abstract onLoad(): void;
abstract onLoad(): void
/**
* Called when the extension is unloaded.
* Any cleanup logic for the extension should be put here.
*/
abstract onUnload(): void;
abstract onUnload(): void
}

View File

@ -1,5 +1,5 @@
import { Assistant, AssistantInterface } from "../index";
import { BaseExtension, ExtensionTypeEnum } from "../extension";
import { Assistant, AssistantInterface } from '../index'
import { BaseExtension, ExtensionTypeEnum } from '../extension'
/**
* Assistant extension for managing assistants.
@ -10,10 +10,10 @@ export abstract class AssistantExtension extends BaseExtension implements Assist
* Assistant extension type.
*/
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.Assistant;
return ExtensionTypeEnum.Assistant
}
abstract createAssistant(assistant: Assistant): Promise<void>;
abstract deleteAssistant(assistant: Assistant): Promise<void>;
abstract getAssistants(): Promise<Assistant[]>;
abstract createAssistant(assistant: Assistant): Promise<void>
abstract deleteAssistant(assistant: Assistant): Promise<void>
abstract getAssistants(): Promise<Assistant[]>
}

View File

@ -14,7 +14,7 @@ export abstract class ConversationalExtension
* Conversation extension type.
*/
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.Conversational;
return ExtensionTypeEnum.Conversational
}
abstract getThreads(): Promise<Thread[]>

View File

@ -2,24 +2,24 @@
* Conversational extension. Persists and retrieves conversations.
* @module
*/
export { ConversationalExtension } from "./conversational";
export { ConversationalExtension } from './conversational'
/**
* Inference extension. Start, stop and inference models.
*/
export { InferenceExtension } from "./inference";
export { InferenceExtension } from './inference'
/**
* Monitoring extension for system monitoring.
*/
export { MonitoringExtension } from "./monitoring";
export { MonitoringExtension } from './monitoring'
/**
* Assistant extension for managing assistants.
*/
export { AssistantExtension } from "./assistant";
export { AssistantExtension } from './assistant'
/**
* Model extension for managing models.
*/
export { ModelExtension } from "./model";
export { ModelExtension } from './model'

View File

@ -1,5 +1,5 @@
import { InferenceInterface, MessageRequest, ThreadMessage } from "../index";
import { BaseExtension, ExtensionTypeEnum } from "../extension";
import { InferenceInterface, MessageRequest, ThreadMessage } from '../index'
import { BaseExtension, ExtensionTypeEnum } from '../extension'
/**
* Inference extension. Start, stop and inference models.
@ -9,8 +9,8 @@ export abstract class InferenceExtension extends BaseExtension implements Infere
* Inference extension type.
*/
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.Inference;
return ExtensionTypeEnum.Inference
}
abstract inference(data: MessageRequest): Promise<ThreadMessage>;
abstract inference(data: MessageRequest): Promise<ThreadMessage>
}

View File

@ -1,5 +1,5 @@
import { BaseExtension, ExtensionTypeEnum } from "../extension";
import { Model, ModelInterface } from "../index";
import { BaseExtension, ExtensionTypeEnum } from '../extension'
import { Model, ModelInterface } from '../index'
/**
* Model extension for managing models.
@ -9,16 +9,16 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter
* Model extension type.
*/
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.Model;
return ExtensionTypeEnum.Model
}
abstract downloadModel(
model: Model,
network?: { proxy: string; ignoreSSL?: boolean },
): Promise<void>;
abstract cancelModelDownload(modelId: string): Promise<void>;
abstract deleteModel(modelId: string): Promise<void>;
abstract saveModel(model: Model): Promise<void>;
abstract getDownloadedModels(): Promise<Model[]>;
abstract getConfiguredModels(): Promise<Model[]>;
network?: { proxy: string; ignoreSSL?: boolean }
): Promise<void>
abstract cancelModelDownload(modelId: string): Promise<void>
abstract deleteModel(modelId: string): Promise<void>
abstract saveModel(model: Model): Promise<void>
abstract getDownloadedModels(): Promise<Model[]>
abstract getConfiguredModels(): Promise<Model[]>
}

View File

@ -1,5 +1,5 @@
import { BaseExtension, ExtensionTypeEnum } from "../extension";
import { MonitoringInterface } from "../index";
import { BaseExtension, ExtensionTypeEnum } from '../extension'
import { MonitoringInterface } from '../index'
/**
* Monitoring extension for system monitoring.
@ -10,9 +10,9 @@ export abstract class MonitoringExtension extends BaseExtension implements Monit
* Monitoring extension type.
*/
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.SystemMonitoring;
return ExtensionTypeEnum.SystemMonitoring
}
abstract getResourcesInfo(): Promise<any>;
abstract getCurrentLoad(): Promise<any>;
abstract getResourcesInfo(): Promise<any>
abstract getCurrentLoad(): Promise<any>
}

View File

@ -38,3 +38,10 @@ export * from './extension'
* @module
*/
export * from './extensions/index'
/**
* Declare global object
*/
declare global {
var core: any | undefined
}

View File

@ -0,0 +1,43 @@
import {
AppRoute,
DownloadRoute,
ExtensionRoute,
FileManagerRoute,
FileSystemRoute,
} from '../../../api'
import { Downloader } from '../processors/download'
import { FileSystem } from '../processors/fs'
import { Extension } from '../processors/extension'
import { FSExt } from '../processors/fsExt'
import { App } from '../processors/app'
export class RequestAdapter {
downloader: Downloader
fileSystem: FileSystem
extension: Extension
fsExt: FSExt
app: App
constructor(observer?: Function) {
this.downloader = new Downloader(observer)
this.fileSystem = new FileSystem()
this.extension = new Extension()
this.fsExt = new FSExt()
this.app = new App()
}
// TODO: Clearer Factory pattern here
process(route: string, ...args: any) {
if (route in DownloadRoute) {
return this.downloader.process(route, ...args)
} else if (route in FileSystemRoute) {
return this.fileSystem.process(route, ...args)
} else if (route in ExtensionRoute) {
return this.extension.process(route, ...args)
} else if (route in FileManagerRoute) {
return this.fsExt.process(route, ...args)
} else if (route in AppRoute) {
return this.app.process(route, ...args)
}
}
}

View File

@ -0,0 +1,23 @@
import { CoreRoutes } from '../../../api'
import { RequestAdapter } from './adapter'
export type Handler = (route: string, args: any) => any
export class RequestHandler {
handler: Handler
adataper: RequestAdapter
constructor(handler: Handler, observer?: Function) {
this.handler = handler
this.adataper = new RequestAdapter(observer)
}
handle() {
CoreRoutes.map((route) => {
this.handler(route, async (...args: any[]) => {
const values = await this.adataper.process(route, ...args)
return values
})
})
}
}

View File

@ -1,2 +1,3 @@
export * from './HttpServer'
export * from './routes'
export * from './restful/v1'
export * from './common/handler'

View File

@ -0,0 +1,3 @@
export abstract class Processor {
abstract process(key: string, ...args: any[]): any
}

View File

@ -0,0 +1,96 @@
import { basename, isAbsolute, join, relative } from 'path'
import { Processor } from './Processor'
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
import { appResourcePath } from '../../helper/path'
export class App implements Processor {
observer?: Function
constructor(observer?: Function) {
this.observer = observer
}
process(key: string, ...args: any[]): any {
const instance = this as any
const func = instance[key]
return func(...args)
}
/**
* Joins multiple paths together, respect to the current OS.
*/
joinPath(args: any[]) {
return join(...args)
}
/**
* Checks if the given path is a subdirectory of the given directory.
*
* @param _event - The IPC event object.
* @param from - The path to check.
* @param to - The directory to check against.
*
* @returns {Promise<boolean>} - A promise that resolves with the result.
*/
isSubdirectory(from: any, to: any) {
const rel = relative(from, to)
const isSubdir = rel && !rel.startsWith('..') && !isAbsolute(rel)
if (isSubdir === '') return false
else return isSubdir
}
/**
* Retrieve basename from given path, respect to the current OS.
*/
baseName(args: any) {
return basename(args)
}
/**
* Log message to log file.
*/
log(args: any) {
writeLog(args)
}
/**
* Log message to log file.
*/
logServer(args: any) {
writeServerLog(args)
}
getAppConfigurations() {
return appConfiguration()
}
async updateAppConfiguration(args: any) {
await updateAppConfiguration(args)
}
/**
* Start Jan API Server.
*/
async startServer(args?: any) {
const { startServer } = require('@janhq/server')
return startServer({
host: args?.host,
port: args?.port,
isCorsEnabled: args?.isCorsEnabled,
isVerboseEnabled: args?.isVerboseEnabled,
schemaPath: join(await appResourcePath(), 'docs', 'openapi', 'jan.yaml'),
baseDir: join(await appResourcePath(), 'docs', 'openapi'),
})
}
/**
* Stop Jan API Server.
*/
stopServer() {
const { stopServer } = require('@janhq/server')
return stopServer()
}
}

View File

@ -0,0 +1,106 @@
import { resolve, sep } from 'path'
import { DownloadEvent } from '../../../api'
import { normalizeFilePath } from '../../helper/path'
import { getJanDataFolderPath } from '../../helper'
import { DownloadManager } from '../../helper/download'
import { createWriteStream, renameSync } from 'fs'
import { Processor } from './Processor'
import { DownloadState } from '../../../types'
export class Downloader implements Processor {
observer?: Function
constructor(observer?: Function) {
this.observer = observer
}
process(key: string, ...args: any[]): any {
const instance = this as any
const func = instance[key]
return func(this.observer, ...args)
}
downloadFile(observer: any, url: string, localPath: string, network: any) {
const request = require('request')
const progress = require('request-progress')
const strictSSL = !network?.ignoreSSL
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
if (typeof localPath === 'string') {
localPath = normalizeFilePath(localPath)
}
const array = localPath.split(sep)
const fileName = array.pop() ?? ''
const modelId = array.pop() ?? ''
const destination = resolve(getJanDataFolderPath(), localPath)
const rq = request({ url, strictSSL, proxy })
// Put request to download manager instance
DownloadManager.instance.setRequest(localPath, rq)
// Downloading file to a temp file first
const downloadingTempFile = `${destination}.download`
progress(rq, {})
.on('progress', (state: any) => {
const downloadState: DownloadState = {
...state,
modelId,
fileName,
downloadState: 'downloading',
}
console.log('progress: ', downloadState)
observer?.(DownloadEvent.onFileDownloadUpdate, downloadState)
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
})
.on('error', (error: Error) => {
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
const downloadState: DownloadState = {
...currentDownloadState,
error: error.message,
downloadState: 'error',
}
if (currentDownloadState) {
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
}
observer?.(DownloadEvent.onFileDownloadError, downloadState)
})
.on('end', () => {
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
if (currentDownloadState && DownloadManager.instance.networkRequests[localPath]) {
// Finished downloading, rename temp file to actual file
renameSync(downloadingTempFile, destination)
const downloadState: DownloadState = {
...currentDownloadState,
downloadState: 'end',
}
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
}
})
.pipe(createWriteStream(downloadingTempFile))
}
abortDownload(observer: any, fileName: string) {
const rq = DownloadManager.instance.networkRequests[fileName]
if (rq) {
DownloadManager.instance.networkRequests[fileName] = undefined
rq?.abort()
} else {
observer?.(DownloadEvent.onFileDownloadError, {
fileName,
error: 'aborted',
})
}
}
resumeDownload(observer: any, fileName: any) {
DownloadManager.instance.networkRequests[fileName]?.resume()
}
pauseDownload(observer: any, fileName: any) {
DownloadManager.instance.networkRequests[fileName]?.pause()
}
}

View File

@ -0,0 +1,88 @@
import { readdirSync } from 'fs'
import { join, extname } from 'path'
import { Processor } from './Processor'
import { ModuleManager } from '../../helper/module'
import { getJanExtensionsPath as getPath } from '../../helper'
import {
getActiveExtensions as getExtensions,
getExtension,
removeExtension,
installExtensions,
} from '../../extension/store'
import { appResourcePath } from '../../helper/path'
export class Extension implements Processor {
observer?: Function
constructor(observer?: Function) {
this.observer = observer
}
process(key: string, ...args: any[]): any {
const instance = this as any
const func = instance[key]
return func(...args)
}
invokeExtensionFunc(modulePath: string, method: string, ...params: any[]) {
const module = require(join(getPath(), modulePath))
ModuleManager.instance.setModule(modulePath, module)
if (typeof module[method] === 'function') {
return module[method](...params)
} else {
console.debug(module[method])
console.error(`Function "${method}" does not exist in the module.`)
}
}
/**
* Returns the paths of the base extensions.
* @returns An array of paths to the base extensions.
*/
async baseExtensions() {
const baseExtensionPath = join(await appResourcePath(), 'pre-install')
return readdirSync(baseExtensionPath)
.filter((file) => extname(file) === '.tgz')
.map((file) => join(baseExtensionPath, file))
}
/**MARK: Extension Manager handlers */
async installExtension(extensions: any) {
// Install and activate all provided extensions
const installed = await installExtensions(extensions)
return JSON.parse(JSON.stringify(installed))
}
// Register IPC route to uninstall a extension
async uninstallExtension(extensions: any) {
// Uninstall all provided extensions
for (const ext of extensions) {
const extension = getExtension(ext)
await extension.uninstall()
if (extension.name) removeExtension(extension.name)
}
// Reload all renderer pages if needed
return true
}
// Register IPC route to update a extension
async updateExtension(extensions: any) {
// Update all provided extensions
const updated: any[] = []
for (const ext of extensions) {
const extension = getExtension(ext)
const res = await extension.update()
if (res) updated.push(extension)
}
// Reload all renderer pages if needed
return JSON.parse(JSON.stringify(updated))
}
getActiveExtensions() {
return JSON.parse(JSON.stringify(getExtensions()))
}
}

View File

@ -0,0 +1,25 @@
import { join } from 'path'
import { normalizeFilePath } from '../../helper/path'
import { getJanDataFolderPath } from '../../helper'
import { Processor } from './Processor'
export class FileSystem implements Processor {
observer?: Function
private static moduleName = 'fs'
constructor(observer?: Function) {
this.observer = observer
}
process(route: string, ...args: any[]): any {
return import(FileSystem.moduleName).then((mdl) =>
mdl[route](
...args.map((arg: any) =>
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
? join(getJanDataFolderPath(), normalizeFilePath(arg))
: arg
)
)
)
}
}

View File

@ -0,0 +1,78 @@
import { join } from 'path'
import fs from 'fs'
import { FileManagerRoute } from '../../../api'
import { appResourcePath, normalizeFilePath } from '../../helper/path'
import { getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper'
import { Processor } from './Processor'
import { FileStat } from '../../../types'
export class FSExt implements Processor {
observer?: Function
constructor(observer?: Function) {
this.observer = observer
}
process(key: string, ...args: any): any {
const instance = this as any
const func = instance[key]
return func(...args)
}
// Handles the 'syncFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
syncFile(src: string, dest: string) {
const reflect = require('@alumna/reflect')
return reflect({
src,
dest,
recursive: true,
delete: false,
overwrite: true,
errorOnExist: false,
})
}
// Handles the 'getJanDataFolderPath' IPC event. This event is triggered to get the user space path.
getJanDataFolderPath() {
return Promise.resolve(getPath())
}
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
getResourcePath() {
return appResourcePath()
}
// Handles the 'getUserHomePath' IPC event. This event is triggered to get the user home path.
getUserHomePath() {
return process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME']
}
// handle fs is directory here
fileStat(path: string) {
const normalizedPath = normalizeFilePath(path)
const fullPath = join(getJanDataFolderPath(), normalizedPath)
const isExist = fs.existsSync(fullPath)
if (!isExist) return undefined
const isDirectory = fs.lstatSync(fullPath).isDirectory()
const size = fs.statSync(fullPath).size
const fileStat: FileStat = {
isDirectory,
size,
}
return fileStat
}
writeBlob(path: string, data: any) {
try {
const normalizedPath = normalizeFilePath(path)
const dataBuffer = Buffer.from(data, 'base64')
fs.writeFileSync(join(getJanDataFolderPath(), normalizedPath), dataBuffer)
} catch (err) {
console.error(`writeFile ${path} result: ${err}`)
}
}
}

View File

@ -0,0 +1,23 @@
import { DownloadRoute } from '../../../../api'
import { DownloadManager } from '../../../helper/download'
import { HttpServer } from '../../HttpServer'
export const downloadRouter = async (app: HttpServer) => {
app.get(`/download/${DownloadRoute.getDownloadProgress}/:modelId`, async (req, res) => {
const modelId = req.params.modelId
console.debug(`Getting download progress for model ${modelId}`)
console.debug(
`All Download progress: ${JSON.stringify(DownloadManager.instance.downloadProgressMap)}`
)
// check if null DownloadManager.instance.downloadProgressMap
if (!DownloadManager.instance.downloadProgressMap[modelId]) {
return res.status(404).send({
message: 'Download progress not found',
})
} else {
return res.status(200).send(DownloadManager.instance.downloadProgressMap[modelId])
}
})
}

View File

@ -0,0 +1,13 @@
import { HttpServer } from '../../HttpServer'
import { Handler, RequestHandler } from '../../common/handler'
export function handleRequests(app: HttpServer) {
const restWrapper: Handler = (route: string, listener: (...args: any[]) => any) => {
app.post(`/app/${route}`, async (request: any, reply: any) => {
const args = JSON.parse(request.body) as any[]
reply.send(JSON.stringify(await listener(...args)))
})
}
const handler = new RequestHandler(restWrapper)
handler.handle()
}

View File

@ -1,24 +1,34 @@
import { AppRoute } from '../../../api'
import { HttpServer } from '../HttpServer'
import { basename, join } from 'path'
import {
chatCompletions,
deleteBuilder,
downloadModel,
getBuilder,
retrieveBuilder,
} from '../common/builder'
createMessage,
createThread,
getMessages,
retrieveMessage,
updateThread,
} from './helper/builder'
import { JanApiRouteConfiguration } from '../common/configuration'
import { startModel, stopModel } from '../common/startStopModel'
import { JanApiRouteConfiguration } from './helper/configuration'
import { startModel, stopModel } from './helper/startStopModel'
import { ModelSettingParams } from '../../../types'
import { getJanDataFolderPath } from '../../utils'
import { normalizeFilePath } from '../../path'
export const commonRouter = async (app: HttpServer) => {
const normalizeData = (data: any) => {
return {
object: 'list',
data,
}
}
// Common Routes
// Read & Delete :: Threads | Models | Assistants
Object.keys(JanApiRouteConfiguration).forEach((key) => {
app.get(`/${key}`, async (_request) => getBuilder(JanApiRouteConfiguration[key]))
app.get(`/${key}`, async (_request) =>
getBuilder(JanApiRouteConfiguration[key]).then(normalizeData)
)
app.get(`/${key}/:id`, async (request: any) =>
retrieveBuilder(JanApiRouteConfiguration[key], request.params.id)
@ -29,7 +39,26 @@ export const commonRouter = async (app: HttpServer) => {
)
})
// Download Model Routes
// Threads
app.post(`/threads/`, async (req, res) => createThread(req.body))
app.get(`/threads/:threadId/messages`, async (req, res) =>
getMessages(req.params.threadId).then(normalizeData)
)
app.get(`/threads/:threadId/messages/:messageId`, async (req, res) =>
retrieveMessage(req.params.threadId, req.params.messageId)
)
app.post(`/threads/:threadId/messages`, async (req, res) =>
createMessage(req.params.threadId as any, req.body as any)
)
app.patch(`/threads/:threadId`, async (request: any) =>
updateThread(request.params.threadId, request.body)
)
// Models
app.get(`/models/download/:modelId`, async (request: any) =>
downloadModel(request.params.modelId, {
ignoreSSL: request.query.ignoreSSL === 'true',
@ -48,24 +77,6 @@ export const commonRouter = async (app: HttpServer) => {
app.put(`/models/:modelId/stop`, async (request: any) => stopModel(request.params.modelId))
// Chat Completion Routes
// Chat Completion
app.post(`/chat/completions`, async (request: any, reply: any) => chatCompletions(request, reply))
// App Routes
app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => {
const args = JSON.parse(request.body) as any[]
const paths = args[0].map((arg: string) =>
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
? join(getJanDataFolderPath(), normalizeFilePath(arg))
: arg
)
reply.send(JSON.stringify(join(...paths)))
})
app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => {
const args = JSON.parse(request.body) as any[]
reply.send(JSON.stringify(basename(args[0])))
})
}

View File

@ -1,10 +1,11 @@
import fs from 'fs'
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
import { join } from 'path'
import { ContentType, MessageStatus, Model, ThreadMessage } from './../../../index'
import { getEngineConfiguration, getJanDataFolderPath } from '../../utils'
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../index'
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
// TODO: Refactor these
export const getBuilder = async (configuration: RouteConfiguration) => {
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
try {
@ -124,7 +125,7 @@ export const getMessages = async (threadId: string): Promise<ThreadMessage[]> =>
}
}
export const retrieveMesasge = async (threadId: string, messageId: string) => {
export const retrieveMessage = async (threadId: string, messageId: string) => {
const messages = await getMessages(threadId)
const filteredMessages = messages.filter((m) => m.id === messageId)
if (!filteredMessages || filteredMessages.length === 0) {
@ -317,13 +318,6 @@ export const chatCompletions = async (request: any, reply: any) => {
apiUrl = engineConfiguration.full_url
}
reply.raw.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
})
const headers: Record<string, any> = {
'Content-Type': 'application/json',
}
@ -342,8 +336,14 @@ export const chatCompletions = async (request: any, reply: any) => {
})
if (response.status !== 200) {
console.error(response)
return
reply.code(400).send(response)
} else {
reply.raw.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
})
response.body.pipe(reply.raw)
}
}

View File

@ -1,9 +1,9 @@
import fs from 'fs'
import { join } from 'path'
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../utils'
import { logServer } from '../../log'
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
import { logServer } from '../../../helper/log'
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
import { Model, ModelSettingParams, PromptTemplate } from '../../../types'
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
import {
LOCAL_HOST,
NITRO_DEFAULT_PORT,

View File

@ -0,0 +1,16 @@
import { HttpServer } from '../HttpServer'
import { commonRouter } from './common'
import { downloadRouter } from './app/download'
import { handleRequests } from './app/handlers'
export const v1Router = async (app: HttpServer) => {
// MARK: Public API Routes
app.register(commonRouter)
// MARK: Internal Application Routes
handleRequests(app)
// Expanded route for tracking download progress
// TODO: Replace by Observer Wrapper (ZeroMQ / Vanilla Websocket)
app.register(downloadRouter)
}

View File

@ -1,58 +0,0 @@
import { DownloadRoute } from '../../../api'
import { join } from 'path'
import { DownloadManager } from '../../download'
import { HttpServer } from '../HttpServer'
import { createWriteStream } from 'fs'
import { getJanDataFolderPath } from '../../utils'
import { normalizeFilePath } from '../../path'
export const downloadRouter = async (app: HttpServer) => {
app.post(`/${DownloadRoute.downloadFile}`, async (req, res) => {
const strictSSL = !(req.query.ignoreSSL === 'true')
const proxy = req.query.proxy?.startsWith('http') ? req.query.proxy : undefined
const body = JSON.parse(req.body as any)
const normalizedArgs = body.map((arg: any) => {
if (typeof arg === 'string' && arg.startsWith('file:')) {
return join(getJanDataFolderPath(), normalizeFilePath(arg))
}
return arg
})
const localPath = normalizedArgs[1]
const fileName = localPath.split('/').pop() ?? ''
const request = require('request')
const progress = require('request-progress')
const rq = request({ url: normalizedArgs[0], strictSSL, proxy })
progress(rq, {})
.on('progress', function (state: any) {
console.log('download onProgress', state)
})
.on('error', function (err: Error) {
console.log('download onError', err)
})
.on('end', function () {
console.log('download onEnd')
})
.pipe(createWriteStream(normalizedArgs[1]))
DownloadManager.instance.setRequest(fileName, rq)
})
app.post(`/${DownloadRoute.abortDownload}`, async (req, res) => {
const body = JSON.parse(req.body as any)
const normalizedArgs = body.map((arg: any) => {
if (typeof arg === 'string' && arg.startsWith('file:')) {
return join(getJanDataFolderPath(), normalizeFilePath(arg))
}
return arg
})
const localPath = normalizedArgs[0]
const fileName = localPath.split('/').pop() ?? ''
const rq = DownloadManager.instance.networkRequests[fileName]
DownloadManager.instance.networkRequests[fileName] = undefined
rq?.abort()
})
}

View File

@ -1,49 +0,0 @@
import { join, extname } from 'path'
import { ExtensionRoute } from '../../../api/index'
import { ModuleManager } from '../../module'
import { getActiveExtensions, installExtensions } from '../../extension/store'
import { HttpServer } from '../HttpServer'
import { readdirSync } from 'fs'
import { getJanExtensionsPath } from '../../utils'
export const extensionRouter = async (app: HttpServer) => {
// TODO: Share code between node projects
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (_req, res) => {
const activeExtensions = await getActiveExtensions()
res.status(200).send(activeExtensions)
})
app.post(`/${ExtensionRoute.baseExtensions}`, async (_req, res) => {
const baseExtensionPath = join(__dirname, '..', '..', '..', 'pre-install')
const extensions = readdirSync(baseExtensionPath)
.filter((file) => extname(file) === '.tgz')
.map((file) => join(baseExtensionPath, file))
res.status(200).send(extensions)
})
app.post(`/${ExtensionRoute.installExtension}`, async (req) => {
const extensions = req.body as any
const installed = await installExtensions(JSON.parse(extensions)[0])
return JSON.parse(JSON.stringify(installed))
})
app.post(`/${ExtensionRoute.invokeExtensionFunc}`, async (req, res) => {
const args = JSON.parse(req.body as any)
console.debug(args)
const module = await import(join(getJanExtensionsPath(), args[0]))
ModuleManager.instance.setModule(args[0], module)
const method = args[1]
if (typeof module[method] === 'function') {
// remove first item from args
const newArgs = args.slice(2)
console.log(newArgs)
return module[method](...args.slice(2))
} else {
console.debug(module[method])
console.error(`Function "${method}" does not exist in the module.`)
}
})
}

View File

@ -1,14 +0,0 @@
import { FileManagerRoute } from '../../../api'
import { HttpServer } from '../../index'
export const fsRouter = async (app: HttpServer) => {
app.post(`/app/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {})
app.post(`/app/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) => {})
app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {})
app.post(`/app/${FileManagerRoute.getUserHomePath}`, async (request: any, reply: any) => {})
app.post(`/app/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {})
}

View File

@ -1,29 +0,0 @@
import { FileSystemRoute } from '../../../api'
import { join } from 'path'
import { HttpServer } from '../HttpServer'
import { getJanDataFolderPath } from '../../utils'
import { normalizeFilePath } from '../../path'
export const fsRouter = async (app: HttpServer) => {
const moduleName = 'fs'
// Generate handlers for each fs route
Object.values(FileSystemRoute).forEach((route) => {
app.post(`/${route}`, async (req, res) => {
const body = JSON.parse(req.body as any)
try {
const result = await import(moduleName).then((mdl) => {
return mdl[route](
...body.map((arg: any) =>
typeof arg === 'string' && (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`))
? join(getJanDataFolderPath(), normalizeFilePath(arg))
: arg
)
)
})
res.status(200).send(result)
} catch (ex) {
console.log(ex)
}
})
})
}

View File

@ -1,6 +0,0 @@
export * from './download'
export * from './extension'
export * from './fs'
export * from './thread'
export * from './common'
export * from './v1'

View File

@ -1,30 +0,0 @@
import { HttpServer } from '../HttpServer'
import {
createMessage,
createThread,
getMessages,
retrieveMesasge,
updateThread,
} from '../common/builder'
export const threadRouter = async (app: HttpServer) => {
// create thread
app.post(`/`, async (req, res) => createThread(req.body))
app.get(`/:threadId/messages`, async (req, res) => getMessages(req.params.threadId))
// retrieve message
app.get(`/:threadId/messages/:messageId`, async (req, res) =>
retrieveMesasge(req.params.threadId, req.params.messageId),
)
// create message
app.post(`/:threadId/messages`, async (req, res) =>
createMessage(req.params.threadId as any, req.body as any),
)
// modify thread
app.patch(`/:threadId`, async (request: any) =>
updateThread(request.params.threadId, request.body),
)
}

View File

@ -1,25 +0,0 @@
import { HttpServer } from '../HttpServer'
import { commonRouter } from './common'
import { threadRouter } from './thread'
import { fsRouter } from './fs'
import { extensionRouter } from './extension'
import { downloadRouter } from './download'
export const v1Router = async (app: HttpServer) => {
// MARK: External Routes
app.register(commonRouter)
app.register(threadRouter, {
prefix: '/threads',
})
// MARK: Internal Application Routes
app.register(fsRouter, {
prefix: '/fs',
})
app.register(extensionRouter, {
prefix: '/extension',
})
app.register(downloadRouter, {
prefix: '/download',
})
}

View File

@ -104,7 +104,7 @@ export default class Extension {
await pacote.extract(
this.specifier,
join(ExtensionManager.instance.getExtensionsPath() ?? '', this.name ?? ''),
this.installOptions,
this.installOptions
)
// Set the url using the custom extensions protocol

View File

@ -41,8 +41,8 @@ async function registerExtensionProtocol() {
console.error('Electron is not available')
}
const extensionPath = ExtensionManager.instance.getExtensionsPath()
if (electron) {
return electron.protocol.registerFileProtocol('extension', (request: any, callback: any) => {
if (electron && electron.protocol) {
return electron.protocol?.registerFileProtocol('extension', (request: any, callback: any) => {
const entry = request.url.substr('extension://'.length - 1)
const url = normalize(extensionPath + entry)
@ -69,7 +69,7 @@ export function useExtensions(extensionsPath: string) {
// Read extension list from extensions folder
const extensions = JSON.parse(
readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8'),
readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8')
)
try {
// Create and store a Extension instance for each extension in list
@ -82,7 +82,7 @@ export function useExtensions(extensionsPath: string) {
throw new Error(
'Could not successfully rebuild list of installed extensions.\n' +
error +
'\nPlease check the extensions.json file in the extensions folder.',
'\nPlease check the extensions.json file in the extensions folder.'
)
}
@ -122,7 +122,7 @@ function loadExtension(ext: any) {
export function getStore() {
if (!ExtensionManager.instance.getExtensionsFile()) {
throw new Error(
'The extension path has not yet been set up. Please run useExtensions before accessing the store',
'The extension path has not yet been set up. Please run useExtensions before accessing the store'
)
}

View File

@ -1,6 +1,6 @@
import { writeFileSync } from "fs";
import Extension from "./extension";
import { ExtensionManager } from "./manager";
import { writeFileSync } from 'fs'
import Extension from './extension'
import { ExtensionManager } from './manager'
/**
* @module store
@ -11,7 +11,7 @@ import { ExtensionManager } from "./manager";
* Register of installed extensions
* @type {Object.<string, Extension>} extension - List of installed extensions
*/
const extensions: Record<string, Extension> = {};
const extensions: Record<string, Extension> = {}
/**
* Get a extension from the stored extensions.
@ -21,10 +21,10 @@ const extensions: Record<string, Extension> = {};
*/
export function getExtension(name: string) {
if (!Object.prototype.hasOwnProperty.call(extensions, name)) {
throw new Error(`Extension ${name} does not exist`);
throw new Error(`Extension ${name} does not exist`)
}
return extensions[name];
return extensions[name]
}
/**
@ -33,7 +33,7 @@ export function getExtension(name: string) {
* @alias extensionManager.getAllExtensions
*/
export function getAllExtensions() {
return Object.values(extensions);
return Object.values(extensions)
}
/**
@ -42,7 +42,7 @@ export function getAllExtensions() {
* @alias extensionManager.getActiveExtensions
*/
export function getActiveExtensions() {
return Object.values(extensions).filter((extension) => extension.active);
return Object.values(extensions).filter((extension) => extension.active)
}
/**
@ -53,9 +53,9 @@ export function getActiveExtensions() {
* @alias extensionManager.removeExtension
*/
export function removeExtension(name: string, persist = true) {
const del = delete extensions[name];
if (persist) persistExtensions();
return del;
const del = delete extensions[name]
if (persist) persistExtensions()
return del
}
/**
@ -65,10 +65,10 @@ export function removeExtension(name: string, persist = true) {
* @returns {void}
*/
export function addExtension(extension: Extension, persist = true) {
if (extension.name) extensions[extension.name] = extension;
if (extension.name) extensions[extension.name] = extension
if (persist) {
persistExtensions();
extension.subscribe("pe-persist", persistExtensions);
persistExtensions()
extension.subscribe('pe-persist', persistExtensions)
}
}
@ -77,14 +77,11 @@ export function addExtension(extension: Extension, persist = true) {
* @returns {void}
*/
export function persistExtensions() {
const persistData: Record<string, Extension> = {};
const persistData: Record<string, Extension> = {}
for (const name in extensions) {
persistData[name] = extensions[name];
persistData[name] = extensions[name]
}
writeFileSync(
ExtensionManager.instance.getExtensionsFile(),
JSON.stringify(persistData),
);
writeFileSync(ExtensionManager.instance.getExtensionsFile(), JSON.stringify(persistData))
}
/**
@ -94,26 +91,29 @@ export function persistExtensions() {
* @returns {Promise.<Array.<Extension>>} New extension
* @alias extensionManager.installExtensions
*/
export async function installExtensions(extensions: any, store = true) {
const installed: Extension[] = [];
export async function installExtensions(extensions: any) {
const installed: Extension[] = []
for (const ext of extensions) {
// Set install options and activation based on input type
const isObject = typeof ext === "object";
const spec = isObject ? [ext.specifier, ext] : [ext];
const activate = isObject ? ext.activate !== false : true;
const isObject = typeof ext === 'object'
const spec = isObject ? [ext.specifier, ext] : [ext]
const activate = isObject ? ext.activate !== false : true
// Install and possibly activate extension
const extension = new Extension(...spec);
await extension._install();
if (activate) extension.setActive(true);
const extension = new Extension(...spec)
if (!extension.origin) {
continue
}
await extension._install()
if (activate) extension.setActive(true)
// Add extension to store if needed
if (store) addExtension(extension);
installed.push(extension);
addExtension(extension)
installed.push(extension)
}
// Return list of all installed extensions
return installed;
return installed
}
/**

View File

@ -1,8 +1,7 @@
import { AppConfiguration, SystemResourceInfo } from '../../types'
import { AppConfiguration } from '../../types'
import { join } from 'path'
import fs from 'fs'
import os from 'os'
import { log, logServer } from '../log'
import childProcess from 'child_process'
// TODO: move this to core
@ -56,34 +55,6 @@ export const updateAppConfiguration = (configuration: AppConfiguration): Promise
return Promise.resolve()
}
/**
* Utility function to get server log path
*
* @returns {string} The log path.
*/
export const getServerLogPath = (): string => {
const appConfigurations = getAppConfigurations()
const logFolderPath = join(appConfigurations.data_folder, 'logs')
if (!fs.existsSync(logFolderPath)) {
fs.mkdirSync(logFolderPath, { recursive: true })
}
return join(logFolderPath, 'server.log')
}
/**
* Utility function to get app log path
*
* @returns {string} The log path.
*/
export const getAppLogPath = (): string => {
const appConfigurations = getAppConfigurations()
const logFolderPath = join(appConfigurations.data_folder, 'logs')
if (!fs.existsSync(logFolderPath)) {
fs.mkdirSync(logFolderPath, { recursive: true })
}
return join(logFolderPath, 'app.log')
}
/**
* Utility function to get data folder path
*
@ -146,18 +117,6 @@ const exec = async (command: string): Promise<string> => {
})
}
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
const cpu = await physicalCpuCount()
const message = `[NITRO]::CPU informations - ${cpu}`
log(message)
logServer(message)
return {
numCpuPhysicalCore: cpu,
memAvailable: 0, // TODO: this should not be 0
}
}
export const getEngineConfiguration = async (engineId: string) => {
if (engineId !== 'openai') {
return undefined
@ -167,3 +126,31 @@ export const getEngineConfiguration = async (engineId: string) => {
const data = fs.readFileSync(filePath, 'utf-8')
return JSON.parse(data)
}
/**
* Utility function to get server log path
*
* @returns {string} The log path.
*/
export const getServerLogPath = (): string => {
const appConfigurations = getAppConfigurations()
const logFolderPath = join(appConfigurations.data_folder, 'logs')
if (!fs.existsSync(logFolderPath)) {
fs.mkdirSync(logFolderPath, { recursive: true })
}
return join(logFolderPath, 'server.log')
}
/**
* Utility function to get app log path
*
* @returns {string} The log path.
*/
export const getAppLogPath = (): string => {
const appConfigurations = getAppConfigurations()
const logFolderPath = join(appConfigurations.data_folder, 'logs')
if (!fs.existsSync(logFolderPath)) {
fs.mkdirSync(logFolderPath, { recursive: true })
}
return join(logFolderPath, 'app.log')
}

View File

@ -1,15 +1,18 @@
import { DownloadState } from '../../types'
/**
* Manages file downloads and network requests.
*/
export class DownloadManager {
public networkRequests: Record<string, any> = {};
public networkRequests: Record<string, any> = {}
public static instance: DownloadManager = new DownloadManager();
public static instance: DownloadManager = new DownloadManager()
public downloadProgressMap: Record<string, DownloadState> = {}
constructor() {
if (DownloadManager.instance) {
return DownloadManager.instance;
return DownloadManager.instance
}
}
/**
@ -18,6 +21,6 @@ export class DownloadManager {
* @param {Request | undefined} request - The network request to set, or undefined to clear the request.
*/
setRequest(fileName: string, request: any | undefined) {
this.networkRequests[fileName] = request;
this.networkRequests[fileName] = request
}
}

View File

@ -0,0 +1,6 @@
export * from './config'
export * from './download'
export * from './log'
export * from './module'
export * from './path'
export * from './resource'

View File

@ -1,6 +1,6 @@
import fs from 'fs'
import util from 'util'
import { getAppLogPath, getServerLogPath } from './utils'
import { getAppLogPath, getServerLogPath } from './config'
export const log = (message: string) => {
const path = getAppLogPath()

View File

@ -0,0 +1,35 @@
import { join } from 'path'
/**
* Normalize file path
* Remove all file protocol prefix
* @param path
* @returns
*/
export function normalizeFilePath(path: string): string {
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, '$2')
}
export async function appResourcePath(): Promise<string> {
let electron: any = undefined
try {
const moduleName = 'electron'
electron = await import(moduleName)
} catch (err) {
console.error('Electron is not available')
}
// electron
if (electron && electron.protocol) {
let appPath = join(electron.app.getAppPath(), '..', 'app.asar.unpacked')
if (!electron.app.isPackaged) {
// for development mode
appPath = join(electron.app.getAppPath())
}
return appPath
}
// server
return join(global.core.appPath(), '../../..')
}

View File

@ -0,0 +1,14 @@
import { SystemResourceInfo } from '../../types'
import { physicalCpuCount } from './config'
import { log, logServer } from './log'
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
const cpu = await physicalCpuCount()
const message = `[NITRO]::CPU informations - ${cpu}`
log(message)
return {
numCpuPhysicalCore: cpu,
memAvailable: 0, // TODO: this should not be 0
}
}

View File

@ -2,9 +2,5 @@ export * from './extension/index'
export * from './extension/extension'
export * from './extension/manager'
export * from './extension/store'
export * from './download'
export * from './module'
export * from './api'
export * from './log'
export * from './utils'
export * from './path'
export * from './helper'

View File

@ -1,9 +0,0 @@
/**
* Normalize file path
* Remove all file protocol prefix
* @param path
* @returns
*/
export function normalizeFilePath(path: string): string {
return path.replace(/^(file:[\\/]+)([^:\s]+)$/, "$2");
}

View File

@ -0,0 +1,7 @@
/**
* The `EventName` enumeration contains the names of all the available events in the Jan platform.
*/
export enum AssistantEvent {
/** The `OnAssistantsUpdate` event is emitted when the assistant list is updated. */
OnAssistantsUpdate = 'OnAssistantsUpdate',
}

View File

@ -1,2 +1,3 @@
export * from './assistantEntity'
export * from './assistantEvent'
export * from './assistantInterface'

View File

@ -2,3 +2,26 @@ export type FileStat = {
isDirectory: boolean
size: number
}
export type DownloadState = {
modelId: string
fileName: string
time: DownloadTime
speed: number
percent: number
size: DownloadSize
children?: DownloadState[]
error?: string
downloadState: 'downloading' | 'error' | 'end'
}
type DownloadTime = {
elapsed: number
remaining: number
}
type DownloadSize = {
total: number
transferred: number
}

View File

@ -1,3 +1,4 @@
export * from './messageEntity'
export * from './messageInterface'
export * from './messageEvent'
export * from './messageRequestType'

View File

@ -27,6 +27,8 @@ export type ThreadMessage = {
updated: number
/** The additional metadata of this message. **/
metadata?: Record<string, unknown>
type?: string
}
/**
@ -56,6 +58,8 @@ export type MessageRequest = {
/** The thread of this message is belong to. **/
// TODO: deprecate threadId field
thread?: Thread
type?: string
}
/**

View File

@ -0,0 +1,5 @@
export enum MessageRequestType {
Thread = 'Thread',
Assistant = 'Assistant',
Summary = 'Summary',
}

View File

@ -12,4 +12,6 @@ export enum ModelEvent {
OnModelStop = 'OnModelStop',
/** The `OnModelStopped` event is emitted when a model stopped ok. */
OnModelStopped = 'OnModelStopped',
/** The `OnModelUpdate` event is emitted when the model list is updated. */
OnModelsUpdate = 'OnModelsUpdate',
}

View File

@ -10,7 +10,7 @@ export interface ModelInterface {
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
* @returns A Promise that resolves when the model has been downloaded.
*/
downloadModel(model: Model, network?: { ignoreSSL?: boolean, proxy?: string }): Promise<void>
downloadModel(model: Model, network?: { ignoreSSL?: boolean; proxy?: string }): Promise<void>
/**
* Cancels the download of a specific model.

View File

@ -1,4 +1,4 @@
import { normalizeFilePath } from "../../src/node/path";
import { normalizeFilePath } from "../../src/node/helper/path";
describe("Test file normalize", () => {
test("returns no file protocol prefix on Unix", async () => {

View File

@ -1,6 +1,3 @@
{
"extends": [
"tslint-config-standard",
"tslint-config-prettier"
]
}
"extends": ["tslint-config-standard", "tslint-config-prettier"]
}

172
docker-compose.yml Normal file
View File

@ -0,0 +1,172 @@
# Docker Compose file for setting up Minio, createbuckets, app_cpu, and app_gpu services
version: '3.7'
services:
# Minio service for object storage
minio:
image: minio/minio
volumes:
- minio_data:/data
ports:
- "9000:9000"
- "9001:9001"
environment:
# Set the root user and password for Minio
MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY
MINIO_ROOT_PASSWORD: minioadmin # This acts as AWS_SECRET_ACCESS_KEY
command: server --console-address ":9001" /data
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
networks:
vpcbr:
ipv4_address: 10.5.0.2
# createbuckets service to create a bucket and set its policy
createbuckets:
image: minio/mc
depends_on:
- minio
entrypoint: >
/bin/sh -c "
/usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin;
/usr/bin/mc mb myminio/mybucket;
/usr/bin/mc policy set public myminio/mybucket;
exit 0;
"
networks:
vpcbr:
# app_cpu service for running the CPU version of the application
app_cpu_s3fs:
image: jan:latest
volumes:
- app_data_cpu_s3fs:/app/server/build/jan
build:
context: .
dockerfile: Dockerfile
environment:
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_cpu
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: minioadmin
S3_BUCKET_NAME: mybucket
AWS_ENDPOINT: http://10.5.0.2:9000
AWS_REGION: us-east-1
API_BASE_URL: http://localhost:1337
restart: always
profiles:
- cpu-s3fs
ports:
- "3000:3000"
- "1337:1337"
- "3928:3928"
networks:
vpcbr:
ipv4_address: 10.5.0.3
# app_gpu service for running the GPU version of the application
app_gpu_s3fs:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
image: jan-gpu:latest
volumes:
- app_data_gpu_s3fs:/app/server/build/jan
build:
context: .
dockerfile: Dockerfile.gpu
restart: always
environment:
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_gpu
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: minioadmin
S3_BUCKET_NAME: mybucket
AWS_ENDPOINT: http://10.5.0.2:9000
AWS_REGION: us-east-1
API_BASE_URL: http://localhost:1337
profiles:
- gpu-s3fs
ports:
- "3000:3000"
- "1337:1337"
- "3928:3928"
networks:
vpcbr:
ipv4_address: 10.5.0.4
app_cpu_fs:
image: jan:latest
volumes:
- app_data_cpu_fs:/app/server/build/jan
build:
context: .
dockerfile: Dockerfile
environment:
API_BASE_URL: http://localhost:1337
restart: always
profiles:
- cpu-fs
ports:
- "3000:3000"
- "1337:1337"
- "3928:3928"
networks:
vpcbr:
ipv4_address: 10.5.0.5
# app_gpu service for running the GPU version of the application
app_gpu_fs:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
image: jan-gpu:latest
volumes:
- app_data_gpu_fs:/app/server/build/jan
build:
context: .
dockerfile: Dockerfile.gpu
restart: always
environment:
API_BASE_URL: http://localhost:1337
profiles:
- gpu-fs
ports:
- "3000:3000"
- "1337:1337"
- "3928:3928"
networks:
vpcbr:
ipv4_address: 10.5.0.6
volumes:
minio_data:
app_data_cpu_s3fs:
app_data_gpu_s3fs:
app_data_cpu_fs:
app_data_gpu_fs:
networks:
vpcbr:
driver: bridge
ipam:
config:
- subnet: 10.5.0.0/16
gateway: 10.5.0.1
# Usage:
# - Run 'docker compose --profile cpu-s3fs up -d' to start the app_cpu service
# - Run 'docker compose --profile gpu-s3fs up -d' to start the app_gpu service
# - Run 'docker compose --profile cpu-fs up -d' to start the app_cpu service
# - Run 'docker compose --profile gpu-fs up -d' to start the app_gpu service

View File

@ -1,22 +1,8 @@
---
title: |
10/1/24: Bitdefender False Positive Flag (Resolved)
title: "Post Mortem: Bitdefender False Positive Flag"
description: "10th January 2024, Jan's 0.4.4 Release on Windows triggered Bitdefender to incorrectly flag it as infected with Gen:Variant.Tedy.258323, leading to automatic quarantine warnings on users' computers."
slug: /postmortems/january-10-2024-bitdefender-false-positive-flag
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
postmortem,
incident,
flagging issue,
]
tags: [Postmortem]
---
Following the recent incident related to Jan version 0.4.4 triggering Bitdefender on Windows with Gen:Variant.Tedy.258323 on January 10, 2024, we wanted to provide a comprehensive postmortem and outline the necessary follow-up actions.

View File

@ -1 +0,0 @@
# TODO

View File

@ -1,6 +1,76 @@
dan-jan:
name: Daniel Onggunhao
title: Co-Founder
url: https://github.com/dan-jan
url: https://github.com/dan-jan
image_url: https://avatars.githubusercontent.com/u/101145494?v=4
email: daniel@jan.ai
email: daniel@jan.ai
namchuai:
name: Nam Nguyen
title: Developer
url: https://github.com/namchuai
image_url: https://avatars.githubusercontent.com/u/10397206?v=4
email: james@jan.ai
hiro-v:
name: Hiro Vuong
title: MLE
url: https://github.com/hiro-v
image_url: https://avatars.githubusercontent.com/u/22463238?v=4
email: hiro@jan.ai
ashley-jan:
name: Ashley Tran
title: Product Designer
url: https://github.com/imtuyethan
image_url: https://avatars.githubusercontent.com/u/89722390?v=4
email: ashley@jan.ai
hientominh:
name: Hien To
title: DevOps Engineer
url: https://github.com/hientominh
image_url: https://avatars.githubusercontent.com/u/37921427?v=4
email: hien@jan.ai
Van-QA:
name: Van Pham
title: QA & Release Manager
url: https://github.com/Van-QA
image_url: https://avatars.githubusercontent.com/u/64197333?v=4
email: van@jan.ai
louis-jan:
name: Louis Le
title: Software Engineer
url: https://github.com/louis-jan
image_url: https://avatars.githubusercontent.com/u/133622055?v=4
email: louis@jan.ai
hahuyhoang411:
name: Rex Ha
title: LLM Researcher & Content Writer
url: https://github.com/hahuyhoang411
image_url: https://avatars.githubusercontent.com/u/64120343?v=4
email: rex@jan.ai
automaticcat:
name: Alan Dao
title: AI Engineer
url: https://github.com/tikikun
image_url: https://avatars.githubusercontent.com/u/22268502?v=4
email: alan@jan.ai
hieu-jan:
name: Henry Ho
title: Software Engineer
url: https://github.com/hieu-jan
image_url: https://avatars.githubusercontent.com/u/150573299?v=4
email: hieu@jan.ai
0xsage:
name: Nicole Zhu
title: Co-Founder
url: https://github.com/0xsage
image_url: https://avatars.githubusercontent.com/u/69952136?v=4
email: nicole@jan.ai

View File

@ -1,139 +0,0 @@
---
title: About Jan
slug: /about
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
Jan believes in the need for an **open source AI ecosystem**. We are focused on building the infra and tooling to allow open source AIs to compete on a level playing field with proprietary ones.
Jan's long-term technical endeavor is to build a cognitive framework for future robots, who are practical, useful assistants for humans and businesses in everyday life.
## Quicklinks
- Core product vision for [Jan Framework](../docs)
- R&D and model training efforts [Discord](https://discord.gg/9NfUSyzp3y) (via our small data-center which is `free & open to all researchers who lack GPUs`!)
- Current implementations of Jan Framework: [Jan Desktop](https://jan.ai/), [Nitro](https://nitro.jan.ai/)
## Why does Jan Exist?
### Mission
Our current mission is to allow humans and businesses to **own their AI, with the right to tinker, repair and innovate**.
:::tip
Our life-long mission is to **eliminate work - so human can focus on creation, invention, and moral governance over robots**.
:::
### Ideal Customer
Our ideal customer is an AI enthusiast or business who has experienced some limitations with current AI solutions and is keen to find open source alternatives.
### Problems
Our ideal customer would use Jan to solve one of these problems.
_Control_
- Control (e.g. preventing vendor lock-in)
- Stability (e.g. runs predictably every time)
- Local-use (e.g. for speed, or for airgapped environments)
_Privacy_
- Data protection (e.g. personal data or company data)
- Privacy (e.g. nsfw)
_Customisability_
- Tinkerability (e.g. ability to change model, experiment)
- Niche Models (e.g. fine-tuned, domain-specific models that outperform OpenAI)
Sources: [^1] [^2] [^3] [^4]
[^1]: [What are you guys doing that can't be done with ChatGPT?](https://www.reddit.com/r/LocalLLaMA/comments/17mghqr/comment/k7ksti6/?utm_source=share&utm_medium=web2x&context=3)
[^2]: [What's your main interest in running a local LLM instead of an existing API?](https://www.reddit.com/r/LocalLLaMA/comments/1718a9o/whats_your_main_interest_in_running_a_local_llm/)
[^3]: [Ask HN: What's the best self-hosted/local alternative to GPT-4?](https://news.ycombinator.com/item?id=36138224)
[^4]: [LoRAs](https://www.reddit.com/r/LocalLLaMA/comments/17mghqr/comment/k7mdz1i/?utm_source=share&utm_medium=web2x&context=3)
### Solution
Jan is a seamless user experience that runs on your personal computer, that glues the different pieces of the open source AI ecosystem to provide an alternative to OpenAI's closed platform.
- We build a comprehensive, seamless platform that takes care of the technical chores across the stack required to run open source AI
- We run on top of a local folder of non-proprietary files, that anyone can tinker with (yes, even other apps!)
- We provide open formats for packaging and distributing AI to run reproducibly across devices
## How Jan Works
### Open Source
Jan is a startup with an open source business model. We believe in the need for an open source AI ecosystem, and are committed to building it.
- [Jan Framework](https://github.com/janhq/jan) (AGPLv3)
- [Jan Desktop Client & Local server](https://jan.ai) (AGPLv3, built on Jan Framework)
- [Nitro: run Local AI](https://github.com/janhq/nitro) (AGPLv3)
### Build in Public
We use GitHub to build in public and welcome anyone to join in.
- [Jan's Kanban](https://github.com/orgs/janhq/projects/5)
- [Jan's Roadmap](https://github.com/orgs/janhq/projects/5/views/29)
- [Jan's Newsletter](https://newsletter.jan.ai)
### Remote Team
Jan has a fully-remote team. We are mainly based in the APAC timezone. We use [Discord](https://discord.gg/af6SaTdzpx) and [Github](https://github.com/janhq) to work.
### Bootstrapped
Jan is currently a bootstrapped, founder funded startup.
We balance technical invention with the search for a sustainable business model. Thus, we appreciate any business inquiries that can balance growth with cashflow.
**We invite you to join us on our journey to find PMF**. Join our [Discord here](https://discord.gg/BnHRr3Q7Ms)
### Analytics
Adhering to Jan's privacy preserving philosophy, our analytics philosophy is to get `barely-enough-to-function`.
#### What is tracked
1. By default, Github tracks downloads and device metadata for all public GitHub repositories. This helps us troubleshoot & ensure cross-platform support.
2. We use [Umami](https://umami.is/) to collect, analyze, and understand application data while maintaining visitor privacy and data ownership. We are using the Umami Cloud in Europe to ensure GDPR compliance. Please see [Umami Privacy Policy](https://umami.is/privacy) for more details.
3. We use Umami to track a single `app.opened` event without additional user metadata, in order to understand retention. In addition, we track `app.event` to understand app version usage.
4. Additionally, we plan to enable a `Settings` feature for users to turn off all tracking.
#### Request for help
As a result, our feature prioritization can feel a bit black box at times.
We'd appreciate high quality insights and volunteers for user interviews through [Discord](https://discord.gg/af6SaTdzpx) and [Github](https://github.com/janhq).
## Contact
### General Enquiries
Drop us a message in our [Discord](https://discord.gg/af6SaTdzpx) and we'll get back to you.
- `#general`: for general discussion
- `#jan-dev`: for Jan-related questions
- `#nitro-dev`: for Nitro-related questions
- '#bd-inquiries': for enterprise license queries
### Careers
Jan has a culture of ownership, independent thought, and lightning fast execution. If you'd like to join us, we have open positions on our [careers page](https://janai.bamboohr.com/careers).
## Footnotes

View File

@ -1,79 +0,0 @@
---
title: Onboarding
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
slug: /onboarding
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
# Onboarding
Welcome to Jan! Were really excited to bring you onboard.
## Expectations
- **Take Initiative** Take ownership of an area. If you see a problem, take it and own it to completion. Your work will often not be defined, or poorly defined. Take the initiative to figure out what needs to be done, seek others out for clarification, and then communicate what you will be doing to the team.
- **Bias to Action** There are many problem-filled areas. There is no need to ask for permission or try to build consensus: just take action.
- **Speak Up** We require clear, effective and timely communication, which enables others to coordinate with you to be effective. We are a fully distributed, remote team of people from different cultures and languages. If conflicts do arise, first assume Hanlons Razor: “Never attribute to malice that which is adequately explained by ~~stupidity~~ lack of communication or too much ambiguity”. Dont take things personally, be a professional.
- **Mastery** We are working in a frontier industry, where there are no playbooks, and expertise is developed by doing. Own your area, and build mastery.
## Code of conduct
- We operate on the basis of trust.
- We expect you to be available and communicative during scheduled meetings or work hours.
- Turning on video during meetings is encouraged.
- Casual dress during meetings is acceptable; however, use discretion (No nudity, pajamas, etc.)
- While its natural for people to disagree at times, disagreement is no excuse for poor behavior and poor manners. We cannot allow that frustration to turn into a personal attack.
- Respect other people's cultures. Especially since we are working in a diverse working culture.
- Sexual harassment is a specific type of prohibited conduct. Sexual harassment is any unwelcome conduct of a sexual nature that might reasonably be expected or be perceived to cause offense or humiliation. Sexual harassment may involve any conduct of a verbal, nonverbal, or physical nature, including written and electronic communications, and may occur between persons of the same or different genders.
## Onboarding Checklist
### HR
- [ ] Service Agreement
- [ ] Equipment Loan Agreement
- [ ] Calendar events
- [ ] Add to Google Team
- [ ] Add to Standup & TGIF
- [ ] `#hr-*` channel
- [ ] BambooHr Log-in
- [ ] Add Emergency Contact in BambooHR
### Apps you will need
:::info
💡 In order to feel connected in a remote workplace, we encourage you to add your profile photo to all the accounts.
:::
- Company-wide
- [ ] Google:`[first_name]@jan.ai`
- Recommended: setup on Mobile too (i.e. Calendar, Mail)
- We use Google Calendar for tracking meetings, etc.
- [ ] Discord:
- [Invite link](https://discord.gg/sZb6qxfgyx) to Jans Discord
- We use Discord for day-to-day Comms in the company (ala Slack)
- Recommended: setup on Mobile with Notifications
- Download the desktop app
- [ ] 1Password
- [ ] [Jan](https://jan.ai/) - Desktop App
- Engineering
- [ ] Code Editor (such as VSCode, Vim, ect)
- [ ] Github
- Communications
- [ ] Fill in your contact details [here](https://docs.google.com/spreadsheets/d/1KAxya29_wb1bEESiFJeCrOec4pCG3uA2D4_VPgAn89U/edit#gid=0)
- [ ] To make sure everyone in the remote working environment understands more about each other, we encourage you to share your `How to work with me` in the [Drive Tab](https://docs.google.com/spreadsheets/d/1KAxya29_wb1bEESiFJeCrOec4pCG3uA2D4_VPgAn89U/edit#gid=0) under your name and the Discord `Internal` channel.

View File

@ -1,149 +0,0 @@
---
title: MLOps
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
slug: /engineering/mlops
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
## Connecting to Rigs
We have a small data rig you can remote into for R&D and CI.
### Pritunl Setup
1. **Install Pritunl**: [Download here](https://client.pritunl.com/#install)
2. **Import .ovpn file**
3. **VSCode**: Install the "Remote-SSH" extension for connection
### Llama.cpp Setup
1. **Clone Repo**: `git clone https://github.com/ggerganov/llama.cpp && cd llama.cpp`
2. **Build**:
```bash
mkdir build && cd build
cmake .. -DLLAMA_CUBLAS=ON -DLLAMA_CUDA_F16=ON -DLLAMA_CUDA_MMV_Y=8
cmake --build . --config Release
```
3. **Download Model:**
```bash
cd ../models && wget https://huggingface.co/TheBloke/Llama-2-7B-GGUF/resolve/main/llama-2-7b.Q8_0.gguf
```
4. **Run:**
```bash
cd ../build/bin/
./main -m ./models/llama-2-7b.Q8_0.gguf -p "Writing a thesis proposal can be done in 10 simple steps:\nStep 1:" -n 2048 -e -ngl 100 -t 48
```
For the llama.cpp CLI arguments you can see here:
| Short Option | Long Option | Param Value | Description |
| --------------- | --------------------- | ----------- | ---------------------------------------------------------------- |
| `-h` | `--help` | | Show this help message and exit |
| `-i` | `--interactive` | | Run in interactive mode |
| | `--interactive-first` | | Run in interactive mode and wait for input right away |
| | `-ins`, `--instruct` | | Run in instruction mode (use with Alpaca models) |
| `-r` | `--reverse-prompt` | `PROMPT` | Run in interactive mode and poll user input upon seeing `PROMPT` |
| | `--color` | | Colorise output to distinguish prompt and user input from |
| **Generations** |
| `-s` | `--seed` | `SEED` | Seed for random number generator |
| `-t` | `--threads` | `N` | Number of threads to use during computation |
| `-p` | `--prompt` | `PROMPT` | Prompt to start generation with |
| | `--random-prompt` | | Start with a randomized prompt |
| | `--in-prefix` | `STRING` | String to prefix user inputs with |
| `-f` | `--file` | `FNAME` | Prompt file to start generation |
| `-n` | `--n_predict` | `N` | Number of tokens to predict |
| | `--top_k` | `N` | Top-k sampling |
| | `--top_p` | `N` | Top-p sampling |
| | `--repeat_last_n` | `N` | Last n tokens to consider for penalize |
| | `--repeat_penalty` | `N` | Penalize repeat sequence of tokens |
| `-c` | `--ctx_size` | `N` | Size of the prompt context |
| | `--ignore-eos` | | Ignore end of stream token and continue generating |
| | `--memory_f32` | | Use `f32` instead of `f16` for memory key+value |
| | `--temp` | `N` | Temperature |
| | `--n_parts` | `N` | Number of model parts |
| `-b` | `--batch_size` | `N` | Batch size for prompt processing |
| | `--perplexity` | | Compute perplexity over the prompt |
| | `--keep` | | Number of tokens to keep from the initial prompt |
| | `--mlock` | | Force system to keep model in RAM |
| | `--mtest` | | Determine the maximum memory usage |
| | `--verbose-prompt` | | Print prompt before generation |
| `-m` | `--model` | `FNAME` | Model path |
### TensorRT-LLM Setup
#### **Docker and TensorRT-LLM build**
> Note: You should run with admin permission to make sure everything works fine
1. **Docker Image:**
```bash
sudo make -C docker build
```
2. **Run Container:**
```bash
sudo make -C docker run
```
Once in the container, TensorRT-LLM can be built from the source using the following:
3. **Build:**
```bash
# To build the TensorRT-LLM code.
python3 ./scripts/build_wheel.py --trt_root /usr/local/tensorrt
# Deploy TensorRT-LLM in your environment.
pip install ./build/tensorrt_llm*.whl
```
> Note: You can specify the GPU architecture (e.g. for 4090 is ADA) for compilation time reduction
> The list of supported architectures can be found in the `CMakeLists.txt` file.
```bash
python3 ./scripts/build_wheel.py --cuda_architectures "89-real;90-real"
```
#### Running TensorRT-LLM
1. **Requirements:**
```bash
pip install -r examples/bloom/requirements.txt && git lfs install
```
2. **Download Weights:**
```bash
cd examples/llama && rm -rf ./llama/7B && mkdir -p ./llama/7B && git clone https://huggingface.co/NousResearch/Llama-2-7b-hf ./llama/7B
```
3. **Build Engine:**
```bash
python build.py --model_dir ./llama/7B/ --dtype float16 --remove_input_padding --use_gpt_attention_plugin float16 --enable_context_fmha --use_gemm_plugin float16 --use_weight_only --output_dir ./llama/7B/trt_engines/weight_only/1-gpu/
```
4. **Run Inference:**
```bash
python3 run.py --max_output_len=2048 --tokenizer_dir ./llama/7B/ --engine_dir=./llama/7B/trt_engines/weight_only/1-gpu/ --input_text "Writing a thesis proposal can be done in 10 simple steps:\nStep 1:"
```
For the tensorRT-LLM CLI arguments, you can see in the `run.py`.

View File

@ -1,20 +0,0 @@
---
title: R&D
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
slug: /engineering/research
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
## Foundry Best Practices
@alan/rex TODO

View File

@ -1,20 +0,0 @@
---
title: Postmortems
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
slug: /postmortems
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
import DocCardList from "@theme/DocCardList";
<DocCardList />

View File

@ -1,9 +0,0 @@
---
title: Product
slug: /product
---
## Prerequisites
- [Figma](https://figma.com)
- [ScreenStudio](https://www.screen.studio/)

View File

@ -1,20 +0,0 @@
---
title: Events
slug: /events
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
import DocCardList from "@theme/DocCardList";
<DocCardList />

View File

@ -1,20 +0,0 @@
---
title: Careers
slug: /careers
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
## We're hiring
[Careers on Bamboo](https://janai.bamboohr.com/careers)

12
docs/docs/about/2035.mdx Normal file
View File

@ -0,0 +1,12 @@
---
title: Jan's Vision for 2035
---
[Jan 2035: A Robotics Company](https://hackmd.io/QIWyYbNNQVWVbupuI3kjAA)
We only have 2 planning parameters:
- 10 year vision
- 2 week sprint
And we measure our success on Quarterly OKRs

94
docs/docs/about/about.md Normal file
View File

@ -0,0 +1,94 @@
---
title: About Jan
slug: /about
description: Jan is a productivity tool to customize AI to your needs and workflows.
keywords:
[
Jan AI,
Jan,
ChatGPT alternative,
local AI,
private AI,
conversational AI,
no-subscription fee,
large language model,
]
---
Jan is a [open-source](https://en.wikipedia.org/wiki/Open_source), [local-first](https://www.inkandswitch.com/local-first/) tool to [create, customize and use AI](https://www.gatesnotes.com/AI-agents) for everyday tasks.
You can:
- Run locally using [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/)
- Fine-tune AI with specific knowledge
- Search the web and other databases
- Connect AI to your everyday tools and (with your permission) do work on your behalf
Longer-term, Jan is building a cognitive framework for future robots. We envision a world where we have personal or company robots that we continually improve and customize, growing together with us.
![Human repairing a Droid](/img/star-wars-droids.png)
## Why do we exist
At Jan, our mission is to advance human-machine collaboration. We achieve this through delivering the best open-source, local-first tools to allow users to run, customize and tinker with AI.
## What's different about it?
| | Status Quo | Jan |
| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Ownership | AI Monopolies owned by Big Tech | AI that you own and control |
| Openness? | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
| Your role | Consume | Create, Tinker and Customize |
| Approach | Cloud | [Local-first](https://www.inkandswitch.com/local-first/), running 100% on your devices |
| Data | Data stored on their servers | Data stored in your local filesystem in open, non-proprietary file formats |
| Privacy | 😂 | Runs 100% on your own machine, predictably, privately and offline |
| Transparency | "Black Box" | Runs predictability with code available to tinker and customize |
| What happens if there's an outage or goes out of business? | Your life's work held hostage in the cloud in proprietary data formats[^1] | Continues to run 100% on your computer, your data is safe in your local folder |
| Driving Philosophy | Monetize your users | [Privacy as a human right](https://en.wikipedia.org/wiki/Right_to_privacy) and the [Right to Repair](https://www.repair.org/) |
## How do I get it?
You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
You can read the [User Guide](/docs/user-guide) if you need some help to get started.
## What license is the code under?
Jan is licensed under the [AGPLv3 License](https://github.com/janhq/jan/blob/main/LICENSE).
We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2].
We also have a [Contributor Program](/docs/team/contributor-program) to provide ownership and upside to contributors who have made significant contributions to the project.
## What was it built with?
[Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]).
We follow [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) and currently support multiple frameworks and runtimes:
- A desktop client with [Electron](https://www.electronjs.org/)
- A headless server-mode with [Nodejs](https://nodejs.org/en)
- Planned support for mobile with [Capacitor](https://capacitorjs.com/)
- Planned support for Python runtime
Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale.
:::tip
**At its core, Jan is a software development kit to build and run copilots on personal devices**. The Desktop Client many folks use is, rather, a specific set of extensions packaged by default. We're excited to see what developers do with the SDK (once its in better shape).
:::
## Contact
Drop us a message in our [Discord](https://discord.gg/af6SaTdzpx) and we'll get back to you.
- `#general`: for general discussion
- `#get-help`: for bug reports and troubleshooting
- `#roadmap`: for feature requests and ideas
## Footnotes
[^1]: Credit to Obsidian's original website
[^2]: Credit to [Discourse's About Page](https://www.discourse.org/about)
[^3]: Credit to [Llama.cpp](https://github.com/ggerganov/llama.cpp), [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM), [vLLM](https://github.com/vllm-project/vllm), [LMDeploy](https://github.com/InternLM/lmdeploy) and more.

View File

@ -0,0 +1,6 @@
---
title: Roadmap
---
- [ ] [Immediate Roadmap on Github](https://github.com/orgs/janhq/projects/5/views/16)
- [ ] [Longer-term Roadmap on Discord](https://discord.gg/Ey62mynnYr)

View File

@ -1,5 +1,5 @@
---
title: Community
title: Jan's Community
slug: /community
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
keywords:

View File

@ -1,9 +0,0 @@
---
title: Integrations
---
Existing and upcoming 3rd party integrations on top of Jan Framework.
From both the core development team, and core contributors.
Suggestions? File an [issue here](https://github.com/janhq/jan/issues)

View File

@ -0,0 +1,3 @@
---
title: Ecosystem
---

View File

@ -0,0 +1,3 @@
---
title: Agents Framework
---

View File

@ -0,0 +1,3 @@
---
title: API Server
---

View File

@ -0,0 +1,3 @@
---
title: Data Security
---

View File

@ -0,0 +1,3 @@
---
title: Extensions Framework
---

View File

@ -0,0 +1,3 @@
---
title: Features
---

Some files were not shown because too many files have changed in this diff Show More