commit
ce516f83b9
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "jan",
|
"name": "jan",
|
||||||
"image": "node:20"
|
"image": "node:20"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
name: Jan Electron Linter & Test
|
name: Jan Electron Linter & Test
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|||||||
40
.github/workflows/jan-server-build-nightly.yml
vendored
Normal file
40
.github/workflows/jan-server-build-nightly.yml
vendored
Normal 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
30
.github/workflows/jan-server-build.yml
vendored
Normal 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 }}"
|
||||||
39
.github/workflows/template-build-jan-server.yml
vendored
Normal file
39
.github/workflows/template-build-jan-server.yml
vendored
Normal 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
4
.gitignore
vendored
@ -5,6 +5,7 @@
|
|||||||
error.log
|
error.log
|
||||||
node_modules
|
node_modules
|
||||||
*.tgz
|
*.tgz
|
||||||
|
!charts/server/charts/*.tgz
|
||||||
yarn.lock
|
yarn.lock
|
||||||
dist
|
dist
|
||||||
build
|
build
|
||||||
@ -28,4 +29,5 @@ extensions/inference-nitro-extension/bin/*/*.exp
|
|||||||
extensions/inference-nitro-extension/bin/*/*.lib
|
extensions/inference-nitro-extension/bin/*/*.lib
|
||||||
extensions/inference-nitro-extension/bin/saved-*
|
extensions/inference-nitro-extension/bin/saved-*
|
||||||
extensions/inference-nitro-extension/bin/*.tar.gz
|
extensions/inference-nitro-extension/bin/*.tar.gz
|
||||||
|
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
|
||||||
|
extensions/inference-nitro-extension/bin/vulkaninfo
|
||||||
|
|||||||
70
Dockerfile
70
Dockerfile
@ -1,39 +1,61 @@
|
|||||||
FROM node:20-bullseye AS base
|
FROM node:20-bookworm AS base
|
||||||
|
|
||||||
# 1. Install dependencies only when needed
|
# 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
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies based on the preferred package manager
|
# Install dependencies based on the preferred package manager
|
||||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
COPY . ./
|
||||||
RUN yarn install
|
|
||||||
|
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
|
# # 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
|
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
|
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 the package.json, yarn.lock, and build output of server yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/server/build ./
|
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
|
# Copy pre-install dependencies
|
||||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
COPY --from=builder /app/pre-install ./pre-install/
|
||||||
COPY --from=builder /app/server/node_modules ./node_modules
|
|
||||||
COPY --from=builder /app/server/package.json ./package.json
|
|
||||||
|
|
||||||
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
|
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
||||||
ENV APPDATA /app/data
|
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
88
Dockerfile.gpu
Normal 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
|
||||||
16
Makefile
16
Makefile
@ -24,9 +24,9 @@ endif
|
|||||||
|
|
||||||
check-file-counts: install-and-build
|
check-file-counts: install-and-build
|
||||||
ifeq ($(OS),Windows_NT)
|
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
|
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
|
endif
|
||||||
|
|
||||||
dev: check-file-counts
|
dev: check-file-counts
|
||||||
@ -52,18 +52,28 @@ build: check-file-counts
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
ifeq ($(OS),Windows_NT)
|
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"
|
rmdir /s /q "%USERPROFILE%\jan\extensions"
|
||||||
else ifeq ($(shell uname -s),Linux)
|
else ifeq ($(shell uname -s),Linux)
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||||
find . -name "dist" -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 "~/jan/extensions"
|
||||||
rm -rf "~/.cache/jan*"
|
rm -rf "~/.cache/jan*"
|
||||||
else
|
else
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||||
find . -name "dist" -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 ~/jan/extensions
|
||||||
rm -rf ~/Library/Caches/jan*
|
rm -rf ~/Library/Caches/jan*
|
||||||
endif
|
endif
|
||||||
|
|||||||
122
README.md
122
README.md
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||||
<td style="text-align:center">
|
<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" />
|
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<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" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<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" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<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" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<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" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
@ -167,6 +167,7 @@ To reset your installation:
|
|||||||
- Clear Application cache in `~/Library/Caches/jan`
|
- Clear Application cache in `~/Library/Caches/jan`
|
||||||
|
|
||||||
## Requirements for running Jan
|
## Requirements for running Jan
|
||||||
|
|
||||||
- MacOS: 13 or higher
|
- MacOS: 13 or higher
|
||||||
- Windows:
|
- Windows:
|
||||||
- Windows 10 or higher
|
- 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:**
|
1. **Clone the repository and prepare:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
git checkout -b DESIRED_BRANCH
|
git checkout -b DESIRED_BRANCH
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run development and use Jan Desktop**
|
2. **Run development and use Jan Desktop**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the development server and open the desktop app.
|
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.
|
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
|
## Acknowledgements
|
||||||
|
|
||||||
Jan builds on top of other open-source projects:
|
Jan builds on top of other open-source projects:
|
||||||
|
|||||||
6
charts/server/Chart.lock
Normal file
6
charts/server/Chart.lock
Normal 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
10
charts/server/Chart.yaml
Normal 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
|
||||||
BIN
charts/server/charts/common-0.1.2.tgz
Normal file
BIN
charts/server/charts/common-0.1.2.tgz
Normal file
Binary file not shown.
4
charts/server/config.json
Normal file
4
charts/server/config.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"image-list": "server=ghcr.io/janhq/jan-server",
|
||||||
|
"platforms": "linux/amd64"
|
||||||
|
}
|
||||||
256
charts/server/values.yaml
Normal file
256
charts/server/values.yaml
Normal 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
|
||||||
@ -4,4 +4,4 @@ module.exports = {
|
|||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'@/(.*)': '<rootDir>/src/$1',
|
'@/(.*)': '<rootDir>/src/$1',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,7 @@
|
|||||||
"rollup-plugin-typescript2": "^0.36.0",
|
"rollup-plugin-typescript2": "^0.36.0",
|
||||||
"ts-jest": "^26.1.1",
|
"ts-jest": "^26.1.1",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2",
|
||||||
|
"rimraf": "^3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,8 @@ export default [
|
|||||||
'url',
|
'url',
|
||||||
'http',
|
'http',
|
||||||
'os',
|
'os',
|
||||||
'util'
|
'util',
|
||||||
|
'child_process',
|
||||||
],
|
],
|
||||||
watch: {
|
watch: {
|
||||||
include: 'src/node/**',
|
include: 'src/node/**',
|
||||||
|
|||||||
@ -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
|
* App Route APIs
|
||||||
* @description Enum of all the routes exposed by the app
|
* @description Enum of all the routes exposed by the app
|
||||||
*/
|
*/
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
openExternalUrl = 'openExternalUrl',
|
|
||||||
openAppDirectory = 'openAppDirectory',
|
|
||||||
openFileExplore = 'openFileExplorer',
|
|
||||||
selectDirectory = 'selectDirectory',
|
|
||||||
getAppConfigurations = 'getAppConfigurations',
|
getAppConfigurations = 'getAppConfigurations',
|
||||||
updateAppConfiguration = 'updateAppConfiguration',
|
updateAppConfiguration = 'updateAppConfiguration',
|
||||||
relaunch = 'relaunch',
|
|
||||||
joinPath = 'joinPath',
|
joinPath = 'joinPath',
|
||||||
isSubdirectory = 'isSubdirectory',
|
isSubdirectory = 'isSubdirectory',
|
||||||
baseName = 'baseName',
|
baseName = 'baseName',
|
||||||
@ -30,6 +37,7 @@ export enum DownloadRoute {
|
|||||||
downloadFile = 'downloadFile',
|
downloadFile = 'downloadFile',
|
||||||
pauseDownload = 'pauseDownload',
|
pauseDownload = 'pauseDownload',
|
||||||
resumeDownload = 'resumeDownload',
|
resumeDownload = 'resumeDownload',
|
||||||
|
getDownloadProgress = 'getDownloadProgress',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DownloadEvent {
|
export enum DownloadEvent {
|
||||||
@ -68,6 +76,10 @@ export enum FileManagerRoute {
|
|||||||
|
|
||||||
export type ApiFunction = (...args: any[]) => any
|
export type ApiFunction = (...args: any[]) => any
|
||||||
|
|
||||||
|
export type NativeRouteFunctions = {
|
||||||
|
[K in NativeRoute]: ApiFunction
|
||||||
|
}
|
||||||
|
|
||||||
export type AppRouteFunctions = {
|
export type AppRouteFunctions = {
|
||||||
[K in AppRoute]: ApiFunction
|
[K in AppRoute]: ApiFunction
|
||||||
}
|
}
|
||||||
@ -96,7 +108,8 @@ export type FileManagerRouteFunctions = {
|
|||||||
[K in FileManagerRoute]: ApiFunction
|
[K in FileManagerRoute]: ApiFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export type APIFunctions = AppRouteFunctions &
|
export type APIFunctions = NativeRouteFunctions &
|
||||||
|
AppRouteFunctions &
|
||||||
AppEventFunctions &
|
AppEventFunctions &
|
||||||
DownloadRouteFunctions &
|
DownloadRouteFunctions &
|
||||||
DownloadEventFunctions &
|
DownloadEventFunctions &
|
||||||
@ -104,11 +117,13 @@ export type APIFunctions = AppRouteFunctions &
|
|||||||
FileSystemRouteFunctions &
|
FileSystemRouteFunctions &
|
||||||
FileManagerRoute
|
FileManagerRoute
|
||||||
|
|
||||||
export const APIRoutes = [
|
export const CoreRoutes = [
|
||||||
...Object.values(AppRoute),
|
...Object.values(AppRoute),
|
||||||
...Object.values(DownloadRoute),
|
...Object.values(DownloadRoute),
|
||||||
...Object.values(ExtensionRoute),
|
...Object.values(ExtensionRoute),
|
||||||
...Object.values(FileSystemRoute),
|
...Object.values(FileSystemRoute),
|
||||||
...Object.values(FileManagerRoute),
|
...Object.values(FileManagerRoute),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)]
|
||||||
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
|
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
export enum ExtensionTypeEnum {
|
export enum ExtensionTypeEnum {
|
||||||
Assistant = "assistant",
|
Assistant = 'assistant',
|
||||||
Conversational = "conversational",
|
Conversational = 'conversational',
|
||||||
Inference = "inference",
|
Inference = 'inference',
|
||||||
Model = "model",
|
Model = 'model',
|
||||||
SystemMonitoring = "systemMonitoring",
|
SystemMonitoring = 'systemMonitoring',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionType {
|
export interface ExtensionType {
|
||||||
type(): ExtensionTypeEnum | undefined;
|
type(): ExtensionTypeEnum | undefined
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Represents a base extension.
|
* 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.
|
* Undefined means its not extending any known extension by the application.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return undefined;
|
return undefined
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Called when the extension is loaded.
|
* Called when the extension is loaded.
|
||||||
* Any initialization logic for the extension should be put here.
|
* Any initialization logic for the extension should be put here.
|
||||||
*/
|
*/
|
||||||
abstract onLoad(): void;
|
abstract onLoad(): void
|
||||||
/**
|
/**
|
||||||
* Called when the extension is unloaded.
|
* Called when the extension is unloaded.
|
||||||
* Any cleanup logic for the extension should be put here.
|
* Any cleanup logic for the extension should be put here.
|
||||||
*/
|
*/
|
||||||
abstract onUnload(): void;
|
abstract onUnload(): void
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Assistant, AssistantInterface } from "../index";
|
import { Assistant, AssistantInterface } from '../index'
|
||||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assistant extension for managing assistants.
|
* Assistant extension for managing assistants.
|
||||||
@ -10,10 +10,10 @@ export abstract class AssistantExtension extends BaseExtension implements Assist
|
|||||||
* Assistant extension type.
|
* Assistant extension type.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return ExtensionTypeEnum.Assistant;
|
return ExtensionTypeEnum.Assistant
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract createAssistant(assistant: Assistant): Promise<void>;
|
abstract createAssistant(assistant: Assistant): Promise<void>
|
||||||
abstract deleteAssistant(assistant: Assistant): Promise<void>;
|
abstract deleteAssistant(assistant: Assistant): Promise<void>
|
||||||
abstract getAssistants(): Promise<Assistant[]>;
|
abstract getAssistants(): Promise<Assistant[]>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export abstract class ConversationalExtension
|
|||||||
* Conversation extension type.
|
* Conversation extension type.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return ExtensionTypeEnum.Conversational;
|
return ExtensionTypeEnum.Conversational
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getThreads(): Promise<Thread[]>
|
abstract getThreads(): Promise<Thread[]>
|
||||||
|
|||||||
@ -2,24 +2,24 @@
|
|||||||
* Conversational extension. Persists and retrieves conversations.
|
* Conversational extension. Persists and retrieves conversations.
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
export { ConversationalExtension } from "./conversational";
|
export { ConversationalExtension } from './conversational'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inference extension. Start, stop and inference models.
|
* Inference extension. Start, stop and inference models.
|
||||||
*/
|
*/
|
||||||
export { InferenceExtension } from "./inference";
|
export { InferenceExtension } from './inference'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring extension for system monitoring.
|
* Monitoring extension for system monitoring.
|
||||||
*/
|
*/
|
||||||
export { MonitoringExtension } from "./monitoring";
|
export { MonitoringExtension } from './monitoring'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assistant extension for managing assistants.
|
* Assistant extension for managing assistants.
|
||||||
*/
|
*/
|
||||||
export { AssistantExtension } from "./assistant";
|
export { AssistantExtension } from './assistant'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model extension for managing models.
|
* Model extension for managing models.
|
||||||
*/
|
*/
|
||||||
export { ModelExtension } from "./model";
|
export { ModelExtension } from './model'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { InferenceInterface, MessageRequest, ThreadMessage } from "../index";
|
import { InferenceInterface, MessageRequest, ThreadMessage } from '../index'
|
||||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inference extension. Start, stop and inference models.
|
* Inference extension. Start, stop and inference models.
|
||||||
@ -9,8 +9,8 @@ export abstract class InferenceExtension extends BaseExtension implements Infere
|
|||||||
* Inference extension type.
|
* Inference extension type.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return ExtensionTypeEnum.Inference;
|
return ExtensionTypeEnum.Inference
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract inference(data: MessageRequest): Promise<ThreadMessage>;
|
abstract inference(data: MessageRequest): Promise<ThreadMessage>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
import { Model, ModelInterface } from "../index";
|
import { Model, ModelInterface } from '../index'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model extension for managing models.
|
* Model extension for managing models.
|
||||||
@ -9,16 +9,16 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter
|
|||||||
* Model extension type.
|
* Model extension type.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return ExtensionTypeEnum.Model;
|
return ExtensionTypeEnum.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract downloadModel(
|
abstract downloadModel(
|
||||||
model: Model,
|
model: Model,
|
||||||
network?: { proxy: string; ignoreSSL?: boolean },
|
network?: { proxy: string; ignoreSSL?: boolean }
|
||||||
): Promise<void>;
|
): Promise<void>
|
||||||
abstract cancelModelDownload(modelId: string): Promise<void>;
|
abstract cancelModelDownload(modelId: string): Promise<void>
|
||||||
abstract deleteModel(modelId: string): Promise<void>;
|
abstract deleteModel(modelId: string): Promise<void>
|
||||||
abstract saveModel(model: Model): Promise<void>;
|
abstract saveModel(model: Model): Promise<void>
|
||||||
abstract getDownloadedModels(): Promise<Model[]>;
|
abstract getDownloadedModels(): Promise<Model[]>
|
||||||
abstract getConfiguredModels(): Promise<Model[]>;
|
abstract getConfiguredModels(): Promise<Model[]>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseExtension, ExtensionTypeEnum } from "../extension";
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
import { MonitoringInterface } from "../index";
|
import { MonitoringInterface } from '../index'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring extension for system monitoring.
|
* Monitoring extension for system monitoring.
|
||||||
@ -10,9 +10,9 @@ export abstract class MonitoringExtension extends BaseExtension implements Monit
|
|||||||
* Monitoring extension type.
|
* Monitoring extension type.
|
||||||
*/
|
*/
|
||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return ExtensionTypeEnum.SystemMonitoring;
|
return ExtensionTypeEnum.SystemMonitoring
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getResourcesInfo(): Promise<any>;
|
abstract getResourcesInfo(): Promise<any>
|
||||||
abstract getCurrentLoad(): Promise<any>;
|
abstract getCurrentLoad(): Promise<any>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,3 +38,10 @@ export * from './extension'
|
|||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
export * from './extensions/index'
|
export * from './extensions/index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declare global object
|
||||||
|
*/
|
||||||
|
declare global {
|
||||||
|
var core: any | undefined
|
||||||
|
}
|
||||||
|
|||||||
43
core/src/node/api/common/adapter.ts
Normal file
43
core/src/node/api/common/adapter.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
core/src/node/api/common/handler.ts
Normal file
23
core/src/node/api/common/handler.ts
Normal 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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './HttpServer'
|
export * from './HttpServer'
|
||||||
export * from './routes'
|
export * from './restful/v1'
|
||||||
|
export * from './common/handler'
|
||||||
|
|||||||
3
core/src/node/api/processors/Processor.ts
Normal file
3
core/src/node/api/processors/Processor.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export abstract class Processor {
|
||||||
|
abstract process(key: string, ...args: any[]): any
|
||||||
|
}
|
||||||
96
core/src/node/api/processors/app.ts
Normal file
96
core/src/node/api/processors/app.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
106
core/src/node/api/processors/download.ts
Normal file
106
core/src/node/api/processors/download.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
88
core/src/node/api/processors/extension.ts
Normal file
88
core/src/node/api/processors/extension.ts
Normal 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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
25
core/src/node/api/processors/fs.ts
Normal file
25
core/src/node/api/processors/fs.ts
Normal 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
78
core/src/node/api/processors/fsExt.ts
Normal file
78
core/src/node/api/processors/fsExt.ts
Normal 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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
core/src/node/api/restful/app/download.ts
Normal file
23
core/src/node/api/restful/app/download.ts
Normal 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])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
13
core/src/node/api/restful/app/handlers.ts
Normal file
13
core/src/node/api/restful/app/handlers.ts
Normal 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()
|
||||||
|
}
|
||||||
@ -1,24 +1,34 @@
|
|||||||
import { AppRoute } from '../../../api'
|
|
||||||
import { HttpServer } from '../HttpServer'
|
import { HttpServer } from '../HttpServer'
|
||||||
import { basename, join } from 'path'
|
|
||||||
import {
|
import {
|
||||||
chatCompletions,
|
chatCompletions,
|
||||||
deleteBuilder,
|
deleteBuilder,
|
||||||
downloadModel,
|
downloadModel,
|
||||||
getBuilder,
|
getBuilder,
|
||||||
retrieveBuilder,
|
retrieveBuilder,
|
||||||
} from '../common/builder'
|
createMessage,
|
||||||
|
createThread,
|
||||||
|
getMessages,
|
||||||
|
retrieveMessage,
|
||||||
|
updateThread,
|
||||||
|
} from './helper/builder'
|
||||||
|
|
||||||
import { JanApiRouteConfiguration } from '../common/configuration'
|
import { JanApiRouteConfiguration } from './helper/configuration'
|
||||||
import { startModel, stopModel } from '../common/startStopModel'
|
import { startModel, stopModel } from './helper/startStopModel'
|
||||||
import { ModelSettingParams } from '../../../types'
|
import { ModelSettingParams } from '../../../types'
|
||||||
import { getJanDataFolderPath } from '../../utils'
|
|
||||||
import { normalizeFilePath } from '../../path'
|
|
||||||
|
|
||||||
export const commonRouter = async (app: HttpServer) => {
|
export const commonRouter = async (app: HttpServer) => {
|
||||||
|
const normalizeData = (data: any) => {
|
||||||
|
return {
|
||||||
|
object: 'list',
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
// Common Routes
|
// Common Routes
|
||||||
|
// Read & Delete :: Threads | Models | Assistants
|
||||||
Object.keys(JanApiRouteConfiguration).forEach((key) => {
|
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) =>
|
app.get(`/${key}/:id`, async (request: any) =>
|
||||||
retrieveBuilder(JanApiRouteConfiguration[key], request.params.id)
|
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) =>
|
app.get(`/models/download/:modelId`, async (request: any) =>
|
||||||
downloadModel(request.params.modelId, {
|
downloadModel(request.params.modelId, {
|
||||||
ignoreSSL: request.query.ignoreSSL === 'true',
|
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))
|
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.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])))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { ContentType, MessageStatus, Model, ThreadMessage } from './../../../index'
|
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../index'
|
||||||
import { getEngineConfiguration, getJanDataFolderPath } from '../../utils'
|
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
|
||||||
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
||||||
|
|
||||||
|
// TODO: Refactor these
|
||||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||||
try {
|
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 messages = await getMessages(threadId)
|
||||||
const filteredMessages = messages.filter((m) => m.id === messageId)
|
const filteredMessages = messages.filter((m) => m.id === messageId)
|
||||||
if (!filteredMessages || filteredMessages.length === 0) {
|
if (!filteredMessages || filteredMessages.length === 0) {
|
||||||
@ -317,13 +318,6 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
apiUrl = engineConfiguration.full_url
|
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> = {
|
const headers: Record<string, any> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
@ -342,8 +336,14 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
})
|
})
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
console.error(response)
|
console.error(response)
|
||||||
return
|
reply.code(400).send(response)
|
||||||
} else {
|
} 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)
|
response.body.pipe(reply.raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../utils'
|
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
||||||
import { logServer } from '../../log'
|
import { logServer } from '../../../helper/log'
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../types'
|
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||||
import {
|
import {
|
||||||
LOCAL_HOST,
|
LOCAL_HOST,
|
||||||
NITRO_DEFAULT_PORT,
|
NITRO_DEFAULT_PORT,
|
||||||
16
core/src/node/api/restful/v1.ts
Normal file
16
core/src/node/api/restful/v1.ts
Normal 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)
|
||||||
|
}
|
||||||
@ -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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -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) => {})
|
|
||||||
}
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export * from './download'
|
|
||||||
export * from './extension'
|
|
||||||
export * from './fs'
|
|
||||||
export * from './thread'
|
|
||||||
export * from './common'
|
|
||||||
export * from './v1'
|
|
||||||
@ -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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -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',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -104,7 +104,7 @@ export default class Extension {
|
|||||||
await pacote.extract(
|
await pacote.extract(
|
||||||
this.specifier,
|
this.specifier,
|
||||||
join(ExtensionManager.instance.getExtensionsPath() ?? '', this.name ?? ''),
|
join(ExtensionManager.instance.getExtensionsPath() ?? '', this.name ?? ''),
|
||||||
this.installOptions,
|
this.installOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set the url using the custom extensions protocol
|
// Set the url using the custom extensions protocol
|
||||||
|
|||||||
@ -41,8 +41,8 @@ async function registerExtensionProtocol() {
|
|||||||
console.error('Electron is not available')
|
console.error('Electron is not available')
|
||||||
}
|
}
|
||||||
const extensionPath = ExtensionManager.instance.getExtensionsPath()
|
const extensionPath = ExtensionManager.instance.getExtensionsPath()
|
||||||
if (electron) {
|
if (electron && electron.protocol) {
|
||||||
return electron.protocol.registerFileProtocol('extension', (request: any, callback: any) => {
|
return electron.protocol?.registerFileProtocol('extension', (request: any, callback: any) => {
|
||||||
const entry = request.url.substr('extension://'.length - 1)
|
const entry = request.url.substr('extension://'.length - 1)
|
||||||
|
|
||||||
const url = normalize(extensionPath + entry)
|
const url = normalize(extensionPath + entry)
|
||||||
@ -69,7 +69,7 @@ export function useExtensions(extensionsPath: string) {
|
|||||||
|
|
||||||
// Read extension list from extensions folder
|
// Read extension list from extensions folder
|
||||||
const extensions = JSON.parse(
|
const extensions = JSON.parse(
|
||||||
readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8'),
|
readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8')
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
// Create and store a Extension instance for each extension in list
|
// Create and store a Extension instance for each extension in list
|
||||||
@ -82,7 +82,7 @@ export function useExtensions(extensionsPath: string) {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
'Could not successfully rebuild list of installed extensions.\n' +
|
'Could not successfully rebuild list of installed extensions.\n' +
|
||||||
error +
|
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() {
|
export function getStore() {
|
||||||
if (!ExtensionManager.instance.getExtensionsFile()) {
|
if (!ExtensionManager.instance.getExtensionsFile()) {
|
||||||
throw new Error(
|
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'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { writeFileSync } from "fs";
|
import { writeFileSync } from 'fs'
|
||||||
import Extension from "./extension";
|
import Extension from './extension'
|
||||||
import { ExtensionManager } from "./manager";
|
import { ExtensionManager } from './manager'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module store
|
* @module store
|
||||||
@ -11,7 +11,7 @@ import { ExtensionManager } from "./manager";
|
|||||||
* Register of installed extensions
|
* Register of installed extensions
|
||||||
* @type {Object.<string, Extension>} extension - List 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.
|
* Get a extension from the stored extensions.
|
||||||
@ -21,10 +21,10 @@ const extensions: Record<string, Extension> = {};
|
|||||||
*/
|
*/
|
||||||
export function getExtension(name: string) {
|
export function getExtension(name: string) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(extensions, name)) {
|
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
|
* @alias extensionManager.getAllExtensions
|
||||||
*/
|
*/
|
||||||
export function getAllExtensions() {
|
export function getAllExtensions() {
|
||||||
return Object.values(extensions);
|
return Object.values(extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +42,7 @@ export function getAllExtensions() {
|
|||||||
* @alias extensionManager.getActiveExtensions
|
* @alias extensionManager.getActiveExtensions
|
||||||
*/
|
*/
|
||||||
export function 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
|
* @alias extensionManager.removeExtension
|
||||||
*/
|
*/
|
||||||
export function removeExtension(name: string, persist = true) {
|
export function removeExtension(name: string, persist = true) {
|
||||||
const del = delete extensions[name];
|
const del = delete extensions[name]
|
||||||
if (persist) persistExtensions();
|
if (persist) persistExtensions()
|
||||||
return del;
|
return del
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,10 +65,10 @@ export function removeExtension(name: string, persist = true) {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export function addExtension(extension: Extension, persist = true) {
|
export function addExtension(extension: Extension, persist = true) {
|
||||||
if (extension.name) extensions[extension.name] = extension;
|
if (extension.name) extensions[extension.name] = extension
|
||||||
if (persist) {
|
if (persist) {
|
||||||
persistExtensions();
|
persistExtensions()
|
||||||
extension.subscribe("pe-persist", persistExtensions);
|
extension.subscribe('pe-persist', persistExtensions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,14 +77,11 @@ export function addExtension(extension: Extension, persist = true) {
|
|||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export function persistExtensions() {
|
export function persistExtensions() {
|
||||||
const persistData: Record<string, Extension> = {};
|
const persistData: Record<string, Extension> = {}
|
||||||
for (const name in extensions) {
|
for (const name in extensions) {
|
||||||
persistData[name] = extensions[name];
|
persistData[name] = extensions[name]
|
||||||
}
|
}
|
||||||
writeFileSync(
|
writeFileSync(ExtensionManager.instance.getExtensionsFile(), JSON.stringify(persistData))
|
||||||
ExtensionManager.instance.getExtensionsFile(),
|
|
||||||
JSON.stringify(persistData),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,26 +91,29 @@ export function persistExtensions() {
|
|||||||
* @returns {Promise.<Array.<Extension>>} New extension
|
* @returns {Promise.<Array.<Extension>>} New extension
|
||||||
* @alias extensionManager.installExtensions
|
* @alias extensionManager.installExtensions
|
||||||
*/
|
*/
|
||||||
export async function installExtensions(extensions: any, store = true) {
|
export async function installExtensions(extensions: any) {
|
||||||
const installed: Extension[] = [];
|
const installed: Extension[] = []
|
||||||
for (const ext of extensions) {
|
for (const ext of extensions) {
|
||||||
// Set install options and activation based on input type
|
// Set install options and activation based on input type
|
||||||
const isObject = typeof ext === "object";
|
const isObject = typeof ext === 'object'
|
||||||
const spec = isObject ? [ext.specifier, ext] : [ext];
|
const spec = isObject ? [ext.specifier, ext] : [ext]
|
||||||
const activate = isObject ? ext.activate !== false : true;
|
const activate = isObject ? ext.activate !== false : true
|
||||||
|
|
||||||
// Install and possibly activate extension
|
// Install and possibly activate extension
|
||||||
const extension = new Extension(...spec);
|
const extension = new Extension(...spec)
|
||||||
await extension._install();
|
if (!extension.origin) {
|
||||||
if (activate) extension.setActive(true);
|
continue
|
||||||
|
}
|
||||||
|
await extension._install()
|
||||||
|
if (activate) extension.setActive(true)
|
||||||
|
|
||||||
// Add extension to store if needed
|
// Add extension to store if needed
|
||||||
if (store) addExtension(extension);
|
addExtension(extension)
|
||||||
installed.push(extension);
|
installed.push(extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return list of all installed extensions
|
// Return list of all installed extensions
|
||||||
return installed;
|
return installed
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { AppConfiguration, SystemResourceInfo } from '../../types'
|
import { AppConfiguration } from '../../types'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import { log, logServer } from '../log'
|
|
||||||
import childProcess from 'child_process'
|
import childProcess from 'child_process'
|
||||||
|
|
||||||
// TODO: move this to core
|
// TODO: move this to core
|
||||||
@ -56,34 +55,6 @@ export const updateAppConfiguration = (configuration: AppConfiguration): Promise
|
|||||||
return Promise.resolve()
|
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
|
* 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) => {
|
export const getEngineConfiguration = async (engineId: string) => {
|
||||||
if (engineId !== 'openai') {
|
if (engineId !== 'openai') {
|
||||||
return undefined
|
return undefined
|
||||||
@ -167,3 +126,31 @@ export const getEngineConfiguration = async (engineId: string) => {
|
|||||||
const data = fs.readFileSync(filePath, 'utf-8')
|
const data = fs.readFileSync(filePath, 'utf-8')
|
||||||
return JSON.parse(data)
|
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')
|
||||||
|
}
|
||||||
@ -1,15 +1,18 @@
|
|||||||
|
import { DownloadState } from '../../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages file downloads and network requests.
|
* Manages file downloads and network requests.
|
||||||
*/
|
*/
|
||||||
export class DownloadManager {
|
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() {
|
constructor() {
|
||||||
if (DownloadManager.instance) {
|
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.
|
* @param {Request | undefined} request - The network request to set, or undefined to clear the request.
|
||||||
*/
|
*/
|
||||||
setRequest(fileName: string, request: any | undefined) {
|
setRequest(fileName: string, request: any | undefined) {
|
||||||
this.networkRequests[fileName] = request;
|
this.networkRequests[fileName] = request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
core/src/node/helper/index.ts
Normal file
6
core/src/node/helper/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './config'
|
||||||
|
export * from './download'
|
||||||
|
export * from './log'
|
||||||
|
export * from './module'
|
||||||
|
export * from './path'
|
||||||
|
export * from './resource'
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import util from 'util'
|
import util from 'util'
|
||||||
import { getAppLogPath, getServerLogPath } from './utils'
|
import { getAppLogPath, getServerLogPath } from './config'
|
||||||
|
|
||||||
export const log = (message: string) => {
|
export const log = (message: string) => {
|
||||||
const path = getAppLogPath()
|
const path = getAppLogPath()
|
||||||
35
core/src/node/helper/path.ts
Normal file
35
core/src/node/helper/path.ts
Normal 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(), '../../..')
|
||||||
|
}
|
||||||
14
core/src/node/helper/resource.ts
Normal file
14
core/src/node/helper/resource.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,5 @@ export * from './extension/index'
|
|||||||
export * from './extension/extension'
|
export * from './extension/extension'
|
||||||
export * from './extension/manager'
|
export * from './extension/manager'
|
||||||
export * from './extension/store'
|
export * from './extension/store'
|
||||||
export * from './download'
|
|
||||||
export * from './module'
|
|
||||||
export * from './api'
|
export * from './api'
|
||||||
export * from './log'
|
export * from './helper'
|
||||||
export * from './utils'
|
|
||||||
export * from './path'
|
|
||||||
|
|||||||
@ -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");
|
|
||||||
}
|
|
||||||
7
core/src/types/assistant/assistantEvent.ts
Normal file
7
core/src/types/assistant/assistantEvent.ts
Normal 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',
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from './assistantEntity'
|
export * from './assistantEntity'
|
||||||
|
export * from './assistantEvent'
|
||||||
export * from './assistantInterface'
|
export * from './assistantInterface'
|
||||||
|
|||||||
@ -2,3 +2,26 @@ export type FileStat = {
|
|||||||
isDirectory: boolean
|
isDirectory: boolean
|
||||||
size: number
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './messageEntity'
|
export * from './messageEntity'
|
||||||
export * from './messageInterface'
|
export * from './messageInterface'
|
||||||
export * from './messageEvent'
|
export * from './messageEvent'
|
||||||
|
export * from './messageRequestType'
|
||||||
|
|||||||
@ -27,6 +27,8 @@ export type ThreadMessage = {
|
|||||||
updated: number
|
updated: number
|
||||||
/** The additional metadata of this message. **/
|
/** The additional metadata of this message. **/
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
|
type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +58,8 @@ export type MessageRequest = {
|
|||||||
/** The thread of this message is belong to. **/
|
/** The thread of this message is belong to. **/
|
||||||
// TODO: deprecate threadId field
|
// TODO: deprecate threadId field
|
||||||
thread?: Thread
|
thread?: Thread
|
||||||
|
|
||||||
|
type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
5
core/src/types/message/messageRequestType.ts
Normal file
5
core/src/types/message/messageRequestType.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum MessageRequestType {
|
||||||
|
Thread = 'Thread',
|
||||||
|
Assistant = 'Assistant',
|
||||||
|
Summary = 'Summary',
|
||||||
|
}
|
||||||
@ -12,4 +12,6 @@ export enum ModelEvent {
|
|||||||
OnModelStop = 'OnModelStop',
|
OnModelStop = 'OnModelStop',
|
||||||
/** The `OnModelStopped` event is emitted when a model stopped ok. */
|
/** The `OnModelStopped` event is emitted when a model stopped ok. */
|
||||||
OnModelStopped = 'OnModelStopped',
|
OnModelStopped = 'OnModelStopped',
|
||||||
|
/** The `OnModelUpdate` event is emitted when the model list is updated. */
|
||||||
|
OnModelsUpdate = 'OnModelsUpdate',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export interface ModelInterface {
|
|||||||
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||||
* @returns A Promise that resolves when the model has been downloaded.
|
* @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.
|
* Cancels the download of a specific model.
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { normalizeFilePath } from "../../src/node/path";
|
import { normalizeFilePath } from "../../src/node/helper/path";
|
||||||
|
|
||||||
describe("Test file normalize", () => {
|
describe("Test file normalize", () => {
|
||||||
test("returns no file protocol prefix on Unix", async () => {
|
test("returns no file protocol prefix on Unix", async () => {
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["tslint-config-standard", "tslint-config-prettier"]
|
||||||
"tslint-config-standard",
|
}
|
||||||
"tslint-config-prettier"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
172
docker-compose.yml
Normal file
172
docker-compose.yml
Normal 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
|
||||||
@ -1,22 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: |
|
title: "Post Mortem: Bitdefender False Positive Flag"
|
||||||
10/1/24: Bitdefender False Positive Flag (Resolved)
|
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
|
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.
|
tags: [Postmortem]
|
||||||
keywords:
|
|
||||||
[
|
|
||||||
Jan AI,
|
|
||||||
Jan,
|
|
||||||
ChatGPT alternative,
|
|
||||||
local AI,
|
|
||||||
private AI,
|
|
||||||
conversational AI,
|
|
||||||
no-subscription fee,
|
|
||||||
large language model,
|
|
||||||
postmortem,
|
|
||||||
incident,
|
|
||||||
flagging issue,
|
|
||||||
]
|
|
||||||
---
|
---
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@ -1 +0,0 @@
|
|||||||
# TODO
|
|
||||||
@ -1,6 +1,76 @@
|
|||||||
dan-jan:
|
dan-jan:
|
||||||
name: Daniel Onggunhao
|
name: Daniel Onggunhao
|
||||||
title: Co-Founder
|
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
|
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
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -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! We’re 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 Hanlon’s Razor: “Never attribute to malice that which is adequately explained by ~~stupidity~~ lack of communication or too much ambiguity”. Don’t 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 it’s 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 Jan’s 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.
|
|
||||||
@ -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`.
|
|
||||||
@ -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
|
|
||||||
@ -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 />
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: Product
|
|
||||||
slug: /product
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- [Figma](https://figma.com)
|
|
||||||
- [ScreenStudio](https://www.screen.studio/)
|
|
||||||
@ -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 />
|
|
||||||
@ -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
12
docs/docs/about/2035.mdx
Normal 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
94
docs/docs/about/about.md
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
6
docs/docs/about/roadmap.md
Normal file
6
docs/docs/about/roadmap.md
Normal 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)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Community
|
title: Jan's Community
|
||||||
slug: /community
|
slug: /community
|
||||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||||
keywords:
|
keywords:
|
||||||
@ -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)
|
|
||||||
3
docs/docs/ecosystem/ecosystem.md
Normal file
3
docs/docs/ecosystem/ecosystem.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Ecosystem
|
||||||
|
---
|
||||||
3
docs/docs/features/agents-framework.md
Normal file
3
docs/docs/features/agents-framework.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Agents Framework
|
||||||
|
---
|
||||||
3
docs/docs/features/api-server.md
Normal file
3
docs/docs/features/api-server.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: API Server
|
||||||
|
---
|
||||||
3
docs/docs/features/data-security.md
Normal file
3
docs/docs/features/data-security.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Data Security
|
||||||
|
---
|
||||||
3
docs/docs/features/extensions-framework.md
Normal file
3
docs/docs/features/extensions-framework.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Extensions Framework
|
||||||
|
---
|
||||||
3
docs/docs/features/features.md
Normal file
3
docs/docs/features/features.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Features
|
||||||
|
---
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user