Merge branch 'website/feb-2024-update' of https://github.com/janhq/jan into website/feb-2024-update
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
@ -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
@ -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
@ -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
|
||||||
|
|||||||
16
Dockerfile
@ -14,9 +14,6 @@ COPY . ./
|
|||||||
RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \
|
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
|
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
|
RUN make install-and-build
|
||||||
RUN yarn workspace jan-web install
|
|
||||||
|
|
||||||
RUN export NODE_ENV=production && yarn workspace jan-web build
|
|
||||||
|
|
||||||
# # 2. Rebuild the source code only when needed
|
# # 2. Rebuild the source code only when needed
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
@ -42,12 +39,13 @@ COPY --from=builder /app/docs/openapi ./docs/openapi/
|
|||||||
COPY --from=builder /app/pre-install ./pre-install/
|
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 the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/web/out ./web/out/
|
COPY --from=builder /app/uikit ./uikit/
|
||||||
COPY --from=builder /app/web/.next ./web/.next/
|
COPY --from=builder /app/web ./web/
|
||||||
COPY --from=builder /app/web/package.json ./web/package.json
|
|
||||||
COPY --from=builder /app/web/yarn.lock ./web/yarn.lock
|
|
||||||
COPY --from=builder /app/models ./models/
|
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
|
RUN npm install -g serve@latest
|
||||||
|
|
||||||
EXPOSE 1337 3000 3928
|
EXPOSE 1337 3000 3928
|
||||||
@ -55,7 +53,9 @@ EXPOSE 1337 3000 3928
|
|||||||
ENV JAN_API_HOST 0.0.0.0
|
ENV JAN_API_HOST 0.0.0.0
|
||||||
ENV JAN_API_PORT 1337
|
ENV JAN_API_PORT 1337
|
||||||
|
|
||||||
CMD ["sh", "-c", "cd server && node build/main.js & cd web && npx serve out"]
|
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 build -t jan .
|
||||||
# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 jan
|
# docker run -p 1337:1337 -p 3000:3000 -p 3928:3928 jan
|
||||||
|
|||||||
@ -28,9 +28,6 @@ COPY . ./
|
|||||||
RUN export NITRO_VERSION=$(cat extensions/inference-nitro-extension/bin/version.txt) && \
|
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
|
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
|
RUN make install-and-build
|
||||||
RUN yarn workspace jan-web install
|
|
||||||
|
|
||||||
RUN export NODE_ENV=production && yarn workspace jan-web build
|
|
||||||
|
|
||||||
# # 2. Rebuild the source code only when needed
|
# # 2. Rebuild the source code only when needed
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
@ -66,12 +63,13 @@ COPY --from=builder /app/docs/openapi ./docs/openapi/
|
|||||||
COPY --from=builder /app/pre-install ./pre-install/
|
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 the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/web/out ./web/out/
|
COPY --from=builder /app/uikit ./uikit/
|
||||||
COPY --from=builder /app/web/.next ./web/.next/
|
COPY --from=builder /app/web ./web/
|
||||||
COPY --from=builder /app/web/package.json ./web/package.json
|
|
||||||
COPY --from=builder /app/web/yarn.lock ./web/yarn.lock
|
|
||||||
COPY --from=builder /app/models ./models/
|
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
|
RUN npm install -g serve@latest
|
||||||
|
|
||||||
EXPOSE 1337 3000 3928
|
EXPOSE 1337 3000 3928
|
||||||
@ -81,7 +79,9 @@ ENV LD_LIBRARY_PATH=/usr/local/cuda/targets/x86_64-linux/lib:/usr/local/cuda-12.
|
|||||||
ENV JAN_API_HOST 0.0.0.0
|
ENV JAN_API_HOST 0.0.0.0
|
||||||
ENV JAN_API_PORT 1337
|
ENV JAN_API_PORT 1337
|
||||||
|
|
||||||
CMD ["sh", "-c", "cd server && node build/main.js & cd web && npx serve out"]
|
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
|
# pre-requisites: nvidia-docker
|
||||||
# docker build -t jan-gpu . -f Dockerfile.gpu
|
# docker build -t jan-gpu . -f Dockerfile.gpu
|
||||||
|
|||||||
12
Makefile
@ -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
|
||||||
|
|||||||
45
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-273.exe'>
|
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.6-281.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-273.dmg'>
|
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.6-281.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-273.dmg'>
|
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.6-281.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-273.deb'>
|
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.6-281.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-273.AppImage'>
|
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.6-281.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>
|
||||||
@ -235,13 +235,33 @@ This will build the app MacOS m1/m2 for production (with code signing already do
|
|||||||
|
|
||||||
- Run Jan in Docker mode
|
- Run Jan in Docker mode
|
||||||
|
|
||||||
- **Option 1**: Run Jan in CPU 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
|
```bash
|
||||||
docker compose --profile cpu up -d
|
# 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
|
- **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
|
- **Step 1**: Check CUDA compatibility with your NVIDIA driver by running `nvidia-smi` and check the CUDA version in the output
|
||||||
|
|
||||||
@ -283,13 +303,16 @@ This will build the app MacOS m1/m2 for production (with code signing already do
|
|||||||
- **Step 4**: Run command to start Jan in GPU mode
|
- **Step 4**: Run command to start Jan in GPU mode
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# GPU mode
|
# GPU mode with default file system
|
||||||
docker compose --profile gpu up -d
|
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`.
|
This will start the web server and you can access Jan at `http://localhost:3000`.
|
||||||
|
|
||||||
> Note: Currently, Docker mode is only work for development and localhost, production is not supported yet. RAG feature is not supported in Docker mode yet.
|
> Note: RAG feature is not supported in Docker mode with s3fs yet.
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|||||||
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
@ -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
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
@ -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
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { basename, isAbsolute, join, relative } from 'path'
|
import { basename, isAbsolute, join, relative } from 'path'
|
||||||
|
|
||||||
import { AppRoute } from '../../../api'
|
|
||||||
import { Processor } from './Processor'
|
import { Processor } from './Processor'
|
||||||
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
||||||
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
createMessage,
|
createMessage,
|
||||||
createThread,
|
createThread,
|
||||||
getMessages,
|
getMessages,
|
||||||
retrieveMesasge,
|
retrieveMessage,
|
||||||
updateThread,
|
updateThread,
|
||||||
} from './helper/builder'
|
} from './helper/builder'
|
||||||
|
|
||||||
@ -17,10 +17,18 @@ import { startModel, stopModel } from './helper/startStopModel'
|
|||||||
import { ModelSettingParams } from '../../../types'
|
import { ModelSettingParams } from '../../../types'
|
||||||
|
|
||||||
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
|
// 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)
|
||||||
@ -34,10 +42,12 @@ export const commonRouter = async (app: HttpServer) => {
|
|||||||
// Threads
|
// Threads
|
||||||
app.post(`/threads/`, async (req, res) => createThread(req.body))
|
app.post(`/threads/`, async (req, res) => createThread(req.body))
|
||||||
|
|
||||||
app.get(`/threads/:threadId/messages`, async (req, res) => getMessages(req.params.threadId))
|
app.get(`/threads/:threadId/messages`, async (req, res) =>
|
||||||
|
getMessages(req.params.threadId).then(normalizeData)
|
||||||
|
)
|
||||||
|
|
||||||
app.get(`/threads/:threadId/messages/:messageId`, async (req, res) =>
|
app.get(`/threads/:threadId/messages/:messageId`, async (req, res) =>
|
||||||
retrieveMesasge(req.params.threadId, req.params.messageId)
|
retrieveMessage(req.params.threadId, req.params.messageId)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.post(`/threads/:threadId/messages`, async (req, res) =>
|
app.post(`/threads/:threadId/messages`, async (req, res) =>
|
||||||
|
|||||||
@ -125,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) {
|
||||||
@ -318,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',
|
||||||
}
|
}
|
||||||
@ -343,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,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
|
||||||
|
|||||||
@ -42,10 +42,10 @@ services:
|
|||||||
vpcbr:
|
vpcbr:
|
||||||
|
|
||||||
# app_cpu service for running the CPU version of the application
|
# app_cpu service for running the CPU version of the application
|
||||||
app_cpu:
|
app_cpu_s3fs:
|
||||||
image: jan:latest
|
image: jan:latest
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/app/server/build/jan
|
- app_data_cpu_s3fs:/app/server/build/jan
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
@ -56,9 +56,10 @@ services:
|
|||||||
S3_BUCKET_NAME: mybucket
|
S3_BUCKET_NAME: mybucket
|
||||||
AWS_ENDPOINT: http://10.5.0.2:9000
|
AWS_ENDPOINT: http://10.5.0.2:9000
|
||||||
AWS_REGION: us-east-1
|
AWS_REGION: us-east-1
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
restart: always
|
restart: always
|
||||||
profiles:
|
profiles:
|
||||||
- cpu
|
- cpu-s3fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
- "1337:1337"
|
- "1337:1337"
|
||||||
@ -68,7 +69,7 @@ services:
|
|||||||
ipv4_address: 10.5.0.3
|
ipv4_address: 10.5.0.3
|
||||||
|
|
||||||
# app_gpu service for running the GPU version of the application
|
# app_gpu service for running the GPU version of the application
|
||||||
app_gpu:
|
app_gpu_s3fs:
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
@ -78,7 +79,7 @@ services:
|
|||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
image: jan-gpu:latest
|
image: jan-gpu:latest
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/app/server/build/jan
|
- app_data_gpu_s3fs:/app/server/build/jan
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.gpu
|
dockerfile: Dockerfile.gpu
|
||||||
@ -90,8 +91,9 @@ services:
|
|||||||
S3_BUCKET_NAME: mybucket
|
S3_BUCKET_NAME: mybucket
|
||||||
AWS_ENDPOINT: http://10.5.0.2:9000
|
AWS_ENDPOINT: http://10.5.0.2:9000
|
||||||
AWS_REGION: us-east-1
|
AWS_REGION: us-east-1
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
profiles:
|
profiles:
|
||||||
- gpu
|
- gpu-s3fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
- "1337:1337"
|
- "1337:1337"
|
||||||
@ -100,9 +102,60 @@ services:
|
|||||||
vpcbr:
|
vpcbr:
|
||||||
ipv4_address: 10.5.0.4
|
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:
|
volumes:
|
||||||
minio_data:
|
minio_data:
|
||||||
app_data:
|
app_data_cpu_s3fs:
|
||||||
|
app_data_gpu_s3fs:
|
||||||
|
app_data_cpu_fs:
|
||||||
|
app_data_gpu_fs:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
vpcbr:
|
vpcbr:
|
||||||
@ -113,5 +166,7 @@ networks:
|
|||||||
gateway: 10.5.0.1
|
gateway: 10.5.0.1
|
||||||
|
|
||||||
# Usage:
|
# Usage:
|
||||||
# - Run 'docker-compose --profile cpu up -d' to start the app_cpu service
|
# - Run 'docker compose --profile cpu-s3fs up -d' to start the app_cpu service
|
||||||
# - Run 'docker-compose --profile gpu up -d' to start the app_gpu 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
|
||||||
|
|||||||
@ -29,6 +29,10 @@ In this section, we will show you how to import a GGUF model from [HuggingFace](
|
|||||||
|
|
||||||
> We are fast shipping a UI to make this easier, but it's a bit manual for now. Apologies.
|
> We are fast shipping a UI to make this easier, but it's a bit manual for now. Apologies.
|
||||||
|
|
||||||
|
## Import Models Using Absolute Filepath (version 0.4.7)
|
||||||
|
|
||||||
|
Starting from version 0.4.7, Jan has introduced the capability to import models using an absolute file path. It allows you to import models from any directory on your computer. Please check the [import models using absolute filepath](../import-models-using-absolute-filepath) guide for more information.
|
||||||
|
|
||||||
## Manually Importing a Downloaded Model (nightly versions and v0.4.4+)
|
## Manually Importing a Downloaded Model (nightly versions and v0.4.4+)
|
||||||
|
|
||||||
### 1. Create a Model Folder
|
### 1. Create a Model Folder
|
||||||
@ -186,7 +190,6 @@ This means that you can easily reconfigure your models, export them, and share y
|
|||||||
|
|
||||||
Edit `model.json` and include the following configurations:
|
Edit `model.json` and include the following configurations:
|
||||||
|
|
||||||
- Ensure the filename must be `model.json`.
|
|
||||||
- Ensure the `id` property matches the folder name you created.
|
- Ensure the `id` property matches the folder name you created.
|
||||||
- Ensure the GGUF filename should match the `id` property exactly.
|
- Ensure the GGUF filename should match the `id` property exactly.
|
||||||
- Ensure the `source.url` property is the direct binary download link ending in `.gguf`. In HuggingFace, you can find the direct links in the `Files and versions` tab.
|
- Ensure the `source.url` property is the direct binary download link ending in `.gguf`. In HuggingFace, you can find the direct links in the `Files and versions` tab.
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
title: Import Models Using Absolute Filepath
|
||||||
|
slug: /guides/using-models/import-models-using-absolute-filepath
|
||||||
|
description: Guide to import model using absolute filepath in Jan.
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
import-models-manually,
|
||||||
|
absolute-filepath,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
In this guide, we will walk you through the process of importing a model using an absolute filepath in Jan, using our latest model, [Trinity](https://huggingface.co/janhq/trinity-v1-GGUF), as an example.
|
||||||
|
|
||||||
|
### 1. Get the Absolute Filepath of the Model
|
||||||
|
|
||||||
|
After downloading .gguf model, you can get the absolute filepath of the model file.
|
||||||
|
|
||||||
|
### 2. Configure the Model JSON
|
||||||
|
|
||||||
|
1. Navigate to the `~/jan/models` folder.
|
||||||
|
2. Create a folder named `<modelname>`, for example, `tinyllama`.
|
||||||
|
3. Create a `model.json` file inside the folder, including the following configurations:
|
||||||
|
|
||||||
|
- Ensure the `id` property matches the folder name you created.
|
||||||
|
- Ensure the `url` property is the direct binary download link ending in `.gguf`. Now, you can use the absolute filepath of the model file.
|
||||||
|
- Ensure the `engine` property is set to `nitro`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "tinyllama.gguf",
|
||||||
|
// highlight-next-line
|
||||||
|
"url": "<absolute-filepath-of-the-model-file>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "tinyllama-1.1b",
|
||||||
|
"object": "model",
|
||||||
|
"name": "(Absolute Path) TinyLlama Chat 1.1B Q4",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "TinyLlama is a tiny model with only 1.1B. It's a good model for less powerful computers.",
|
||||||
|
"format": "gguf",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"prompt_template": "<|system|>\n{system_message}<|user|>\n{prompt}<|assistant|>",
|
||||||
|
"llama_model_path": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"stream": true,
|
||||||
|
"max_tokens": 2048,
|
||||||
|
"stop": [],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "TinyLlama",
|
||||||
|
"tags": ["Tiny", "Foundation Model"],
|
||||||
|
"size": 669000000
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
- If you are using Windows, you need to use double backslashes in the url property, for example: `C:\\Users\\username\\filename.gguf`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 3. Start the Model
|
||||||
|
|
||||||
|
Restart Jan and navigate to the Hub. Locate your model and click the Use button.
|
||||||
|
|
||||||
|

|
||||||
@ -88,7 +88,7 @@ You can find your API keys in the [OpenAI Platform](https://platform.openai.com/
|
|||||||
|
|
||||||
Restart Jan and navigate to the Hub. Then, select your configured model and start the model.
|
Restart Jan and navigate to the Hub. Then, select your configured model and start the model.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Engines with OAI Compatible Configuration
|
## Engines with OAI Compatible Configuration
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ Navigate to the `~/jan/models` folder. Create a folder named `mistral-ins-7b-q4`
|
|||||||
|
|
||||||
Restart Jan and navigate to the Hub. Locate your model and click the Use button.
|
Restart Jan and navigate to the Hub. Locate your model and click the Use button.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Assistance and Support
|
## Assistance and Support
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 3.8 MiB |
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 372 KiB |
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Start Local Server
|
title: Start Local Server
|
||||||
slug: /guides/using-server/server
|
slug: /guides/using-server/start-server
|
||||||
description: How to run Jan's built-in API server.
|
description: How to run Jan's built-in API server.
|
||||||
keywords:
|
keywords:
|
||||||
[
|
[
|
||||||
|
|||||||
@ -35,7 +35,7 @@ To get started with Continue in VS Code, please follow this [guide to install Co
|
|||||||
|
|
||||||
### 2. Enable Jan API Server
|
### 2. Enable Jan API Server
|
||||||
|
|
||||||
To configure the Continue to use Jan's Local Server, you need to enable Jan API Server with your preferred model, please follow this [guide to enable Jan API Server](../05-using-server/01-server.md)
|
To configure the Continue to use Jan's Local Server, you need to enable Jan API Server with your preferred model, please follow this [guide to enable Jan API Server](/guides/using-server/start-server).
|
||||||
|
|
||||||
### 3. Configure Continue to Use Jan's Local Server
|
### 3. Configure Continue to Use Jan's Local Server
|
||||||
|
|
||||||
|
|||||||
89
docs/docs/guides/07-integrations/04-integrate-mistral-ai.mdx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
title: Integrate Mistral AI with Jan
|
||||||
|
slug: /guides/integrations/mistral-ai
|
||||||
|
description: Guide to integrate Mistral AI with Jan
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
Mistral integration,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Introduction
|
||||||
|
|
||||||
|
[Mistral AI](https://docs.mistral.ai/) currently provides two ways of accessing their Large Language Models (LLM) - via their API or via open source models available on Hugging Face. In this guide, we will show you how to integrate Mistral AI with Jan using the API method.
|
||||||
|
|
||||||
|
## Steps to Integrate Mistral AI with Jan
|
||||||
|
|
||||||
|
### 1. Configure Mistral API key
|
||||||
|
|
||||||
|
You can find your API keys in the [Mistral API Key](https://console.mistral.ai/user/api-keys/) and set the Mistral AI API key in `~/jan/engines/openai.json` file.
|
||||||
|
|
||||||
|
```json title="~/jan/engines/openai.json"
|
||||||
|
{
|
||||||
|
// highlight-start
|
||||||
|
"full_url": "https://api.mistral.ai/v1/chat/completions",
|
||||||
|
"api_key": "<your-mistral-ai-api-key>"
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Modify a Model JSON
|
||||||
|
|
||||||
|
Navigate to the `~/jan/models` folder. Create a folder named `<mistral-modelname>`, for example, `mistral-tiny` and create a `model.json` file inside the folder including the following configurations:
|
||||||
|
|
||||||
|
- Ensure the filename must be `model.json`.
|
||||||
|
- Ensure the `id` property is set to the model id from Mistral AI.
|
||||||
|
- Ensure the `format` property is set to `api`.
|
||||||
|
- Ensure the `engine` property is set to `openai`.
|
||||||
|
- Ensure the `state` property is set to `ready`.
|
||||||
|
|
||||||
|
```json title="~/jan/models/mistral-tiny/model.json"
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "mistral-tiny",
|
||||||
|
"url": "https://mistral.ai/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "mistral-tiny",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Mistral-7B-v0.2 (Tiny Endpoint)",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Currently powered by Mistral-7B-v0.2, a better fine-tuning of the initial Mistral-7B released, inspired by the fantastic work of the community.",
|
||||||
|
// highlight-next-line
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral AI",
|
||||||
|
"tags": ["General", "Big Context Length"]
|
||||||
|
},
|
||||||
|
// highlight-start
|
||||||
|
"engine": "openai"
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
Mistral AI provides different endpoints. Please check out their [endpoint documentation](https://docs.mistral.ai/platform/endpoints/) to find the one that suits your needs. In this example, we will use the `mistral-tiny` model.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 3. Start the Model
|
||||||
|
|
||||||
|
Restart Jan and navigate to the Hub. Locate your model and click the Use button.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4. Try Out the Integration of Jan and Mistral AI
|
||||||
|
|
||||||
|

|
||||||
184
docs/docs/guides/07-integrations/05-integrate-lmstudio.mdx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
---
|
||||||
|
title: Integrate LM Studio with Jan
|
||||||
|
slug: /guides/integrations/lmstudio
|
||||||
|
description: Guide to integrate LM Studio with Jan
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
LM Studio integration,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Introduction
|
||||||
|
|
||||||
|
With [LM Studio](https://lmstudio.ai/), you can discover, download, and run local Large Language Models (LLMs). In this guide, we will show you how to integrate and use your current models on LM Studio with Jan using 2 methods. The first method is integrating LM Studio server with Jan UI. The second method is migrating your downloaded model from LM Studio to Jan. We will use the [Phi 2 - GGUF](https://huggingface.co/TheBloke/phi-2-GGUF) model on Hugging Face as an example.
|
||||||
|
|
||||||
|
## Steps to Integrate LM Studio Server with Jan UI
|
||||||
|
|
||||||
|
### 1. Start the LM Studio Server
|
||||||
|
|
||||||
|
1. Navigate to the `Local Inference Server` on the LM Studio application.
|
||||||
|
2. Select the model you want to use.
|
||||||
|
3. Start the server after configuring the server port and options.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
Modify the `openai.json` file in the `~/jan/engines` folder to include the full URL of the LM Studio server.
|
||||||
|
|
||||||
|
```json title="~/jan/engines/openai.json"
|
||||||
|
{
|
||||||
|
"full_url": "http://localhost:<port>/v1/chat/completions"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
- Replace `<port>` with the port number you set in the LM Studio server. The default port is `1234`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 2. Modify a Model JSON
|
||||||
|
|
||||||
|
Navigate to the `~/jan/models` folder. Create a folder named `<lmstudio-modelname>`, for example, `lmstudio-phi-2` and create a `model.json` file inside the folder including the following configurations:
|
||||||
|
|
||||||
|
- Set the `format` property to `api`.
|
||||||
|
- Set the `engine` property to `openai`.
|
||||||
|
- Set the `state` property to `ready`.
|
||||||
|
|
||||||
|
```json title="~/jan/models/lmstudio-phi-2/model.json"
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "phi-2-GGUF",
|
||||||
|
"url": "https://huggingface.co/TheBloke/phi-2-GGUF"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "lmstudio-phi-2",
|
||||||
|
"object": "model",
|
||||||
|
"name": "LM Studio - Phi 2 - GGUF",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "TheBloke/phi-2-GGUF",
|
||||||
|
// highlight-next-line
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Microsoft",
|
||||||
|
"tags": ["General", "Big Context Length"]
|
||||||
|
},
|
||||||
|
// highlight-start
|
||||||
|
"engine": "openai"
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start the Model
|
||||||
|
|
||||||
|
1. Restart Jan and navigate to the **Hub**.
|
||||||
|
2. Locate your model and click the **Use** button.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4. Try Out the Integration of Jan and LM Studio
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Steps to Migrate Your Downloaded Model from LM Studio to Jan (version 0.4.6 and older)
|
||||||
|
|
||||||
|
### 1. Migrate Your Downloaded Model
|
||||||
|
|
||||||
|
1. Navigate to `My Models` in the LM Studio application and reveal the model folder.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Copy the model folder that you want to migrate to `~/jan/models` folder.
|
||||||
|
|
||||||
|
3. Ensure the folder name property is the same as the model name of `.gguf` filename by changing the folder name if necessary. For example, in this case, we changed foldername from `TheBloke` to `phi-2.Q4_K_S`.
|
||||||
|
|
||||||
|
### 2. Start the Model
|
||||||
|
|
||||||
|
1. Restart Jan and navigate to the **Hub**. Jan will automatically detect the model and display it in the **Hub**.
|
||||||
|
2. Locate your model and click the **Use** button to try the migrating model.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Steps to Pointing to the Downloaded Model of LM Studio from Jan (version 0.4.7+)
|
||||||
|
|
||||||
|
Starting from version 0.4.7, Jan supports importing models using an absolute filepath, so you can directly use the model from the LM Studio folder.
|
||||||
|
|
||||||
|
### 1. Reveal the Model Absolute Path
|
||||||
|
|
||||||
|
Navigate to `My Models` in the LM Studio application and reveal the model folder. Then, you can get the absolute path of your model.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2. Modify a Model JSON
|
||||||
|
|
||||||
|
Navigate to the `~/jan/models` folder. Create a folder named `<modelname>`, for example, `phi-2.Q4_K_S` and create a `model.json` file inside the folder including the following configurations:
|
||||||
|
|
||||||
|
- Ensure the `id` property matches the folder name you created.
|
||||||
|
- Ensure the `url` property is the direct binary download link ending in `.gguf`. Now, you can use the absolute filepath of the model file. In this example, the absolute filepath is `/Users/<username>/.cache/lm-studio/models/TheBloke/phi-2-GGUF/phi-2.Q4_K_S.gguf`.
|
||||||
|
- Ensure the `engine` property is set to `nitro`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"object": "model",
|
||||||
|
"version": 1,
|
||||||
|
"format": "gguf",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "phi-2.Q4_K_S.gguf",
|
||||||
|
"url": "<absolute-path-of-model-file>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "phi-2.Q4_K_S",
|
||||||
|
"name": "phi-2.Q4_K_S",
|
||||||
|
"created": 1708308111506,
|
||||||
|
"description": "phi-2.Q4_K_S - user self import model",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"embedding": false,
|
||||||
|
"prompt_template": "{system_message}\n### Instruction: {prompt}\n### Response:",
|
||||||
|
"llama_model_path": "phi-2.Q4_K_S.gguf"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"stream": true,
|
||||||
|
"max_tokens": 2048,
|
||||||
|
"stop": ["<endofstring>"],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"size": 1615568736,
|
||||||
|
"author": "User",
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
- If you are using Windows, you need to use double backslashes in the url property, for example: `C:\\Users\\username\\filename.gguf`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Start the Model
|
||||||
|
|
||||||
|
1. Restart Jan and navigate to the **Hub**.
|
||||||
|
2. Jan will automatically detect the model and display it in the **Hub**.
|
||||||
|
3. Locate your model and click the **Use** button to try the migrating model.
|
||||||
|
|
||||||
|

|
||||||
90
docs/docs/guides/07-integrations/06-integrate-ollama.mdx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
title: Integrate Ollama with Jan
|
||||||
|
slug: /guides/integrations/ollama
|
||||||
|
description: Guide to integrate Ollama with Jan
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
Ollama integration,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Introduction
|
||||||
|
|
||||||
|
With [Ollama](https://ollama.com/), you can run large language models locally. In this guide, we will show you how to integrate and use your current models on Ollama with Jan using 2 methods. The first method is integrating Ollama server with Jan UI. The second method is migrating your downloaded model from Ollama to Jan. We will use the [llama2](https://ollama.com/library/llama2) model as an example.
|
||||||
|
|
||||||
|
## Steps to Integrate Ollama Server with Jan UI
|
||||||
|
|
||||||
|
### 1. Start the Ollama Server
|
||||||
|
|
||||||
|
1. Select the model you want to use from the [Ollama library](https://ollama.com/library).
|
||||||
|
2. Run your model by using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ollama run <model-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. According to the [Ollama documentation on OpenAI compatibility](https://github.com/ollama/ollama/blob/main/docs/openai.md), you can use the `http://localhost:11434/v1/chat/completions` endpoint to interact with the Ollama server. Thus, modify the `openai.json` file in the `~/jan/engines` folder to include the full URL of the Ollama server.
|
||||||
|
|
||||||
|
```json title="~/jan/engines/openai.json"
|
||||||
|
{
|
||||||
|
"full_url": "http://localhost:11434/v1/chat/completions"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Modify a Model JSON
|
||||||
|
|
||||||
|
1. Navigate to the `~/jan/models` folder.
|
||||||
|
2. Create a folder named `<ollam-modelname>`, for example, `lmstudio-phi-2`.
|
||||||
|
3. Create a `model.json` file inside the folder including the following configurations:
|
||||||
|
|
||||||
|
- Set the `id` property to the model name as Ollama model name.
|
||||||
|
- Set the `format` property to `api`.
|
||||||
|
- Set the `engine` property to `openai`.
|
||||||
|
- Set the `state` property to `ready`.
|
||||||
|
|
||||||
|
```json title="~/jan/models/llama2/model.json"
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "llama2",
|
||||||
|
"url": "https://ollama.com/library/llama2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// highlight-next-line
|
||||||
|
"id": "llama2",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Ollama - Llama2",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Llama 2 is a collection of foundation language models ranging from 7B to 70B parameters.",
|
||||||
|
// highlight-next-line
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Meta",
|
||||||
|
"tags": ["General", "Big Context Length"]
|
||||||
|
},
|
||||||
|
// highlight-next-line
|
||||||
|
"engine": "openai"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start the Model
|
||||||
|
|
||||||
|
1. Restart Jan and navigate to the **Hub**.
|
||||||
|
2. Locate your model and click the **Use** button.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 4. Try Out the Integration of Jan and Ollama
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After Width: | Height: | Size: 8.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 5.3 MiB |
|
After Width: | Height: | Size: 5.7 MiB |
|
After Width: | Height: | Size: 6.6 MiB |
BIN
docs/docs/guides/07-integrations/assets/05-lmstudio-run.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
BIN
docs/docs/guides/07-integrations/assets/06-ollama-run.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
@ -16,6 +16,50 @@ keywords:
|
|||||||
]
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
import DocCardList from "@theme/DocCardList";
|
This guide will show you how to use the advanced settings in Jan.
|
||||||
|
|
||||||
<DocCardList />
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
Keyboard shortcuts are a great way to speed up your workflow. Here are some of the keyboard shortcuts that you can use in Jan.
|
||||||
|
|
||||||
|
| Combination | Description |
|
||||||
|
| --------------- | -------------------------------------------------- |
|
||||||
|
| `⌘ E` | Show list your models |
|
||||||
|
| `⌘ K` | Show list navigation pages |
|
||||||
|
| `⌘ B` | Toggle collapsible left panel |
|
||||||
|
| `⌘ ,` | Navigate to setting page |
|
||||||
|
| `Enter` | Send a message |
|
||||||
|
| `Shift + Enter` | Insert new line in input box |
|
||||||
|
| `Arrow Up` | Navigate to previous option (within search dialog) |
|
||||||
|
| `Arrow Down` | Navigate to next option (within search dialog) |
|
||||||
|
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
:::note
|
||||||
|
`⌘` is the command key on macOS, and `Ctrl` on Windows.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Experimental Mode
|
||||||
|
|
||||||
|
Experimental mode allows you to enable experimental features that may be unstable tested.
|
||||||
|
|
||||||
|
## Jan Data Folder
|
||||||
|
|
||||||
|
The Jan data folder is the location where messages, model configurations, and other user data are placed. You can change the location of the data folder to a different location.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## HTTPS Proxy & Ignore SSL Certificate
|
||||||
|
|
||||||
|
HTTPS Proxy allows you to use a proxy server to connect to the internet. You can also ignore SSL certificates if you are using a self-signed certificate.
|
||||||
|
Please check out the guide on [how to set up your own HTTPS proxy server and configure Jan to use it](../advanced-settings/https-proxy) for more information.
|
||||||
|
|
||||||
|
## Clear Logs
|
||||||
|
|
||||||
|
Clear logs will remove all logs from the Jan application.
|
||||||
|
|
||||||
|
## Reset To Factory Default
|
||||||
|
|
||||||
|
Reset the application to its original state, deleting all your usage data, including model customizations and conversation history. This action is irreversible and recommended only if the application is in a corrupted state.
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
|
After Width: | Height: | Size: 8.1 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 390 KiB |
@ -11,7 +11,8 @@ export function handleAppUpdates() {
|
|||||||
/* New Update Available */
|
/* New Update Available */
|
||||||
autoUpdater.on('update-available', async (_info: any) => {
|
autoUpdater.on('update-available', async (_info: any) => {
|
||||||
const action = await dialog.showMessageBox({
|
const action = await dialog.showMessageBox({
|
||||||
message: `Update available. Do you want to download the latest update?`,
|
title: 'Update Available',
|
||||||
|
message: 'Would you like to download and install it now?',
|
||||||
buttons: ['Download', 'Later'],
|
buttons: ['Download', 'Later'],
|
||||||
})
|
})
|
||||||
if (action.response === 0) await autoUpdater.downloadUpdate()
|
if (action.response === 0) await autoUpdater.downloadUpdate()
|
||||||
|
|||||||
@ -83,6 +83,7 @@ function createMainWindow() {
|
|||||||
|
|
||||||
/* Enable dev tools for development */
|
/* Enable dev tools for development */
|
||||||
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
||||||
|
log(`Version: ${app.getVersion()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"main": "./build/main.js",
|
"main": "./build/main.js",
|
||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"productName": "Jan",
|
||||||
"homepage": "https://github.com/janhq/jan/tree/main/electron",
|
"homepage": "https://github.com/janhq/jan/tree/main/electron",
|
||||||
"description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.",
|
"description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.",
|
||||||
"build": {
|
"build": {
|
||||||
|
|||||||
@ -1,18 +1,41 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { app, Menu, shell } from 'electron'
|
import { app, Menu, shell, dialog } from 'electron'
|
||||||
const isMac = process.platform === 'darwin'
|
|
||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
|
import { log } from '@janhq/core/node'
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
||||||
const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
||||||
{
|
{
|
||||||
label: app.name,
|
label: app.name,
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'about' },
|
{
|
||||||
|
label: `About ${app.name}`,
|
||||||
|
click: () =>
|
||||||
|
dialog.showMessageBox({
|
||||||
|
title: `Jan`,
|
||||||
|
message: `Jan Version v${app.getVersion()}\n\nCopyright © 2024 Jan`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Check for Updates...',
|
label: 'Check for Updates...',
|
||||||
click: () =>
|
click: () =>
|
||||||
// Check for updates and notify user if there are any
|
// Check for updates and notify user if there are any
|
||||||
autoUpdater.checkForUpdatesAndNotify(),
|
autoUpdater
|
||||||
|
.checkForUpdatesAndNotify()
|
||||||
|
.then((updateCheckResult) => {
|
||||||
|
if (
|
||||||
|
!updateCheckResult?.updateInfo ||
|
||||||
|
updateCheckResult?.updateInfo.version === app.getVersion()
|
||||||
|
) {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
message: `No updates available.`,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
log('Error checking for updates:' + JSON.stringify(error))
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'services' },
|
{ role: 'services' },
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
@echo off
|
@echo off
|
||||||
set /p NITRO_VERSION=<./bin/version.txt
|
set /p NITRO_VERSION=<./bin/version.txt
|
||||||
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu
|
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/win-vulkan && .\node_modules\.bin\download https://delta.jan.ai/vulkaninfoSDK.exe -o ./bin
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||||
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro",
|
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/linux-vulkan && chmod +x ./bin/linux-vulkan/nitro && download https://delta.jan.ai/vulkaninfo -o ./bin && chmod +x ./bin/vulkaninfo",
|
||||||
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
||||||
"downloadnitro:win32": "download.bat",
|
"downloadnitro:win32": "download.bat",
|
||||||
"downloadnitro": "run-script-os",
|
"downloadnitro": "run-script-os",
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
const electronApi = window?.electronAPI
|
const electronApi = window?.electronAPI
|
||||||
this.inferenceUrl = INFERENCE_URL
|
this.inferenceUrl = INFERENCE_URL
|
||||||
if (!electronApi) {
|
if (!electronApi) {
|
||||||
this.inferenceUrl = JAN_SERVER_INFERENCE_URL
|
this.inferenceUrl = `${window.core?.api?.baseApiUrl}/v1/chat/completions`
|
||||||
}
|
}
|
||||||
console.debug('Inference url: ', this.inferenceUrl)
|
console.debug('Inference url: ', this.inferenceUrl)
|
||||||
|
|
||||||
@ -154,7 +154,10 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (nitroInitResult?.error) {
|
if (nitroInitResult?.error) {
|
||||||
events.emit(ModelEvent.OnModelFail, model)
|
events.emit(ModelEvent.OnModelFail, {
|
||||||
|
...model,
|
||||||
|
error: nitroInitResult.error,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { writeFileSync, existsSync, readFileSync } from 'fs'
|
import { writeFileSync, existsSync, readFileSync } from 'fs'
|
||||||
import { exec } from 'child_process'
|
import { exec, spawn } from 'child_process'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
import { getJanDataFolderPath, log } from '@janhq/core/node'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default GPU settings
|
* Default GPU settings
|
||||||
|
* TODO: This needs to be refactored to support multiple accelerators
|
||||||
**/
|
**/
|
||||||
const DEFALT_SETTINGS = {
|
const DEFALT_SETTINGS = {
|
||||||
notify: true,
|
notify: true,
|
||||||
@ -21,12 +22,17 @@ const DEFALT_SETTINGS = {
|
|||||||
gpu_highest_vram: '',
|
gpu_highest_vram: '',
|
||||||
gpus_in_use: [],
|
gpus_in_use: [],
|
||||||
is_initial: true,
|
is_initial: true,
|
||||||
|
// TODO: This needs to be set based on user toggle in settings
|
||||||
|
vulkan: {
|
||||||
|
enabled: true,
|
||||||
|
gpu_in_use: '1',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to the settings file
|
* Path to the settings file
|
||||||
**/
|
**/
|
||||||
export const NVIDIA_INFO_FILE = path.join(
|
export const GPU_INFO_FILE = path.join(
|
||||||
getJanDataFolderPath(),
|
getJanDataFolderPath(),
|
||||||
'settings',
|
'settings',
|
||||||
'settings.json'
|
'settings.json'
|
||||||
@ -52,10 +58,10 @@ export async function updateNvidiaInfo() {
|
|||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
let data
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, 'utf-8'))
|
data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data = DEFALT_SETTINGS
|
data = DEFALT_SETTINGS
|
||||||
writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2))
|
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
||||||
}
|
}
|
||||||
updateNvidiaDriverInfo()
|
updateNvidiaDriverInfo()
|
||||||
updateGpuInfo()
|
updateGpuInfo()
|
||||||
@ -79,7 +85,7 @@ export async function updateNvidiaDriverInfo(): Promise<void> {
|
|||||||
exec(
|
exec(
|
||||||
'nvidia-smi --query-gpu=driver_version --format=csv,noheader',
|
'nvidia-smi --query-gpu=driver_version --format=csv,noheader',
|
||||||
(error, stdout) => {
|
(error, stdout) => {
|
||||||
let data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, 'utf-8'))
|
let data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
const firstLine = stdout.split('\n')[0].trim()
|
const firstLine = stdout.split('\n')[0].trim()
|
||||||
@ -89,7 +95,7 @@ export async function updateNvidiaDriverInfo(): Promise<void> {
|
|||||||
data['nvidia_driver'].exist = false
|
data['nvidia_driver'].exist = false
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2))
|
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -158,12 +164,46 @@ export function updateCudaExistence(
|
|||||||
* Get GPU information
|
* Get GPU information
|
||||||
*/
|
*/
|
||||||
export async function updateGpuInfo(): Promise<void> {
|
export async function updateGpuInfo(): Promise<void> {
|
||||||
|
let data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||||
|
|
||||||
|
// Cuda
|
||||||
|
if (data['vulkan'] === true) {
|
||||||
|
// Vulkan
|
||||||
|
exec(
|
||||||
|
process.platform === 'win32'
|
||||||
|
? `${__dirname}\\..\\bin\\vulkaninfoSDK.exe --summary`
|
||||||
|
: `${__dirname}/../bin/vulkaninfo --summary`,
|
||||||
|
(error, stdout) => {
|
||||||
|
if (!error) {
|
||||||
|
const output = stdout.toString()
|
||||||
|
log(output)
|
||||||
|
const gpuRegex = /GPU(\d+):(?:[\s\S]*?)deviceName\s*=\s*(.*)/g
|
||||||
|
|
||||||
|
let gpus = []
|
||||||
|
let match
|
||||||
|
while ((match = gpuRegex.exec(output)) !== null) {
|
||||||
|
const id = match[1]
|
||||||
|
const name = match[2]
|
||||||
|
gpus.push({ id, vram: 0, name })
|
||||||
|
}
|
||||||
|
data.gpus = gpus
|
||||||
|
|
||||||
|
if (!data['gpus_in_use'] || data['gpus_in_use'].length === 0) {
|
||||||
|
data.gpus_in_use = [data.gpus.length > 1 ? '1' : '0']
|
||||||
|
}
|
||||||
|
|
||||||
|
data = updateCudaExistence(data)
|
||||||
|
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
||||||
|
}
|
||||||
|
Promise.resolve()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
exec(
|
exec(
|
||||||
'nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits',
|
'nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits',
|
||||||
(error, stdout) => {
|
(error, stdout) => {
|
||||||
let data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, 'utf-8'))
|
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
|
log(stdout)
|
||||||
// Get GPU info and gpu has higher memory first
|
// Get GPU info and gpu has higher memory first
|
||||||
let highestVram = 0
|
let highestVram = 0
|
||||||
let highestVramId = '0'
|
let highestVramId = '0'
|
||||||
@ -192,8 +232,9 @@ export async function updateGpuInfo(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = updateCudaExistence(data)
|
data = updateCudaExistence(data)
|
||||||
writeFileSync(NVIDIA_INFO_FILE, JSON.stringify(data, null, 2))
|
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
||||||
Promise.resolve()
|
Promise.resolve()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { NVIDIA_INFO_FILE } from './nvidia'
|
import { GPU_INFO_FILE } from './accelerator'
|
||||||
|
|
||||||
export interface NitroExecutableOptions {
|
export interface NitroExecutableOptions {
|
||||||
executablePath: string
|
executablePath: string
|
||||||
cudaVisibleDevices: string
|
cudaVisibleDevices: string
|
||||||
|
vkVisibleDevices: string
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Find which executable file to run based on the current platform.
|
* Find which executable file to run based on the current platform.
|
||||||
@ -13,24 +14,30 @@ export interface NitroExecutableOptions {
|
|||||||
export const executableNitroFile = (): NitroExecutableOptions => {
|
export const executableNitroFile = (): NitroExecutableOptions => {
|
||||||
let binaryFolder = path.join(__dirname, '..', 'bin') // Current directory by default
|
let binaryFolder = path.join(__dirname, '..', 'bin') // Current directory by default
|
||||||
let cudaVisibleDevices = ''
|
let cudaVisibleDevices = ''
|
||||||
|
let vkVisibleDevices = ''
|
||||||
let binaryName = 'nitro'
|
let binaryName = 'nitro'
|
||||||
/**
|
/**
|
||||||
* The binary folder is different for each platform.
|
* The binary folder is different for each platform.
|
||||||
*/
|
*/
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
/**
|
/**
|
||||||
* For Windows: win-cpu, win-cuda-11-7, win-cuda-12-0
|
* For Windows: win-cpu, win-vulkan, win-cuda-11-7, win-cuda-12-0
|
||||||
*/
|
*/
|
||||||
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, 'utf-8'))
|
let gpuInfo = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||||
if (nvidiaInfo['run_mode'] === 'cpu') {
|
if (gpuInfo['run_mode'] === 'cpu') {
|
||||||
binaryFolder = path.join(binaryFolder, 'win-cpu')
|
binaryFolder = path.join(binaryFolder, 'win-cpu')
|
||||||
} else {
|
} else {
|
||||||
if (nvidiaInfo['cuda'].version === '11') {
|
if (gpuInfo['cuda']?.version === '11') {
|
||||||
binaryFolder = path.join(binaryFolder, 'win-cuda-11-7')
|
binaryFolder = path.join(binaryFolder, 'win-cuda-11-7')
|
||||||
} else {
|
} else {
|
||||||
binaryFolder = path.join(binaryFolder, 'win-cuda-12-0')
|
binaryFolder = path.join(binaryFolder, 'win-cuda-12-0')
|
||||||
}
|
}
|
||||||
cudaVisibleDevices = nvidiaInfo['gpus_in_use'].join(',')
|
cudaVisibleDevices = gpuInfo['gpus_in_use'].join(',')
|
||||||
|
}
|
||||||
|
if (gpuInfo['vulkan'] === true) {
|
||||||
|
binaryFolder = path.join(__dirname, '..', 'bin')
|
||||||
|
binaryFolder = path.join(binaryFolder, 'win-vulkan')
|
||||||
|
vkVisibleDevices = gpuInfo['gpus_in_use'].toString()
|
||||||
}
|
}
|
||||||
binaryName = 'nitro.exe'
|
binaryName = 'nitro.exe'
|
||||||
} else if (process.platform === 'darwin') {
|
} else if (process.platform === 'darwin') {
|
||||||
@ -44,22 +51,29 @@ export const executableNitroFile = (): NitroExecutableOptions => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* For Linux: linux-cpu, linux-cuda-11-7, linux-cuda-12-0
|
* For Linux: linux-cpu, linux-vulkan, linux-cuda-11-7, linux-cuda-12-0
|
||||||
*/
|
*/
|
||||||
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, 'utf-8'))
|
let gpuInfo = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
||||||
if (nvidiaInfo['run_mode'] === 'cpu') {
|
if (gpuInfo['run_mode'] === 'cpu') {
|
||||||
binaryFolder = path.join(binaryFolder, 'linux-cpu')
|
binaryFolder = path.join(binaryFolder, 'linux-cpu')
|
||||||
} else {
|
} else {
|
||||||
if (nvidiaInfo['cuda'].version === '11') {
|
if (gpuInfo['cuda']?.version === '11') {
|
||||||
binaryFolder = path.join(binaryFolder, 'linux-cuda-11-7')
|
binaryFolder = path.join(binaryFolder, 'linux-cuda-11-7')
|
||||||
} else {
|
} else {
|
||||||
binaryFolder = path.join(binaryFolder, 'linux-cuda-12-0')
|
binaryFolder = path.join(binaryFolder, 'linux-cuda-12-0')
|
||||||
}
|
}
|
||||||
cudaVisibleDevices = nvidiaInfo['gpus_in_use'].join(',')
|
cudaVisibleDevices = gpuInfo['gpus_in_use'].join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpuInfo['vulkan'] === true) {
|
||||||
|
binaryFolder = path.join(__dirname, '..', 'bin')
|
||||||
|
binaryFolder = path.join(binaryFolder, 'win-vulkan')
|
||||||
|
vkVisibleDevices = gpuInfo['gpus_in_use'].toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
executablePath: path.join(binaryFolder, binaryName),
|
executablePath: path.join(binaryFolder, binaryName),
|
||||||
cudaVisibleDevices,
|
cudaVisibleDevices,
|
||||||
|
vkVisibleDevices,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
|||||||
import tcpPortUsed from 'tcp-port-used'
|
import tcpPortUsed from 'tcp-port-used'
|
||||||
import fetchRT from 'fetch-retry'
|
import fetchRT from 'fetch-retry'
|
||||||
import { log, getSystemResourceInfo } from '@janhq/core/node'
|
import { log, getSystemResourceInfo } from '@janhq/core/node'
|
||||||
import { getNitroProcessInfo, updateNvidiaInfo } from './nvidia'
|
import { getNitroProcessInfo, updateNvidiaInfo } from './accelerator'
|
||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
InferenceEngine,
|
InferenceEngine,
|
||||||
@ -310,9 +310,15 @@ async function killSubprocess(): Promise<void> {
|
|||||||
subprocess?.kill()
|
subprocess?.kill()
|
||||||
subprocess = undefined
|
subprocess = undefined
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {}) // Do nothing with this attempt
|
||||||
.then(() => tcpPortUsed.waitUntilFree(PORT, 300, 5000))
|
.then(() => tcpPortUsed.waitUntilFree(PORT, 300, 5000))
|
||||||
.then(() => log(`[NITRO]::Debug: Nitro process is terminated`))
|
.then(() => log(`[NITRO]::Debug: Nitro process is terminated`))
|
||||||
|
.catch((err) => {
|
||||||
|
log(
|
||||||
|
`[NITRO]::Debug: Could not kill running process on port ${PORT}. Might be another process running on the same port? ${err}`
|
||||||
|
)
|
||||||
|
throw 'PORT_NOT_AVAILABLE'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -339,6 +345,10 @@ function spawnNitroProcess(): Promise<any> {
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
CUDA_VISIBLE_DEVICES: executableOptions.cudaVisibleDevices,
|
CUDA_VISIBLE_DEVICES: executableOptions.cudaVisibleDevices,
|
||||||
|
// Vulkan - Support 1 device at a time for now
|
||||||
|
...(executableOptions.vkVisibleDevices?.length > 0 && {
|
||||||
|
GGML_VULKAN_DEVICE: executableOptions.vkVisibleDevices[0],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/model-extension",
|
"name": "@janhq/model-extension",
|
||||||
"version": "1.0.23",
|
"version": "1.0.25",
|
||||||
"description": "Model Management Extension provides model exploration and seamless downloads",
|
"description": "Model Management Extension provides model exploration and seamless downloads",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/module.js",
|
"module": "dist/module.js",
|
||||||
|
|||||||
@ -37,10 +37,10 @@ const getCurrentLoad = () =>
|
|||||||
}
|
}
|
||||||
if (data.run_mode === 'gpu' && data.gpus_in_use.length > 0) {
|
if (data.run_mode === 'gpu' && data.gpus_in_use.length > 0) {
|
||||||
const gpuIds = data['gpus_in_use'].join(',')
|
const gpuIds = data['gpus_in_use'].join(',')
|
||||||
if (gpuIds !== '') {
|
if (gpuIds !== '' && data['vulkan'] !== true) {
|
||||||
exec(
|
exec(
|
||||||
`nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.total,memory.free,utilization.memory --format=csv,noheader,nounits --id=${gpuIds}`,
|
`nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.total,memory.free,utilization.memory --format=csv,noheader,nounits --id=${gpuIds}`,
|
||||||
(error, stdout, stderr) => {
|
(error, stdout, _) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(`exec error: ${error}`)
|
console.error(`exec error: ${error}`)
|
||||||
reject(error)
|
reject(error)
|
||||||
|
|||||||
32
models/dolphin-phi-2/model.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://huggingface.co/TheBloke/dolphin-2_6-phi-2-GGUF/resolve/main/dolphin-2_6-phi-2.Q8_0.gguf",
|
||||||
|
"filename": "dolphin-2_6-phi-2.Q8_0.gguf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "dolphin-phi-2",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Dolphin Phi-2 2.7B Q8",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Dolphin Phi-2 is a 2.7B model, fine-tuned for chat, excelling in common sense and logical reasoning benchmarks.",
|
||||||
|
"format": "gguf",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"prompt_template": "<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant",
|
||||||
|
"llama_model_path": "dolphin-2_6-phi-2.Q8_0.gguf"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"stop": ["<|im_end|>"]
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Cognitive Computations, Microsoft",
|
||||||
|
"tags": [
|
||||||
|
"3B",
|
||||||
|
"Finetuned"
|
||||||
|
],
|
||||||
|
"size": 2960000000
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
37
models/llamacorn-1.1b/model.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url":"https://huggingface.co/janhq/llamacorn-1.1b-chat-GGUF/resolve/main/llamacorn-1.1b-chat.Q8_0.gguf",
|
||||||
|
"filename": "llamacorn-1.1b-chat.Q8_0.gguf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "llamacorn-1.1b",
|
||||||
|
"object": "model",
|
||||||
|
"name": "LlamaCorn 1.1B Q8",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "LlamaCorn is designed to improve chat functionality from TinyLlama.",
|
||||||
|
"format": "gguf",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 2048,
|
||||||
|
"prompt_template": "<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant",
|
||||||
|
"llama_model_path": "llamacorn-1.1b-chat.Q8_0.gguf"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"stream": true,
|
||||||
|
"max_tokens": 2048,
|
||||||
|
"stop": [],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Jan",
|
||||||
|
"tags": [
|
||||||
|
"Tiny",
|
||||||
|
"Finetuned"
|
||||||
|
],
|
||||||
|
"size": 1170000000
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"author": "MistralAI, The Bloke",
|
"author": "MistralAI, The Bloke",
|
||||||
"tags": ["Featured", "7B", "Foundational Model"],
|
"tags": ["Featured", "7B", "Foundational Model"],
|
||||||
"size": 4370000000,
|
"size": 4370000000,
|
||||||
"cover": "https://raw.githubusercontent.com/janhq/jan/main/models/mistral-ins-7b-q4/cover.png"
|
"cover": "https://raw.githubusercontent.com/janhq/jan/dev/models/mistral-ins-7b-q4/cover.png"
|
||||||
},
|
},
|
||||||
"engine": "nitro"
|
"engine": "nitro"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"filename": "openchat-3.5-1210.Q4_K_M.gguf",
|
"filename": "openchat-3.5-0106.Q4_K_M.gguf",
|
||||||
"url": "https://huggingface.co/TheBloke/openchat-3.5-1210-GGUF/resolve/main/openchat-3.5-1210.Q4_K_M.gguf"
|
"url": "https://huggingface.co/TheBloke/openchat-3.5-0106-GGUF/resolve/main/openchat-3.5-0106.Q4_K_M.gguf"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "openchat-3.5-7b",
|
"id": "openchat-3.5-7b",
|
||||||
@ -14,7 +14,7 @@
|
|||||||
"settings": {
|
"settings": {
|
||||||
"ctx_len": 4096,
|
"ctx_len": 4096,
|
||||||
"prompt_template": "GPT4 Correct User: {prompt}<|end_of_turn|>GPT4 Correct Assistant:",
|
"prompt_template": "GPT4 Correct User: {prompt}<|end_of_turn|>GPT4 Correct Assistant:",
|
||||||
"llama_model_path": "openchat-3.5-1210.Q4_K_M.gguf"
|
"llama_model_path": "openchat-3.5-0106.Q4_K_M.gguf"
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
"author": "Intel, Jan",
|
"author": "Intel, Jan",
|
||||||
"tags": ["7B", "Merged", "Featured"],
|
"tags": ["7B", "Merged", "Featured"],
|
||||||
"size": 4370000000,
|
"size": 4370000000,
|
||||||
"cover": "https://raw.githubusercontent.com/janhq/jan/main/models/openhermes-neural-7b/cover.png"
|
"cover": "https://raw.githubusercontent.com/janhq/jan/dev/models/openhermes-neural-7b/cover.png"
|
||||||
},
|
},
|
||||||
"engine": "nitro"
|
"engine": "nitro"
|
||||||
}
|
}
|
||||||
|
|||||||
34
models/stable-zephyr-3b/model.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q8_0.gguf",
|
||||||
|
"filename": "stablelm-zephyr-3b.Q8_0.gguf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "stable-zephyr-3b",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Stable Zephyr 3B Q8",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "StableLM Zephyr 3B is trained for safe and reliable chatting.",
|
||||||
|
"format": "gguf",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"prompt_template": "<|user|>\n{prompt}<|endoftext|>\n<|assistant|>",
|
||||||
|
"llama_model_path": "stablelm-zephyr-3b.Q8_0.gguf"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"stream": true,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"stop": ["<|endoftext|>"],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "StabilityAI",
|
||||||
|
"tags": ["3B", "Finetuned"],
|
||||||
|
"size": 2970000000
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"author": "Jan",
|
"author": "Jan",
|
||||||
"tags": ["7B", "Merged", "Featured"],
|
"tags": ["7B", "Merged", "Featured"],
|
||||||
"size": 4370000000,
|
"size": 4370000000,
|
||||||
"cover": "https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png"
|
"cover": "https://raw.githubusercontent.com/janhq/jan/dev/models/trinity-v1.2-7b/cover.png"
|
||||||
},
|
},
|
||||||
"engine": "nitro"
|
"engine": "nitro"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"url": "https://huggingface.co/TheBloke/Yarn-Mistral-7B-128k-GGUF/resolve/main/yarn-mistral-7b-128k.Q4_K_M.gguf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "yarn-mistral-7b",
|
|
||||||
"object": "model",
|
|
||||||
"name": "Yarn Mistral 7B Q4",
|
|
||||||
"version": "1.0",
|
|
||||||
"description": "Yarn Mistral 7B is a language model for long context and supports a 128k token context window.",
|
|
||||||
"format": "gguf",
|
|
||||||
"settings": {
|
|
||||||
"ctx_len": 4096,
|
|
||||||
"prompt_template": "{prompt}"
|
|
||||||
},
|
|
||||||
"parameters": {
|
|
||||||
"temperature": 0.7,
|
|
||||||
"top_p": 0.95,
|
|
||||||
"stream": true,
|
|
||||||
"max_tokens": 4096,
|
|
||||||
"frequency_penalty": 0,
|
|
||||||
"presence_penalty": 0
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"author": "NousResearch, The Bloke",
|
|
||||||
"tags": ["7B", "Finetuned"],
|
|
||||||
"size": 4370000000
|
|
||||||
},
|
|
||||||
"engine": "nitro"
|
|
||||||
}
|
|
||||||
@ -31,6 +31,32 @@ export async function setup() {
|
|||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!existsSync(join(appDir, 'settings'))) {
|
||||||
|
console.debug('Writing nvidia config file...')
|
||||||
|
mkdirSync(join(appDir, 'settings'))
|
||||||
|
writeFileSync(
|
||||||
|
join(appDir, 'settings', 'settings.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
notify: true,
|
||||||
|
run_mode: 'cpu',
|
||||||
|
nvidia_driver: {
|
||||||
|
exist: false,
|
||||||
|
version: '',
|
||||||
|
},
|
||||||
|
cuda: {
|
||||||
|
exist: false,
|
||||||
|
version: '',
|
||||||
|
},
|
||||||
|
gpus: [],
|
||||||
|
gpu_highest_vram: '',
|
||||||
|
gpus_in_use: [],
|
||||||
|
is_initial: true,
|
||||||
|
}),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install extensions
|
* Install extensions
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
getJanExtensionsPath,
|
getJanExtensionsPath,
|
||||||
} from '@janhq/core/node'
|
} from '@janhq/core/node'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import tcpPortUsed from 'tcp-port-used'
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -46,6 +47,15 @@ export interface ServerConfig {
|
|||||||
* @param configs - Server configurations
|
* @param configs - Server configurations
|
||||||
*/
|
*/
|
||||||
export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
||||||
|
if (configs?.port && configs?.host) {
|
||||||
|
const inUse = await tcpPortUsed.check(Number(configs.port), configs.host)
|
||||||
|
if (inUse) {
|
||||||
|
const errorMessage = `Port ${configs.port} is already in use.`
|
||||||
|
logServer(errorMessage)
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update server settings
|
// Update server settings
|
||||||
isVerbose = configs?.isVerboseEnabled ?? true
|
isVerbose = configs?.isVerboseEnabled ?? true
|
||||||
hostSetting = configs?.host ?? JAN_API_HOST
|
hostSetting = configs?.host ?? JAN_API_HOST
|
||||||
|
|||||||
@ -27,19 +27,20 @@
|
|||||||
"@npmcli/arborist": "^7.3.1",
|
"@npmcli/arborist": "^7.3.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"fastify": "^4.24.3",
|
"fastify": "^4.24.3",
|
||||||
"request": "^2.88.2",
|
|
||||||
"fetch-retry": "^5.0.6",
|
"fetch-retry": "^5.0.6",
|
||||||
"tcp-port-used": "^1.0.2",
|
"node-fetch": "2",
|
||||||
"request-progress": "^3.0.0"
|
"request": "^2.88.2",
|
||||||
|
"request-progress": "^3.0.0",
|
||||||
|
"tcp-port-used": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/body-parser": "^1.19.5",
|
"@types/body-parser": "^1.19.5",
|
||||||
"@types/npmcli__arborist": "^5.6.4",
|
"@types/npmcli__arborist": "^5.6.4",
|
||||||
|
"@types/tcp-port-used": "^1.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
"@typescript-eslint/parser": "^6.7.3",
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"run-script-os": "^1.1.6",
|
"run-script-os": "^1.1.6",
|
||||||
"@types/tcp-port-used": "^1.0.4",
|
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ interface Props {
|
|||||||
rightAction?: ReactNode
|
rightAction?: ReactNode
|
||||||
title: string
|
title: string
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
|
isShow?: boolean
|
||||||
hideMoreVerticalAction?: boolean
|
hideMoreVerticalAction?: boolean
|
||||||
}
|
}
|
||||||
export default function CardSidebar({
|
export default function CardSidebar({
|
||||||
@ -30,8 +31,9 @@ export default function CardSidebar({
|
|||||||
asChild,
|
asChild,
|
||||||
rightAction,
|
rightAction,
|
||||||
hideMoreVerticalAction,
|
hideMoreVerticalAction,
|
||||||
|
isShow,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(isShow ?? false)
|
||||||
const [more, setMore] = useState(false)
|
const [more, setMore] = useState(false)
|
||||||
const [menu, setMenu] = useState<HTMLDivElement | null>(null)
|
const [menu, setMenu] = useState<HTMLDivElement | null>(null)
|
||||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||||
@ -67,8 +69,8 @@ export default function CardSidebar({
|
|||||||
show && 'rotate-180'
|
show && 'rotate-180'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
|
||||||
<span className="font-bold">{title}</span>
|
<span className="font-bold">{title}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{rightAction && rightAction}
|
{rightAction && rightAction}
|
||||||
|
|||||||
@ -195,7 +195,12 @@ const DropdownListSidebar = ({
|
|||||||
</SelectValue>
|
</SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectPortal>
|
<SelectPortal>
|
||||||
<SelectContent className="right-2 block w-full min-w-[450px] pr-0">
|
<SelectContent
|
||||||
|
className={twMerge(
|
||||||
|
'right-2 block w-full min-w-[450px] pr-0',
|
||||||
|
isTabActive === 1 && '[&_.select-scroll-down-button]:hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="relative px-2 py-2 dark:bg-secondary/50">
|
<div className="relative px-2 py-2 dark:bg-secondary/50">
|
||||||
<ul className="inline-flex w-full space-x-2 rounded-lg bg-zinc-100 px-1 dark:bg-secondary">
|
<ul className="inline-flex w-full space-x-2 rounded-lg bg-zinc-100 px-1 dark:bg-secondary">
|
||||||
{engineOptions.map((name, i) => {
|
{engineOptions.map((name, i) => {
|
||||||
|
|||||||
@ -152,7 +152,7 @@ const BottomBar = () => {
|
|||||||
{gpus.length > 0 && (
|
{gpus.length > 0 && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<div className="flex cursor-pointer items-center">
|
<div className="flex items-center">
|
||||||
<SystemItem
|
<SystemItem
|
||||||
name={`${gpus.length} GPU `}
|
name={`${gpus.length} GPU `}
|
||||||
value={`${calculateUtilization()}% `}
|
value={`${calculateUtilization()}% `}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
import { motion as m } from 'framer-motion'
|
import { motion as m } from 'framer-motion'
|
||||||
|
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom, useSetAtom } from 'jotai'
|
||||||
import {
|
import {
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
@ -23,16 +23,19 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
|
import { editMessageAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
import { serverEnabledAtom } from '@/helpers/atoms/LocalServer.atom'
|
||||||
|
|
||||||
export default function RibbonNav() {
|
export default function RibbonNav() {
|
||||||
const { mainViewState, setMainViewState } = useMainViewState()
|
const { mainViewState, setMainViewState } = useMainViewState()
|
||||||
const [serverEnabled] = useAtom(serverEnabledAtom)
|
const [serverEnabled] = useAtom(serverEnabledAtom)
|
||||||
|
const setEditMessage = useSetAtom(editMessageAtom)
|
||||||
|
|
||||||
const onMenuClick = (state: MainViewState) => {
|
const onMenuClick = (state: MainViewState) => {
|
||||||
if (mainViewState === state) return
|
if (mainViewState === state) return
|
||||||
if (serverEnabled && state === MainViewState.Thread) return
|
if (serverEnabled && state === MainViewState.Thread) return
|
||||||
setMainViewState(state)
|
setMainViewState(state)
|
||||||
|
setEditMessage('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const primaryMenus = [
|
const primaryMenus = [
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const OpenAiKeyInput: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4">
|
<div className="my-4">
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
id="thread-title"
|
||||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||||
|
|||||||
@ -114,8 +114,8 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
const onModelInitFailed = useCallback(
|
const onModelInitFailed = useCallback(
|
||||||
(res: any) => {
|
(res: any) => {
|
||||||
const errorMessage = `${res.error}`
|
const errorMessage = res?.error ?? res
|
||||||
console.error('Failed to load model: ' + errorMessage)
|
console.error('Failed to load model: ', errorMessage)
|
||||||
setStateModel(() => ({
|
setStateModel(() => ({
|
||||||
state: 'start',
|
state: 'start',
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@ -4,18 +4,26 @@ interface FeatureToggleContextType {
|
|||||||
experimentalFeature: boolean
|
experimentalFeature: boolean
|
||||||
ignoreSSL: boolean
|
ignoreSSL: boolean
|
||||||
proxy: string
|
proxy: string
|
||||||
|
proxyEnabled: boolean
|
||||||
|
vulkanEnabled: boolean
|
||||||
setExperimentalFeature: (on: boolean) => void
|
setExperimentalFeature: (on: boolean) => void
|
||||||
|
setVulkanEnabled: (on: boolean) => void
|
||||||
setIgnoreSSL: (on: boolean) => void
|
setIgnoreSSL: (on: boolean) => void
|
||||||
setProxy: (value: string) => void
|
setProxy: (value: string) => void
|
||||||
|
setProxyEnabled: (on: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialContext: FeatureToggleContextType = {
|
const initialContext: FeatureToggleContextType = {
|
||||||
experimentalFeature: false,
|
experimentalFeature: false,
|
||||||
ignoreSSL: false,
|
ignoreSSL: false,
|
||||||
proxy: '',
|
proxy: '',
|
||||||
|
proxyEnabled: false,
|
||||||
|
vulkanEnabled: false,
|
||||||
setExperimentalFeature: () => {},
|
setExperimentalFeature: () => {},
|
||||||
|
setVulkanEnabled: () => {},
|
||||||
setIgnoreSSL: () => {},
|
setIgnoreSSL: () => {},
|
||||||
setProxy: () => {},
|
setProxy: () => {},
|
||||||
|
setProxyEnabled: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeatureToggleContext =
|
export const FeatureToggleContext =
|
||||||
@ -27,10 +35,15 @@ export default function FeatureToggleWrapper({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
const EXPERIMENTAL_FEATURE = 'experimentalFeature'
|
||||||
|
const VULKAN_ENABLED = 'vulkanEnabled'
|
||||||
const IGNORE_SSL = 'ignoreSSLFeature'
|
const IGNORE_SSL = 'ignoreSSLFeature'
|
||||||
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
const HTTPS_PROXY_FEATURE = 'httpsProxyFeature'
|
||||||
|
const PROXY_FEATURE_ENABLED = 'proxyFeatureEnabled'
|
||||||
|
|
||||||
const [experimentalFeature, directSetExperimentalFeature] =
|
const [experimentalFeature, directSetExperimentalFeature] =
|
||||||
useState<boolean>(false)
|
useState<boolean>(false)
|
||||||
|
const [proxyEnabled, directSetProxyEnabled] = useState<boolean>(false)
|
||||||
|
const [vulkanEnabled, directEnableVulkan] = useState<boolean>(false)
|
||||||
const [ignoreSSL, directSetIgnoreSSL] = useState<boolean>(false)
|
const [ignoreSSL, directSetIgnoreSSL] = useState<boolean>(false)
|
||||||
const [proxy, directSetProxy] = useState<string>('')
|
const [proxy, directSetProxy] = useState<string>('')
|
||||||
|
|
||||||
@ -40,6 +53,9 @@ export default function FeatureToggleWrapper({
|
|||||||
)
|
)
|
||||||
directSetIgnoreSSL(localStorage.getItem(IGNORE_SSL) === 'true')
|
directSetIgnoreSSL(localStorage.getItem(IGNORE_SSL) === 'true')
|
||||||
directSetProxy(localStorage.getItem(HTTPS_PROXY_FEATURE) ?? '')
|
directSetProxy(localStorage.getItem(HTTPS_PROXY_FEATURE) ?? '')
|
||||||
|
directSetProxyEnabled(
|
||||||
|
localStorage.getItem(PROXY_FEATURE_ENABLED) === 'true'
|
||||||
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const setExperimentalFeature = (on: boolean) => {
|
const setExperimentalFeature = (on: boolean) => {
|
||||||
@ -47,6 +63,11 @@ export default function FeatureToggleWrapper({
|
|||||||
directSetExperimentalFeature(on)
|
directSetExperimentalFeature(on)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setVulkanEnabled = (on: boolean) => {
|
||||||
|
localStorage.setItem(VULKAN_ENABLED, on ? 'true' : 'false')
|
||||||
|
directEnableVulkan(on)
|
||||||
|
}
|
||||||
|
|
||||||
const setIgnoreSSL = (on: boolean) => {
|
const setIgnoreSSL = (on: boolean) => {
|
||||||
localStorage.setItem(IGNORE_SSL, on ? 'true' : 'false')
|
localStorage.setItem(IGNORE_SSL, on ? 'true' : 'false')
|
||||||
directSetIgnoreSSL(on)
|
directSetIgnoreSSL(on)
|
||||||
@ -57,15 +78,24 @@ export default function FeatureToggleWrapper({
|
|||||||
directSetProxy(proxy)
|
directSetProxy(proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setProxyEnabled = (on: boolean) => {
|
||||||
|
localStorage.setItem(PROXY_FEATURE_ENABLED, on ? 'true' : 'false')
|
||||||
|
directSetProxyEnabled(on)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FeatureToggleContext.Provider
|
<FeatureToggleContext.Provider
|
||||||
value={{
|
value={{
|
||||||
experimentalFeature,
|
experimentalFeature,
|
||||||
ignoreSSL,
|
ignoreSSL,
|
||||||
proxy,
|
proxy,
|
||||||
|
proxyEnabled,
|
||||||
|
vulkanEnabled,
|
||||||
setExperimentalFeature,
|
setExperimentalFeature,
|
||||||
|
setVulkanEnabled,
|
||||||
setIgnoreSSL,
|
setIgnoreSSL,
|
||||||
setProxy,
|
setProxy,
|
||||||
|
setProxyEnabled,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -83,7 +83,10 @@ export class ExtensionManager {
|
|||||||
// Import class
|
// Import class
|
||||||
const extensionUrl = window.electronAPI
|
const extensionUrl = window.electronAPI
|
||||||
? extension.url
|
? extension.url
|
||||||
: extension.url.replace('extension://', `${API_BASE_URL}/extensions/`)
|
: extension.url.replace(
|
||||||
|
'extension://',
|
||||||
|
`${window.core?.api?.baseApiUrl ?? ''}/extensions/`
|
||||||
|
)
|
||||||
await import(/* webpackIgnore: true */ extensionUrl).then(
|
await import(/* webpackIgnore: true */ extensionUrl).then(
|
||||||
(extensionClass) => {
|
(extensionClass) => {
|
||||||
// Register class if it has a default export
|
// Register class if it has a default export
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { extensionManager } from '@/extension/ExtensionManager'
|
|||||||
import { addDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
import { addDownloadingModelAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
export default function useDownloadModel() {
|
export default function useDownloadModel() {
|
||||||
const { ignoreSSL, proxy } = useContext(FeatureToggleContext)
|
const { ignoreSSL, proxy, proxyEnabled } = useContext(FeatureToggleContext)
|
||||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||||
const addDownloadingModel = useSetAtom(addDownloadingModelAtom)
|
const addDownloadingModel = useSetAtom(addDownloadingModelAtom)
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ export default function useDownloadModel() {
|
|||||||
|
|
||||||
addDownloadingModel(model)
|
addDownloadingModel(model)
|
||||||
|
|
||||||
await localDownloadModel(model, ignoreSSL, proxy)
|
await localDownloadModel(model, ignoreSSL, proxyEnabled ? proxy : '')
|
||||||
},
|
},
|
||||||
[ignoreSSL, proxy, addDownloadingModel, setDownloadState]
|
[ignoreSSL, proxy, proxyEnabled, addDownloadingModel, setDownloadState]
|
||||||
)
|
)
|
||||||
|
|
||||||
const abortModelDownload = useCallback(async (model: Model) => {
|
const abortModelDownload = useCallback(async (model: Model) => {
|
||||||
|
|||||||
@ -130,8 +130,8 @@ export const setDownloadStateAtom = atom(
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
modelDownloadState.size.transferred = transferredSize
|
modelDownloadState.size.transferred = transferredSize
|
||||||
modelDownloadState.percent = transferredSize / parentTotalSize
|
modelDownloadState.percent =
|
||||||
|
parentTotalSize === 0 ? 0 : transferredSize / parentTotalSize
|
||||||
currentState[state.modelId] = modelDownloadState
|
currentState[state.modelId] = modelDownloadState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -297,6 +297,10 @@ export default function useSendChatMessage() {
|
|||||||
const updatedThread: Thread = {
|
const updatedThread: Thread = {
|
||||||
...activeThread,
|
...activeThread,
|
||||||
updated: timestamp,
|
updated: timestamp,
|
||||||
|
metadata: {
|
||||||
|
...(activeThread.metadata ?? {}),
|
||||||
|
lastMessage: prompt,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// change last update thread when send message
|
// change last update thread when send message
|
||||||
|
|||||||
@ -48,17 +48,33 @@ export const useSettings = () => {
|
|||||||
runMode,
|
runMode,
|
||||||
notify,
|
notify,
|
||||||
gpusInUse,
|
gpusInUse,
|
||||||
|
vulkan,
|
||||||
}: {
|
}: {
|
||||||
runMode?: string | undefined
|
runMode?: string | undefined
|
||||||
notify?: boolean | undefined
|
notify?: boolean | undefined
|
||||||
gpusInUse?: string[] | undefined
|
gpusInUse?: string[] | undefined
|
||||||
|
vulkan?: boolean | undefined
|
||||||
}) => {
|
}) => {
|
||||||
const settingsFile = await joinPath(['file://settings', 'settings.json'])
|
const settingsFile = await joinPath(['file://settings', 'settings.json'])
|
||||||
const settings = await readSettings()
|
const settings = await readSettings()
|
||||||
if (runMode != null) settings.run_mode = runMode
|
if (runMode != null) settings.run_mode = runMode
|
||||||
if (notify != null) settings.notify = notify
|
if (notify != null) settings.notify = notify
|
||||||
if (gpusInUse != null) settings.gpus_in_use = gpusInUse
|
if (gpusInUse != null) settings.gpus_in_use = gpusInUse
|
||||||
|
if (vulkan != null) {
|
||||||
|
settings.vulkan = vulkan
|
||||||
|
// GPU enabled, set run_mode to 'gpu'
|
||||||
|
if (settings.vulkan) {
|
||||||
|
settings.run_mode = 'gpu'
|
||||||
|
} else {
|
||||||
|
settings.run_mode = settings.gpus?.length > 0 ? 'gpu' : 'cpu'
|
||||||
|
}
|
||||||
|
}
|
||||||
await fs.writeFileSync(settingsFile, JSON.stringify(settings))
|
await fs.writeFileSync(settingsFile, JSON.stringify(settings))
|
||||||
|
|
||||||
|
// Relaunch to apply settings
|
||||||
|
if (vulkan != null) {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -6,6 +6,11 @@ const webpack = require('webpack')
|
|||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
eslint: {
|
||||||
|
// Warning: This allows production builds to successfully complete even if
|
||||||
|
// your project has ESLint errors.
|
||||||
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
output: 'export',
|
output: 'export',
|
||||||
assetPrefix: '.',
|
assetPrefix: '.',
|
||||||
images: {
|
images: {
|
||||||
|
|||||||
@ -145,7 +145,7 @@ const ChatInput: React.FC = () => {
|
|||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'max-h-[400px] resize-none overflow-y-hidden pr-20',
|
'max-h-[400px] resize-none pr-20',
|
||||||
fileUpload.length && 'rounded-t-none'
|
fileUpload.length && 'rounded-t-none'
|
||||||
)}
|
)}
|
||||||
style={{ height: '40px' }}
|
style={{ height: '40px' }}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
@ -10,7 +10,17 @@ import {
|
|||||||
events,
|
events,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { Textarea, Button } from '@janhq/uikit'
|
import {
|
||||||
|
Textarea,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
ModalClose,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalPortal,
|
||||||
|
ModalTitle,
|
||||||
|
} from '@janhq/uikit'
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
@ -51,6 +61,7 @@ const EditChatInput: React.FC<Props> = ({ message }) => {
|
|||||||
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const setEditMessage = useSetAtom(editMessageAtom)
|
const setEditMessage = useSetAtom(editMessageAtom)
|
||||||
|
const [showDialog, setshowDialog] = useState(false)
|
||||||
|
|
||||||
const onPromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const onPromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setEditPrompt(e.target.value)
|
setEditPrompt(e.target.value)
|
||||||
@ -152,6 +163,28 @@ const EditChatInput: React.FC<Props> = ({ message }) => {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal open={showDialog} onOpenChange={() => setshowDialog(false)}>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Edit Message</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Do you want to discard the change
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={() => setshowDialog(false)}>
|
||||||
|
<Button themes="outline">Cancel</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<ModalClose asChild onClick={() => setEditMessage('')}>
|
||||||
|
<Button autoFocus>Yes</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import ModalTroubleShooting, {
|
|||||||
modalTroubleShootingAtom,
|
modalTroubleShootingAtom,
|
||||||
} from '@/containers/ModalTroubleShoot'
|
} from '@/containers/ModalTroubleShoot'
|
||||||
|
|
||||||
|
import { loadModelErrorAtom } from '@/hooks/useActiveModel'
|
||||||
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
@ -15,6 +16,8 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const { resendChatMessage } = useSendChatMessage()
|
const { resendChatMessage } = useSendChatMessage()
|
||||||
const setModalTroubleShooting = useSetAtom(modalTroubleShootingAtom)
|
const setModalTroubleShooting = useSetAtom(modalTroubleShootingAtom)
|
||||||
|
const loadModelError = useAtomValue(loadModelErrorAtom)
|
||||||
|
const PORT_NOT_AVAILABLE = 'PORT_NOT_AVAILABLE'
|
||||||
|
|
||||||
const regenerateMessage = async () => {
|
const regenerateMessage = async () => {
|
||||||
const lastMessageIndex = messages.length - 1
|
const lastMessageIndex = messages.length - 1
|
||||||
@ -23,9 +26,9 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="mt-10">
|
||||||
{message.status === MessageStatus.Stopped && (
|
{message.status === MessageStatus.Stopped && (
|
||||||
<div key={message.id} className="mt-10 flex flex-col items-center">
|
<div key={message.id} className="flex flex-col items-center">
|
||||||
<span className="mb-3 text-center text-sm font-medium text-gray-500">
|
<span className="mb-3 text-center text-sm font-medium text-gray-500">
|
||||||
Oops! The generation was interrupted. Let's give it another go!
|
Oops! The generation was interrupted. Let's give it another go!
|
||||||
</span>
|
</span>
|
||||||
@ -41,6 +44,26 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{message.status === MessageStatus.Error && (
|
{message.status === MessageStatus.Error && (
|
||||||
|
<>
|
||||||
|
{loadModelError === PORT_NOT_AVAILABLE ? (
|
||||||
|
<div
|
||||||
|
key={message.id}
|
||||||
|
className="flex flex-col items-center text-center text-sm font-medium text-gray-500 w-full"
|
||||||
|
>
|
||||||
|
<p className="w-[90%]">
|
||||||
|
Port 3928 is currently unavailable. Check for conflicting apps,
|
||||||
|
or access
|
||||||
|
<span
|
||||||
|
className="cursor-pointer text-primary dark:text-blue-400"
|
||||||
|
onClick={() => setModalTroubleShooting(true)}
|
||||||
|
>
|
||||||
|
troubleshooting assistance
|
||||||
|
</span>
|
||||||
|
for further support.
|
||||||
|
</p>
|
||||||
|
<ModalTroubleShooting />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div
|
<div
|
||||||
key={message.id}
|
key={message.id}
|
||||||
className="flex flex-col items-center text-center text-sm font-medium text-gray-500"
|
className="flex flex-col items-center text-center text-sm font-medium text-gray-500"
|
||||||
@ -60,6 +83,8 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default ErrorMessage
|
export default ErrorMessage
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
ConversationalExtension,
|
ConversationalExtension,
|
||||||
ContentType,
|
ContentType,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
import {
|
import {
|
||||||
RefreshCcw,
|
RefreshCcw,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
@ -30,7 +30,7 @@ import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
|||||||
|
|
||||||
const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
||||||
const deleteMessage = useSetAtom(deleteMessageAtom)
|
const deleteMessage = useSetAtom(deleteMessageAtom)
|
||||||
const [editMessage, setEditMessage] = useAtom(editMessageAtom)
|
const setEditMessage = useSetAtom(editMessageAtom)
|
||||||
const thread = useAtomValue(activeThreadAtom)
|
const thread = useAtomValue(activeThreadAtom)
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const { resendChatMessage } = useSendChatMessage()
|
const { resendChatMessage } = useSendChatMessage()
|
||||||
@ -49,11 +49,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onEditClick = async () => {
|
const onEditClick = async () => {
|
||||||
if (!editMessage.length) {
|
|
||||||
setEditMessage(message.id ?? '')
|
setEditMessage(message.id ?? '')
|
||||||
} else {
|
|
||||||
setEditMessage('')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onRegenerateClick = async () => {
|
const onRegenerateClick = async () => {
|
||||||
|
|||||||
@ -116,7 +116,7 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardSidebar title="Assistant">
|
<CardSidebar title="Assistant" isShow={true}>
|
||||||
<div className="flex flex-col space-y-4 p-2">
|
<div className="flex flex-col space-y-4 p-2">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<LogoMark width={24} height={24} />
|
<LogoMark width={24} height={24} />
|
||||||
@ -152,11 +152,52 @@ const Sidebar: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
|
|
||||||
|
<CardSidebar title="Model" isShow={true}>
|
||||||
|
<div className="px-2 pt-4">
|
||||||
|
<DropdownListSidebar />
|
||||||
|
|
||||||
|
{componentDataRuntimeSetting.length > 0 && (
|
||||||
|
<div className="mt-6">
|
||||||
|
<CardSidebar title="Inference Parameters" asChild>
|
||||||
|
<div className="px-2 py-4">
|
||||||
|
<ModelSetting componentData={componentDataRuntimeSetting} />
|
||||||
|
</div>
|
||||||
|
</CardSidebar>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{componentDataEngineSetting.filter(
|
||||||
|
(x) => x.name === 'prompt_template'
|
||||||
|
).length !== 0 && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<CardSidebar title="Model Parameters" asChild>
|
||||||
|
<div className="px-2 py-4">
|
||||||
|
<SettingComponentBuilder
|
||||||
|
componentData={componentDataEngineSetting}
|
||||||
|
selector={(x: any) => x.name === 'prompt_template'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardSidebar>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{componentDataEngineSetting.length > 0 && (
|
||||||
|
<div className="my-4">
|
||||||
|
<CardSidebar title="Engine Parameters" asChild>
|
||||||
|
<div className="px-2 py-4">
|
||||||
|
<EngineSetting componentData={componentDataEngineSetting} />
|
||||||
|
</div>
|
||||||
|
</CardSidebar>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardSidebar>
|
||||||
|
|
||||||
{experimentalFeature && (
|
{experimentalFeature && (
|
||||||
<div>
|
<div>
|
||||||
{activeThread?.assistants[0]?.tools &&
|
{activeThread?.assistants[0]?.tools &&
|
||||||
componentDataAssistantSetting.length > 0 && (
|
componentDataAssistantSetting.length > 0 && (
|
||||||
<CardSidebar title="Tools">
|
<CardSidebar title="Tools" isShow={true}>
|
||||||
<div className="px-2 pt-4">
|
<div className="px-2 pt-4">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -312,47 +353,6 @@ const Sidebar: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CardSidebar title="Model">
|
|
||||||
<div className="px-2 pt-4">
|
|
||||||
<DropdownListSidebar />
|
|
||||||
|
|
||||||
{componentDataRuntimeSetting.length > 0 && (
|
|
||||||
<div className="mt-6">
|
|
||||||
<CardSidebar title="Inference Parameters" asChild>
|
|
||||||
<div className="px-2 py-4">
|
|
||||||
<ModelSetting componentData={componentDataRuntimeSetting} />
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{componentDataEngineSetting.filter(
|
|
||||||
(x) => x.name === 'prompt_template'
|
|
||||||
).length !== 0 && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<CardSidebar title="Model Parameters" asChild>
|
|
||||||
<div className="px-2 py-4">
|
|
||||||
<SettingComponentBuilder
|
|
||||||
componentData={componentDataEngineSetting}
|
|
||||||
selector={(x: any) => x.name === 'prompt_template'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{componentDataEngineSetting.length > 0 && (
|
|
||||||
<div className="my-4">
|
|
||||||
<CardSidebar title="Engine Parameters" asChild>
|
|
||||||
<div className="px-2 py-4">
|
|
||||||
<EngineSetting componentData={componentDataEngineSetting} />
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useCallback, useEffect } from 'react'
|
|||||||
import { Thread } from '@janhq/core/'
|
import { Thread } from '@janhq/core/'
|
||||||
|
|
||||||
import { motion as m } from 'framer-motion'
|
import { motion as m } from 'framer-motion'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
import { GalleryHorizontalEndIcon, MoreVerticalIcon } from 'lucide-react'
|
import { GalleryHorizontalEndIcon, MoreVerticalIcon } from 'lucide-react'
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
@ -18,6 +18,7 @@ import CleanThreadModal from '../CleanThreadModal'
|
|||||||
import DeleteThreadModal from '../DeleteThreadModal'
|
import DeleteThreadModal from '../DeleteThreadModal'
|
||||||
|
|
||||||
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
import { assistantsAtom } from '@/helpers/atoms/Assistant.atom'
|
||||||
|
import { editMessageAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
getActiveThreadIdAtom,
|
getActiveThreadIdAtom,
|
||||||
threadDataReadyAtom,
|
threadDataReadyAtom,
|
||||||
@ -33,12 +34,14 @@ export default function ThreadList() {
|
|||||||
const assistants = useAtomValue(assistantsAtom)
|
const assistants = useAtomValue(assistantsAtom)
|
||||||
const threadDataReady = useAtomValue(threadDataReadyAtom)
|
const threadDataReady = useAtomValue(threadDataReadyAtom)
|
||||||
const { requestCreateNewThread } = useCreateNewThread()
|
const { requestCreateNewThread } = useCreateNewThread()
|
||||||
|
const setEditMessage = useSetAtom(editMessageAtom)
|
||||||
|
|
||||||
const onThreadClick = useCallback(
|
const onThreadClick = useCallback(
|
||||||
(thread: Thread) => {
|
(thread: Thread) => {
|
||||||
setActiveThread(thread)
|
setActiveThread(thread)
|
||||||
|
setEditMessage('')
|
||||||
},
|
},
|
||||||
[setActiveThread]
|
[setActiveThread, setEditMessage]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -39,6 +39,8 @@ import ModalTroubleShooting, {
|
|||||||
} from '@/containers/ModalTroubleShoot'
|
} from '@/containers/ModalTroubleShoot'
|
||||||
import ServerLogs from '@/containers/ServerLogs'
|
import ServerLogs from '@/containers/ServerLogs'
|
||||||
|
|
||||||
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { loadModelErrorAtom, useActiveModel } from '@/hooks/useActiveModel'
|
import { loadModelErrorAtom, useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useLogs } from '@/hooks/useLogs'
|
import { useLogs } from '@/hooks/useLogs'
|
||||||
|
|
||||||
@ -106,6 +108,45 @@ const LocalServerScreen = () => {
|
|||||||
handleChangePort(port)
|
handleChangePort(port)
|
||||||
}, [handleChangePort, port])
|
}, [handleChangePort, port])
|
||||||
|
|
||||||
|
const onStartServerClick = async () => {
|
||||||
|
if (selectedModel == null) return
|
||||||
|
try {
|
||||||
|
const isStarted = await window.core?.api?.startServer({
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
isCorsEnabled,
|
||||||
|
isVerboseEnabled,
|
||||||
|
})
|
||||||
|
await startModel(selectedModel.id)
|
||||||
|
if (isStarted) setServerEnabled(true)
|
||||||
|
if (firstTimeVisitAPIServer) {
|
||||||
|
localStorage.setItem(FIRST_TIME_VISIT_API_SERVER, 'false')
|
||||||
|
setFirstTimeVisitAPIServer(false)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
toaster({
|
||||||
|
title: `Failed to start server!`,
|
||||||
|
description: 'Please check Server Logs for more details.',
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStopServerClick = async () => {
|
||||||
|
window.core?.api?.stopServer()
|
||||||
|
setServerEnabled(false)
|
||||||
|
setLoadModelError(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToggleServer = async () => {
|
||||||
|
if (serverEnabled) {
|
||||||
|
await onStopServerClick()
|
||||||
|
} else {
|
||||||
|
await onStartServerClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full" data-testid="local-server-testid">
|
<div className="flex h-full w-full" data-testid="local-server-testid">
|
||||||
{/* Left SideBar */}
|
{/* Left SideBar */}
|
||||||
@ -122,26 +163,7 @@ const LocalServerScreen = () => {
|
|||||||
block
|
block
|
||||||
themes={serverEnabled ? 'danger' : 'primary'}
|
themes={serverEnabled ? 'danger' : 'primary'}
|
||||||
disabled={stateModel.loading || errorRangePort || !selectedModel}
|
disabled={stateModel.loading || errorRangePort || !selectedModel}
|
||||||
onClick={async () => {
|
onClick={onToggleServer}
|
||||||
if (serverEnabled) {
|
|
||||||
window.core?.api?.stopServer()
|
|
||||||
setServerEnabled(false)
|
|
||||||
setLoadModelError(undefined)
|
|
||||||
} else {
|
|
||||||
startModel(String(selectedModel?.id))
|
|
||||||
const isStarted = await window.core?.api?.startServer({
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
isCorsEnabled,
|
|
||||||
isVerboseEnabled,
|
|
||||||
})
|
|
||||||
if (isStarted) setServerEnabled(true)
|
|
||||||
if (firstTimeVisitAPIServer) {
|
|
||||||
localStorage.setItem(FIRST_TIME_VISIT_API_SERVER, 'false')
|
|
||||||
setFirstTimeVisitAPIServer(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{serverEnabled ? 'Stop' : 'Start'} Server
|
{serverEnabled ? 'Stop' : 'Start'} Server
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -83,7 +83,8 @@ const DataFolder = () => {
|
|||||||
await window.core?.api?.getAppConfigurations()
|
await window.core?.api?.getAppConfigurations()
|
||||||
const currentJanDataFolder = appConfiguration.data_folder
|
const currentJanDataFolder = appConfiguration.data_folder
|
||||||
appConfiguration.data_folder = destinationPath
|
appConfiguration.data_folder = destinationPath
|
||||||
await fs.syncFile(currentJanDataFolder, destinationPath)
|
const { err } = await fs.syncFile(currentJanDataFolder, destinationPath)
|
||||||
|
if (err) throw err
|
||||||
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
||||||
console.debug(
|
console.debug(
|
||||||
`File sync finished from ${currentJanDataFolder} to ${destinationPath}`
|
`File sync finished from ${currentJanDataFolder} to ${destinationPath}`
|
||||||
@ -94,7 +95,7 @@ const DataFolder = () => {
|
|||||||
}, 1200)
|
}, 1200)
|
||||||
await window.core?.api?.relaunch()
|
await window.core?.api?.relaunch()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error: ${e}`)
|
console.error(e)
|
||||||
setShowLoader(false)
|
setShowLoader(false)
|
||||||
setShowChangeFolderError(true)
|
setShowChangeFolderError(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import { snackbar, toaster } from '@/containers/Toast'
|
|||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useSettings } from '@/hooks/useSettings'
|
import { useSettings } from '@/hooks/useSettings'
|
||||||
|
|
||||||
import DataFolder from './DataFolder'
|
import DataFolder from './DataFolder'
|
||||||
@ -55,6 +56,10 @@ const Advanced = () => {
|
|||||||
setIgnoreSSL,
|
setIgnoreSSL,
|
||||||
proxy,
|
proxy,
|
||||||
setProxy,
|
setProxy,
|
||||||
|
proxyEnabled,
|
||||||
|
setProxyEnabled,
|
||||||
|
vulkanEnabled,
|
||||||
|
setVulkanEnabled,
|
||||||
} = useContext(FeatureToggleContext)
|
} = useContext(FeatureToggleContext)
|
||||||
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
||||||
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
||||||
@ -62,6 +67,7 @@ const Advanced = () => {
|
|||||||
const [gpusInUse, setGpusInUse] = useState<string[]>([])
|
const [gpusInUse, setGpusInUse] = useState<string[]>([])
|
||||||
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
||||||
useSettings()
|
useSettings()
|
||||||
|
const { stopModel } = useActiveModel()
|
||||||
|
|
||||||
const selectedGpu = gpuList
|
const selectedGpu = gpuList
|
||||||
.filter((x) => gpusInUse.includes(x.id))
|
.filter((x) => gpusInUse.includes(x.id))
|
||||||
@ -85,14 +91,15 @@ const Advanced = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setUseGpuIfPossible = async () => {
|
const setUseGpuIfPossible = async () => {
|
||||||
const settings = await readSettings()
|
const settings = await readSettings()
|
||||||
setGpuEnabled(settings.run_mode === 'gpu' && gpuList.length > 0)
|
setGpuEnabled(settings.run_mode === 'gpu' && settings.gpus?.length > 0)
|
||||||
setGpusInUse(settings.gpus_in_use || [])
|
setGpusInUse(settings.gpus_in_use || [])
|
||||||
|
setVulkanEnabled(settings.vulkan || false)
|
||||||
if (settings.gpus) {
|
if (settings.gpus) {
|
||||||
setGpuList(settings.gpus)
|
setGpuList(settings.gpus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setUseGpuIfPossible()
|
setUseGpuIfPossible()
|
||||||
}, [readSettings, gpuList])
|
}, [readSettings, setGpuList, setGpuEnabled, setGpusInUse, setVulkanEnabled])
|
||||||
|
|
||||||
const clearLogs = async () => {
|
const clearLogs = async () => {
|
||||||
if (await fs.existsSync(`file://logs`)) {
|
if (await fs.existsSync(`file://logs`)) {
|
||||||
@ -106,14 +113,21 @@ const Advanced = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleGPUChange = (gpuId: string) => {
|
const handleGPUChange = (gpuId: string) => {
|
||||||
// TODO detect current use GPU nvidia or AMD
|
|
||||||
let updatedGpusInUse = [...gpusInUse]
|
let updatedGpusInUse = [...gpusInUse]
|
||||||
if (updatedGpusInUse.includes(gpuId)) {
|
if (updatedGpusInUse.includes(gpuId)) {
|
||||||
updatedGpusInUse = updatedGpusInUse.filter((id) => id !== gpuId)
|
updatedGpusInUse = updatedGpusInUse.filter((id) => id !== gpuId)
|
||||||
if (gpuEnabled && updatedGpusInUse.length === 0) {
|
if (gpuEnabled && updatedGpusInUse.length === 0) {
|
||||||
|
// Vulkan support only allow 1 active device at a time
|
||||||
|
if (vulkanEnabled) {
|
||||||
|
updatedGpusInUse = []
|
||||||
|
}
|
||||||
updatedGpusInUse.push(gpuId)
|
updatedGpusInUse.push(gpuId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Vulkan support only allow 1 active device at a time
|
||||||
|
if (vulkanEnabled) {
|
||||||
|
updatedGpusInUse = []
|
||||||
|
}
|
||||||
updatedGpusInUse.push(gpuId)
|
updatedGpusInUse.push(gpuId)
|
||||||
}
|
}
|
||||||
setGpusInUse(updatedGpusInUse)
|
setGpusInUse(updatedGpusInUse)
|
||||||
@ -169,8 +183,8 @@ const Advanced = () => {
|
|||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="pr-8 leading-relaxed">
|
<p className="pr-8 leading-relaxed">
|
||||||
Enable to enhance model performance by utilizing your devices
|
Enable to enhance model performance by utilizing your GPU
|
||||||
GPU for acceleration. Read{' '}
|
devices for acceleration. Read{' '}
|
||||||
<span>
|
<span>
|
||||||
{' '}
|
{' '}
|
||||||
<span
|
<span
|
||||||
@ -198,7 +212,7 @@ const Advanced = () => {
|
|||||||
className="max-w-[240px]"
|
className="max-w-[240px]"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Disabling GPU Acceleration may result in reduced
|
Disabling NVIDIA GPU Acceleration may result in reduced
|
||||||
performance. It is recommended to keep this enabled for
|
performance. It is recommended to keep this enabled for
|
||||||
optimal user experience.
|
optimal user experience.
|
||||||
</span>
|
</span>
|
||||||
@ -210,7 +224,7 @@ const Advanced = () => {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Switch
|
<Switch
|
||||||
disabled={gpuList.length === 0}
|
disabled={gpuList.length === 0 || vulkanEnabled}
|
||||||
checked={gpuEnabled}
|
checked={gpuEnabled}
|
||||||
onCheckedChange={(e) => {
|
onCheckedChange={(e) => {
|
||||||
if (e === true) {
|
if (e === true) {
|
||||||
@ -232,6 +246,8 @@ const Advanced = () => {
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Stop any running model to apply the changes
|
||||||
|
if (e !== gpuEnabled) stopModel()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -253,12 +269,14 @@ const Advanced = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 w-full rounded-lg bg-secondary p-4">
|
<div className="mt-2 w-full rounded-lg bg-secondary p-4">
|
||||||
<label className="mb-1 inline-block font-medium">Choose GPU</label>
|
<label className="mb-1 inline-block font-medium">
|
||||||
|
Choose device(s)
|
||||||
|
</label>
|
||||||
<Select
|
<Select
|
||||||
disabled={gpuList.length === 0 || !gpuEnabled}
|
disabled={gpuList.length === 0 || !gpuEnabled}
|
||||||
value={selectedGpu.join()}
|
value={selectedGpu.join()}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[340px] bg-white">
|
<SelectTrigger className="w-[340px] dark:bg-gray-500 bg-white">
|
||||||
<SelectValue placeholder={gpuSelectionPlaceHolder}>
|
<SelectValue placeholder={gpuSelectionPlaceHolder}>
|
||||||
<span className="line-clamp-1 w-full pr-8">
|
<span className="line-clamp-1 w-full pr-8">
|
||||||
{selectedGpu.join()}
|
{selectedGpu.join()}
|
||||||
@ -268,12 +286,16 @@ const Advanced = () => {
|
|||||||
<SelectPortal>
|
<SelectPortal>
|
||||||
<SelectContent className="w-[400px] px-1 pb-2">
|
<SelectContent className="w-[400px] px-1 pb-2">
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>Nvidia</SelectLabel>
|
<SelectLabel>
|
||||||
|
{vulkanEnabled ? 'Vulkan Supported GPUs' : 'Nvidia'}
|
||||||
|
</SelectLabel>
|
||||||
<div className="px-4 pb-2">
|
<div className="px-4 pb-2">
|
||||||
<div className="rounded-lg bg-secondary p-3">
|
<div className="rounded-lg bg-secondary p-3">
|
||||||
{gpuList
|
{gpuList
|
||||||
.filter((gpu) =>
|
.filter((gpu) =>
|
||||||
gpu.name.toLowerCase().includes('nvidia')
|
vulkanEnabled
|
||||||
|
? gpu.name
|
||||||
|
: gpu.name?.toLowerCase().includes('nvidia')
|
||||||
)
|
)
|
||||||
.map((gpu) => (
|
.map((gpu) => (
|
||||||
<div
|
<div
|
||||||
@ -293,7 +315,9 @@ const Advanced = () => {
|
|||||||
htmlFor={`gpu-${gpu.id}`}
|
htmlFor={`gpu-${gpu.id}`}
|
||||||
>
|
>
|
||||||
<span>{gpu.name}</span>
|
<span>{gpu.name}</span>
|
||||||
|
{!vulkanEnabled && (
|
||||||
<span>{gpu.vram}MB VRAM</span>
|
<span>{gpu.vram}MB VRAM</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -322,12 +346,47 @@ const Advanced = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DataFolder />
|
{/* Vulkan for AMD GPU/ APU and Intel Arc GPU */}
|
||||||
{/* Proxy */}
|
{!isMac && experimentalFeature && (
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
|
Vulkan Support
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs leading-relaxed">
|
||||||
|
Enable Vulkan with AMD GPU/APU and Intel Arc GPU for better model
|
||||||
|
performance (reload needed).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
checked={vulkanEnabled}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
toaster({
|
||||||
|
title: 'Reload',
|
||||||
|
description:
|
||||||
|
'Vulkan settings updated. Reload now to apply the changes.',
|
||||||
|
})
|
||||||
|
stopModel()
|
||||||
|
saveSettings({ vulkan: e, gpusInUse: [] })
|
||||||
|
setVulkanEnabled(e)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DataFolder />
|
||||||
|
{/* Proxy */}
|
||||||
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
|
<div className="flex-shrink-0 space-y-1.5 w-full">
|
||||||
|
<div className="flex gap-x-2 justify-between w-full">
|
||||||
<h6 className="text-sm font-semibold capitalize">HTTPS Proxy</h6>
|
<h6 className="text-sm font-semibold capitalize">HTTPS Proxy</h6>
|
||||||
|
<Switch
|
||||||
|
checked={proxyEnabled}
|
||||||
|
onCheckedChange={(_) => setProxyEnabled(!proxyEnabled)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="leading-relaxed">
|
<p className="leading-relaxed">
|
||||||
Specify the HTTPS proxy or leave blank (proxy auto-configuration and
|
Specify the HTTPS proxy or leave blank (proxy auto-configuration and
|
||||||
@ -337,6 +396,7 @@ const Advanced = () => {
|
|||||||
placeholder={'http://<user>:<password>@<domain or IP>:<port>'}
|
placeholder={'http://<user>:<password>@<domain or IP>:<port>'}
|
||||||
value={partialProxy}
|
value={partialProxy}
|
||||||
onChange={onProxyChange}
|
onChange={onProxyChange}
|
||||||
|
className="w-2/3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,11 +18,14 @@ export const restAPI = {
|
|||||||
...acc,
|
...acc,
|
||||||
[proxy.route]: (...args: any) => {
|
[proxy.route]: (...args: any) => {
|
||||||
// For each route, define a function that sends a request to the API
|
// For each route, define a function that sends a request to the API
|
||||||
return fetch(`${API_BASE_URL}/v1/${proxy.path}/${proxy.route}`, {
|
return fetch(
|
||||||
|
`${window.core?.api.baseApiUrl}/v1/${proxy.path}/${proxy.route}`,
|
||||||
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(args),
|
body: JSON.stringify(args),
|
||||||
headers: { contentType: 'application/json' },
|
headers: { contentType: 'application/json' },
|
||||||
}).then(async (res) => {
|
}
|
||||||
|
).then(async (res) => {
|
||||||
try {
|
try {
|
||||||
if (proxy.path === 'fs') {
|
if (proxy.path === 'fs') {
|
||||||
const text = await res.text()
|
const text = await res.text()
|
||||||
@ -38,6 +41,6 @@ export const restAPI = {
|
|||||||
}, {}),
|
}, {}),
|
||||||
openExternalUrl,
|
openExternalUrl,
|
||||||
// Jan Server URL
|
// Jan Server URL
|
||||||
baseApiUrl: API_BASE_URL,
|
baseApiUrl: process.env.API_BASE_URL ?? API_BASE_URL,
|
||||||
pollingInterval: 5000,
|
pollingInterval: 5000,
|
||||||
}
|
}
|
||||||
|
|||||||