Merge branch 'release/v0.7.0' into feat/inference-llamacpp-extension

# Conflicts:
#	.devcontainer/buildAppImage.sh
#	.github/workflows/template-tauri-build-linux-x64.yml
#	Makefile
#	core/src/node/extension/index.test.ts
#	package.json
#	src-tauri/tauri.conf.json
#	web-app/package.json
This commit is contained in:
Louis 2025-07-10 15:36:41 +07:00
commit 6e0218c084
No known key found for this signature in database
GPG Key ID: 44FA9F4D33C37DE2
55 changed files with 2711 additions and 2123 deletions

View File

@ -1,21 +0,0 @@
#!/usr/bin/env bash
make clean
# To reproduce https://github.com/menloresearch/jan/pull/5463
TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri"
mkdir -p "$TAURI_TOOLKIT_PATH"
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
jq '.bundle.resources = ["resources/pre-install/**/*"] | .bundle.externalBin = ["resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
make build-tauri
cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun
cp -f ./src-tauri/binaries/*.so* ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/
APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
echo $APP_IMAGE
rm -f $APP_IMAGE
/opt/bin/appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE

View File

@ -14,7 +14,3 @@ sudo apt install -yqq libwebkit2gtk-4.1-dev \
librsvg2-dev \
xdg-utils \
libfuse2
sudo mkdir -p /opt/bin
sudo wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O /opt/bin/appimagetool
sudo chmod +x /opt/bin/appimagetool

View File

@ -2,6 +2,7 @@
name: 🚀 Feature Request
about: Suggest an idea for this project 😻!
title: 'idea: '
type: Idea
---
## Problem Statement

View File

@ -1,215 +0,0 @@
name: Electron Builder - Nightly / Manual
on:
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:
inputs:
public_provider:
type: choice
description: 'Public Provider'
options:
- none
- aws-s3
default: none
pull_request:
branches:
- release/**
jobs:
set-public-provider:
runs-on: ubuntu-latest
outputs:
public_provider: ${{ steps.set-public-provider.outputs.public_provider }}
ref: ${{ steps.set-public-provider.outputs.ref }}
steps:
- name: Set public provider
id: set-public-provider
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "::set-output name=public_provider::${{ github.event.inputs.public_provider }}"
echo "::set-output name=ref::${{ github.ref }}"
else
if [ "${{ github.event_name }}" == "schedule" ]; then
echo "::set-output name=public_provider::aws-s3"
echo "::set-output name=ref::refs/heads/dev"
elif [ "${{ github.event_name }}" == "push" ]; then
echo "::set-output name=public_provider::aws-s3"
echo "::set-output name=ref::${{ github.ref }}"
elif [ "${{ github.event_name }}" == "pull_request_review" ]; then
echo "::set-output name=public_provider::none"
echo "::set-output name=ref::${{ github.ref }}"
else
echo "::set-output name=public_provider::none"
echo "::set-output name=ref::${{ github.ref }}"
fi
fi
# 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-tauri-macos:
uses: ./.github/workflows/template-tauri-build-macos.yml
secrets: inherit
needs: [get-update-version, set-public-provider]
with:
ref: ${{ needs.set-public-provider.outputs.ref }}
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
new_version: ${{ needs.get-update-version.outputs.new_version }}
channel: nightly
cortex_api_port: "39261"
build-tauri-windows-x64:
uses: ./.github/workflows/template-tauri-build-windows-x64.yml
secrets: inherit
needs: [get-update-version, set-public-provider]
with:
ref: ${{ needs.set-public-provider.outputs.ref }}
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
new_version: ${{ needs.get-update-version.outputs.new_version }}
channel: nightly
cortex_api_port: "39261"
build-tauri-linux-x64:
uses: ./.github/workflows/template-tauri-build-linux-x64.yml
secrets: inherit
needs: [get-update-version, set-public-provider]
with:
ref: ${{ needs.set-public-provider.outputs.ref }}
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
new_version: ${{ needs.get-update-version.outputs.new_version }}
channel: nightly
cortex_api_port: "39261"
sync-temp-to-latest:
needs: [get-update-version, set-public-provider, build-tauri-windows-x64, build-tauri-linux-x64, build-tauri-macos]
runs-on: ubuntu-latest
steps:
- name: Getting the repo
uses: actions/checkout@v3
- name: Install jq
uses: dcarbone/install-jq-action@v2.0.1
- name: create latest.json file
run: |
VERSION=${{ needs.get-update-version.outputs.new_version }}
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
LINUX_SIGNATURE="${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_SIG }}"
LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_FILE_NAME }}"
WINDOWS_SIGNATURE="${{ needs.build-tauri-windows-x64.outputs.WIN_SIG }}"
WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-windows-x64.outputs.FILE_NAME }}"
DARWIN_SIGNATURE="${{ needs.build-tauri-macos.outputs.MAC_UNIVERSAL_SIG }}"
DARWIN_URL="https://delta.jan.ai/nightly/Jan-nightly_${{ needs.get-update-version.outputs.new_version }}.app.tar.gz"
jq --arg version "$VERSION" \
--arg pub_date "$PUB_DATE" \
--arg linux_signature "$LINUX_SIGNATURE" \
--arg linux_url "$LINUX_URL" \
--arg windows_signature "$WINDOWS_SIGNATURE" \
--arg windows_url "$WINDOWS_URL" \
--arg darwin_arm_signature "$DARWIN_SIGNATURE" \
--arg darwin_arm_url "$DARWIN_URL" \
--arg darwin_amd_signature "$DARWIN_SIGNATURE" \
--arg darwin_amd_url "$DARWIN_URL" \
'.version = $version
| .pub_date = $pub_date
| .platforms["linux-x86_64"].signature = $linux_signature
| .platforms["linux-x86_64"].url = $linux_url
| .platforms["windows-x86_64"].signature = $windows_signature
| .platforms["windows-x86_64"].url = $windows_url
| .platforms["darwin-aarch64"].signature = $darwin_arm_signature
| .platforms["darwin-aarch64"].url = $darwin_arm_url
| .platforms["darwin-x86_64"].signature = $darwin_amd_signature
| .platforms["darwin-x86_64"].url = $darwin_amd_url' \
src-tauri/latest.json.template > latest.json
cat latest.json
- name: Sync temp to latest
if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }}
run: |
aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/latest.json
aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/nightly/
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
AWS_EC2_METADATA_DISABLED: "true"
noti-discord-nightly-and-update-url-readme:
needs: [
build-tauri-macos,
build-tauri-windows-x64,
build-tauri-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest
]
secrets: inherit
if: github.event_name == 'schedule'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Nightly
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-pre-release-and-update-url-readme:
needs: [
build-tauri-macos,
build-tauri-windows-x64,
build-tauri-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest
]
secrets: inherit
if: github.event_name == 'push'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Pre-release
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
noti-discord-manual-and-update-url-readme:
needs: [
build-tauri-macos,
build-tauri-windows-x64,
build-tauri-linux-x64,
get-update-version,
set-public-provider,
sync-temp-to-latest
]
secrets: inherit
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
with:
ref: refs/heads/dev
build_reason: Manual
push_to_branch: dev
new_version: ${{ needs.get-update-version.outputs.new_version }}
# comment-pr-build-url:
# needs: [
# build-tauri-macos,
# build-tauri-windows-x64,
# build-tauri-linux-x64,
# get-update-version,
# set-public-provider,
# sync-temp-to-latest
# ]
# runs-on: ubuntu-latest
# if: github.event_name == 'pull_request_review'
# steps:
# - name: Set up GitHub CLI
# run: |
# curl -sSL https://github.com/cli/cli/releases/download/v2.33.0/gh_2.33.0_linux_amd64.tar.gz | tar xz
# sudo cp gh_2.33.0_linux_amd64/bin/gh /usr/local/bin/
# - name: Comment build URL on PR
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# run: |
# PR_URL=${{ github.event.pull_request.html_url }}
# RUN_ID=${{ github.run_id }}
# COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})."
# gh pr comment $PR_URL --body "$COMMENT"

View File

@ -1,131 +0,0 @@
name: Electron Builder - Tag
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
create-draft-release:
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
version: ${{ steps.get_version.outputs.version }}
permissions:
contents: write
steps:
- name: Extract tag name without v prefix
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
env:
GITHUB_REF: ${{ github.ref }}
- name: Create Draft Release
id: create_release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
token: ${{ secrets.GITHUB_TOKEN }}
name: "${{ env.VERSION }}"
draft: true
prerelease: false
build-electron-macos:
uses: ./.github/workflows/template-electron-build-macos.yml
secrets: inherit
needs: [get-update-version]
with:
ref: ${{ github.ref }}
public_provider: github
beta: false
nightly: false
new_version: ${{ needs.get-update-version.outputs.new_version }}
build-electron-windows-x64:
uses: ./.github/workflows/template-electron-build-windows-x64.yml
secrets: inherit
needs: [get-update-version]
with:
ref: ${{ github.ref }}
public_provider: github
beta: false
nightly: false
new_version: ${{ needs.get-update-version.outputs.new_version }}
build-electron-linux-x64:
uses: ./.github/workflows/template-electron-build-linux-x64.yml
secrets: inherit
needs: [get-update-version]
with:
ref: ${{ github.ref }}
public_provider: github
beta: false
nightly: false
new_version: ${{ needs.get-update-version.outputs.new_version }}
# build-tauri-macos:
# uses: ./.github/workflows/template-tauri-build-macos.yml
# secrets: inherit
# needs: [get-update-version, create-draft-release]
# with:
# ref: ${{ github.ref }}
# public_provider: github
# channel: stable
# new_version: ${{ needs.get-update-version.outputs.new_version }}
# upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
# build-tauri-windows-x64:
# uses: ./.github/workflows/template-tauri-build-windows-x64.yml
# secrets: inherit
# needs: [get-update-version, create-draft-release]
# with:
# ref: ${{ github.ref }}
# public_provider: github
# channel: stable
# new_version: ${{ needs.get-update-version.outputs.new_version }}
# upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
# build-tauri-linux-x64:
# uses: ./.github/workflows/template-tauri-build-linux-x64.yml
# secrets: inherit
# needs: [get-update-version, create-draft-release]
# with:
# ref: ${{ github.ref }}
# public_provider: github
# channel: stable
# new_version: ${{ needs.get-update-version.outputs.new_version }}
# upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
update_release_draft:
needs: [
build-electron-windows-x64,
build-electron-linux-x64,
build-electron-macos,
build-tauri-windows-x64,
build-tauri-linux-x64,
build-tauri-macos
]
permissions:
# write permission is required to create a github release
contents: write
# write permission is required for autolabeler
# otherwise, read permission is required at least
pull-requests: write
runs-on: ubuntu-latest
steps:
# (Optional) GitHub Enterprise requires GHE_HOST variable set
#- name: Set GHE_HOST
# run: |
# echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV
# Drafts your next Release notes as Pull Requests are merged into "master"
- uses: release-drafter/release-drafter@v5
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
# with:
# config-name: my-config.yml
# disable-autolabeler: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,435 +0,0 @@
name: Test - Linter & Playwright
on:
workflow_dispatch:
push:
branches:
- main
- dev
paths:
- 'electron/**'
- .github/workflows/jan-electron-linter-and-test.yml
- 'web/**'
- 'joi/**'
- 'package.json'
- 'node_modules/**'
- 'yarn.lock'
- 'core/**'
- 'extensions/**'
- '!README.md'
- 'Makefile'
pull_request:
branches:
- main
- dev
- release/**
paths:
- 'electron/**'
- .github/workflows/jan-electron-linter-and-test.yml
- 'web/**'
- 'joi/**'
- 'package.json'
- 'node_modules/**'
- 'yarn.lock'
- 'Makefile'
- 'extensions/**'
- 'core/**'
- 'src-tauri/**'
- 'web-app/**'
- '!README.md'
jobs:
base_branch_cov:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.base_ref }}
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
run: |
make config-yarn
yarn
yarn build:core
- name: Run test coverage
run: yarn test:coverage
- name: Upload code coverage for ref branch
uses: actions/upload-artifact@v4
with:
name: ref-lcov.info
path: ./coverage/lcov.info
test-on-macos:
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: macos-latest
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Set IS_TEST environment variable
run: |
echo "IS_TEST=true" >> $GITHUB_ENV
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Get Commit Message for PR
if: github.event_name == 'pull_request'
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV
- name: Get Commit Message for push event
if: github.event_name == 'push'
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV
# - name: 'Config report portal'
# run: |
# make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
- name: Linter and test
run: |
make test
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
test-on-macos-pr-target:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
runs-on: macos-latest
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Linter and test
run: |
make test
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
test-on-windows:
if: github.event_name == 'push'
strategy:
fail-fast: false
matrix:
antivirus-tools: ['mcafee', 'default-windows-security', 'bit-defender']
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
# Clean cache, continue on error
- name: 'Cleanup cache'
shell: powershell
continue-on-error: true
run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean
- name: Get Commit Message for push event
if: github.event_name == 'push'
shell: bash
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
# - name: 'Config report portal'
# shell: bash
# run: |
# make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
- name: Linter and test
shell: powershell
run: |
make test
test-on-windows-pr:
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'workflow_dispatch'
runs-on: windows-latest
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/cache@v4 # v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/yarn.lock') }}
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
# Clean cache, continue on error
- name: 'Cleanup cache'
shell: powershell
continue-on-error: true
run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean
- name: Get Commit Message for PR
if: github.event_name == 'pull_request'
shell: bash
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
# - name: 'Config report portal'
# shell: bash
# run: |
# make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
- name: Install Prerequisites
shell: 'powershell'
# https://github.com/actions/runner-images/issues/9538
# https://github.com/microsoft/playwright/pull/30009/files
# https://github.com/tauri-apps/wry/issues/1268
# Evergreen Bootstrapper
# The Bootstrapper is a tiny installer that downloads
# the Evergreen Runtime matching device architecture
# and installs it locally.
# https://developer.microsoft.com/en-us/microsoft-edge/webview2/consumer/?form=MA13LH
run: |
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
Start-Process -FilePath setup.exe -Verb RunAs -Wait
- name: Linter and test
shell: powershell
run: |
make test
test-on-windows-pr-target:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
runs-on: windows-latest
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v1
with:
node-version: 20
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
# Clean cache, continue on error
- name: 'Cleanup cache'
shell: powershell
continue-on-error: true
run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean
- name: Linter and test
shell: powershell
run: |
make test
test-on-ubuntu:
runs-on: ubuntu-latest
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install Tauri dependencies
run: |
sudo apt update
sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Get Commit Message for PR
if: github.event_name == 'pull_request'
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
- name: Get Commit Message for push event
if: github.event_name == 'push'
run: |
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
# - name: 'Config report portal'
# shell: bash
# run: |
# make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
- name: Linter and test
run: |
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
echo -e "Display ID: $DISPLAY"
make test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: electron/playwright-report/
retention-days: 2
# coverage-check:
# runs-on: ubuntu-latest
# needs: base_branch_cov
# continue-on-error: true
# if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
# steps:
# - name: Getting the repo
# uses: actions/checkout@v3
# with:
# fetch-depth: 0
# - name: Installing node
# uses: actions/setup-node@v3
# with:
# node-version: 20
# - name: Install yarn
# run: npm install -g yarn
# - name: 'Cleanup cache'
# continue-on-error: true
# run: |
# rm -rf ~/jan
# make clean
# - name: Download code coverage report from base branch
# uses: actions/download-artifact@v4
# with:
# name: ref-lcov.info
# - name: Linter and test coverage
# run: |
# export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
# echo -e "Display ID: $DISPLAY"
# make lint
# yarn build:test
# yarn test:coverage
# - name: Generate Code Coverage report
# id: code-coverage
# uses: barecheck/code-coverage-action@v1
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
# lcov-file: './coverage/lcov.info'
# base-lcov-file: './lcov.info'
# send-summary-comment: true
# show-annotations: 'warning'
test-on-ubuntu-pr-target:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install Tauri dependencies
run: |
sudo apt update
sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Linter and test
run: |
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
echo -e "Display ID: $DISPLAY"
make test

View File

@ -0,0 +1,269 @@
name: Test - Linter & Playwright
on:
workflow_dispatch:
push:
branches:
- main
- dev
paths:
- .github/workflows/jan-linter-and-test.yml
- 'web/**'
- 'joi/**'
- 'package.json'
- 'node_modules/**'
- 'yarn.lock'
- 'core/**'
- 'extensions/**'
- '!README.md'
- 'Makefile'
pull_request:
branches:
- main
- dev
- release/**
paths:
- .github/workflows/jan-linter-and-test.yml
- 'web/**'
- 'joi/**'
- 'package.json'
- 'node_modules/**'
- 'yarn.lock'
- 'Makefile'
- 'extensions/**'
- 'core/**'
- 'src-tauri/**'
- 'web-app/**'
- '!README.md'
jobs:
base_branch_cov:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.base_ref }}
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Install dependencies
run: |
make lint
- name: Run test coverage
run: |
yarn test:coverage
- name: Upload code coverage for ref branch
uses: actions/upload-artifact@v4
with:
name: ref-lcov.info
path: coverage/merged/lcov.info
test-on-macos:
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'macos-latest' || 'macos-selfhosted-12-arm64' }}
if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Linter and test
run: |
make test
env:
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
test-on-windows:
if: github.event_name == 'push'
strategy:
fail-fast: false
matrix:
antivirus-tools: ['mcafee', 'default-windows-security', 'bit-defender']
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
# Clean cache, continue on error
- name: 'Cleanup cache'
shell: powershell
continue-on-error: true
run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean
- name: Linter and test
shell: powershell
run: |
make test
test-on-windows-pr:
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'windows-latest' || 'WINDOWS-11' }}
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: install dependencies
run: |
choco install --yes --no-progress make
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
- name: 'Cleanup cache'
shell: powershell
continue-on-error: true
run: |
$path = "$Env:APPDATA\jan"
if (Test-Path $path) {
Remove-Item "\\?\$path" -Recurse -Force
} else {
Write-Output "Folder does not exist."
}
make clean
- name: Install WebView2 Runtime (Bootstrapper)
shell: powershell
run: |
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
Start-Process -FilePath setup.exe -Verb RunAs -Wait
- name: Linter and test
shell: powershell
run: |
make test
env:
NODE_OPTIONS: '--max-old-space-size=2048'
test-on-ubuntu:
runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'ubuntu-latest' || 'ubuntu-latest' }}
if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install Tauri dependencies
run: |
sudo apt update
sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
- name: Install tauri-driver dependencies
run: |
cargo install tauri-driver --locked
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Linter and test
run: |
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
echo -e "Display ID: $DISPLAY"
make test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: electron/playwright-report/
retention-days: 2
coverage-check:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
needs: base_branch_cov
continue-on-error: true
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Installing node
uses: actions/setup-node@v3
with:
node-version: 20
- name: 'Cleanup cache'
continue-on-error: true
run: |
rm -rf ~/jan
make clean
- name: Install dependencies
run: |
make lint
- name: Run test coverage
run: |
yarn test:coverage
- name: Download code coverage report from base branch
uses: actions/download-artifact@v4
with:
name: ref-lcov.info
- name: Generate Code Coverage report
id: code-coverage
uses: barecheck/code-coverage-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
lcov-file: './coverage/merged/lcov.info'
base-lcov-file: './lcov.info'
send-summary-comment: true
show-annotations: 'warning'

View File

@ -1,186 +0,0 @@
name: build-linux-x64
on:
workflow_call:
inputs:
ref:
required: true
type: string
default: 'refs/heads/main'
public_provider:
required: true
type: string
default: none
description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
new_version:
required: true
type: string
default: ''
aws_s3_prefix:
required: false
type: string
default: '/latest/'
beta:
required: false
type: boolean
default: false
nightly:
required: false
type: boolean
default: false
cortex_api_port:
required: false
type: string
default: null
secrets:
DELTA_AWS_S3_BUCKET_NAME:
required: false
DELTA_AWS_ACCESS_KEY_ID:
required: false
DELTA_AWS_SECRET_ACCESS_KEY:
required: false
jobs:
build-linux-x64:
if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.ref }}
- name: Replace Icons for Beta Build
if: inputs.beta == true && inputs.nightly != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
- name: Replace Icons for Nightly Build
if: inputs.nightly == true && inputs.beta != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
- name: Installing node
uses: actions/setup-node@v1
with:
node-version: 20
- name: Install jq
uses: dcarbone/install-jq-action@v2.0.1
- name: Update app version base public_provider
if: inputs.public_provider != 'github'
run: |
echo "Version: ${{ inputs.new_version }}"
# Update the version in electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json nightly
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json nightly
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
- name: Change App Name for beta version
if: inputs.beta == true
shell: bash
run: |
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json beta
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json beta
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
cat ./package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
- name: Update app version base on tag
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
run: |
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
env:
VERSION_TAG: ${{ inputs.new_version }}
- name: Build and publish app to aws s3 r2 or github artifactory
if: inputs.public_provider != 'github'
run: |
# check public_provider is true or not
echo "public_provider is ${{ inputs.public_provider }}"
if [ "${{ inputs.public_provider }}" == "none" ]; then
make build
else
make build-and-publish
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
- name: Build and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Build and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Upload Artifact .deb file
if: inputs.public_provider != 'github'
uses: actions/upload-artifact@v4
with:
name: jan-electron-linux-amd64-${{ inputs.new_version }}-deb
path: ./electron/dist/*.deb
- name: Upload Artifact .AppImage file
if: inputs.public_provider != 'github'
uses: actions/upload-artifact@v4
with:
name: jan-electron-linux-amd64-${{ inputs.new_version }}-AppImage
path: ./electron/dist/*.AppImage

View File

@ -1,233 +0,0 @@
name: build-macos
on:
workflow_call:
inputs:
ref:
required: true
type: string
default: 'refs/heads/main'
public_provider:
required: true
type: string
default: none
description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
new_version:
required: true
type: string
default: ''
aws_s3_prefix:
required: false
type: string
default: '/latest/'
beta:
required: false
type: boolean
default: false
nightly:
required: false
type: boolean
default: false
cortex_api_port:
required: false
type: string
default: null
secrets:
DELTA_AWS_S3_BUCKET_NAME:
required: false
DELTA_AWS_ACCESS_KEY_ID:
required: false
DELTA_AWS_SECRET_ACCESS_KEY:
required: false
CODE_SIGN_P12_BASE64:
required: false
CODE_SIGN_P12_PASSWORD:
required: false
APPLE_ID:
required: false
APPLE_APP_SPECIFIC_PASSWORD:
required: false
DEVELOPER_ID:
required: false
jobs:
build-macos:
if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
runs-on: macos-latest
permissions:
contents: write
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.ref }}
- name: Replace Icons for Beta Build
if: inputs.beta == true && inputs.nightly != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
- name: Replace Icons for Nightly Build
if: inputs.nightly == true && inputs.beta != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
- name: Installing node
uses: actions/setup-node@v1
with:
node-version: 20
- name: Install jq
uses: dcarbone/install-jq-action@v2.0.1
- name: Update app version based on latest release tag with build number
if: inputs.public_provider != 'github'
run: |
echo "Version: ${{ inputs.new_version }}"
# Update the version in electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
# cat electron/package.json
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json nightly
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json nightly
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
- name: Change App Name for beta version
if: inputs.beta == true
shell: bash
run: |
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json beta
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json beta
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
cat ./package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
- name: Update app version base on tag
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
run: |
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
env:
VERSION_TAG: ${{ inputs.new_version }}
- name: Get Cer for code signing
run: base64 -d <<< "$CODE_SIGN_P12_BASE64" > /tmp/codesign.p12
shell: bash
env:
CODE_SIGN_P12_BASE64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
- uses: apple-actions/import-codesign-certs@v2
continue-on-error: true
with:
p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
- name: Build and publish app to aws s3 r2 or github artifactory
if: inputs.public_provider != 'github'
run: |
# check public_provider is true or not
echo "public_provider is ${{ inputs.public_provider }}"
if [ "${{ inputs.public_provider }}" == "none" ]; then
make build
else
make build-and-publish
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: '/tmp/codesign.p12'
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: 'true'
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APP_PATH: '.'
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
- name: Build and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: '/tmp/codesign.p12'
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: 'true'
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APP_PATH: '.'
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Build and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: '/tmp/codesign.p12'
CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
CSC_IDENTITY_AUTO_DISCOVERY: 'true'
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APP_PATH: '.'
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Upload Artifact
if: inputs.public_provider != 'github'
uses: actions/upload-artifact@v4
with:
name: jan-electron-mac-universal-${{ inputs.new_version }}
path: ./electron/dist/*.dmg

View File

@ -1,230 +0,0 @@
name: build-windows-x64
on:
workflow_call:
inputs:
ref:
required: true
type: string
default: 'refs/heads/main'
public_provider:
required: true
type: string
default: none
description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
new_version:
required: true
type: string
default: ''
aws_s3_prefix:
required: false
type: string
default: '/latest/'
beta:
required: false
type: boolean
default: false
nightly:
required: false
type: boolean
default: false
cortex_api_port:
required: false
type: string
default: null
secrets:
DELTA_AWS_S3_BUCKET_NAME:
required: false
DELTA_AWS_ACCESS_KEY_ID:
required: false
DELTA_AWS_SECRET_ACCESS_KEY:
required: false
AZURE_KEY_VAULT_URI:
required: false
AZURE_CLIENT_ID:
required: false
AZURE_TENANT_ID:
required: false
AZURE_CLIENT_SECRET:
required: false
AZURE_CERT_NAME:
required: false
jobs:
build-windows-x64:
if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Getting the repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.ref }}
- name: Replace Icons for Beta Build
if: inputs.beta == true && inputs.nightly != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
- name: Replace Icons for Nightly Build
if: inputs.nightly == true && inputs.beta != true
shell: bash
run: |
rm -rf electron/icons/*
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
- name: Installing node
uses: actions/setup-node@v1
with:
node-version: 20
- name: Install jq
uses: dcarbone/install-jq-action@v2.0.1
- name: Update app version base on tag
if: inputs.public_provider != 'github'
id: version_update
shell: bash
run: |
echo "Version: ${{ inputs.new_version }}"
# Update the version in electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json nightly
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json nightly
chmod +x .github/scripts/rename-uninstaller.sh
.github/scripts/rename-uninstaller.sh nightly
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
cat ./package.json
echo "------------------------"
- name: Change App Name for beta version
if: inputs.beta == true
shell: bash
run: |
chmod +x .github/scripts/rename-app.sh
.github/scripts/rename-app.sh ./electron/package.json beta
chmod +x .github/scripts/rename-workspace.sh
.github/scripts/rename-workspace.sh ./package.json beta
chmod +x .github/scripts/rename-uninstaller.sh
.github/scripts/rename-uninstaller.sh beta
echo "------------------------"
cat ./electron/package.json
echo "------------------------"
cat ./package.json
echo "------------------------"
cat ./electron/scripts/uninstaller.nsh
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
cat electron/package.json
- name: Update app version base on tag
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
shell: bash
run: |
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json
jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json
env:
VERSION_TAG: ${{ inputs.new_version }}
- name: Install AzureSignTool
run: |
dotnet tool install --global AzureSignTool
- name: Build and publish app to aws s3 r2 or github artifactory
shell: bash
if: inputs.public_provider != 'github'
run: |
# check public_provider is true or not
echo "public_provider is ${{ inputs.public_provider }}"
if [ "${{ inputs.public_provider }}" == "none" ]; then
make build
else
make build-and-publish
fi
env:
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_CERT_NAME: homebrewltd
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
- name: Build app and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_CERT_NAME: homebrewltd
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Build app and publish app to github
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
run: |
make build-and-publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
AWS_EC2_METADATA_DISABLED: 'true'
AWS_MAX_ATTEMPTS: '5'
AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
# AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
AZURE_CERT_NAME: homebrewltd
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- name: Upload Artifact
if: inputs.public_provider != 'github'
uses: actions/upload-artifact@v4
with:
name: jan-electron-win-x64-${{ inputs.new_version }}
path: ./electron/dist/*.exe

View File

@ -104,16 +104,15 @@ jobs:
run: |
echo "Version: ${{ inputs.new_version }}"
# Update tauri.conf.json
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.resources = ["resources/pre-install/**/*"] | .bundle.externalBin = [ "resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
if [ "${{ inputs.channel }}" != "stable" ]; then
jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun",
"usr/lib/Jan-${{ inputs.channel }}/resources/lib/libvulkan.so": "resources/lib/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
else
jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun",
"usr/lib/Jan/resources/lib/libvulkan.so": "resources/lib/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
"usr/lib/Jan-${{ inputs.channel }}/binaries": "binaries/deps",
"usr/lib/Jan-${{ inputs.channel }}/binaries/engines": "binaries/engines",
"usr/lib/Jan-${{ inputs.channel }}/binaries/libvulkan.so": "binaries/libvulkan.so"}' ./src-tauri/tauri.linux.conf.json > /tmp/tauri.linux.conf.json
mv /tmp/tauri.linux.conf.json ./src-tauri/tauri.linux.conf.json
fi
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json
mv /tmp/package.json web-app/package.json
@ -146,40 +145,16 @@ jobs:
fi
- name: Build app
run: |
# Pin linuxdeploy version to prevent @tauri-apps/cli-linux-x64-gnu from pulling in an outdated version
TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri"
mkdir -p "$TAURI_TOOLKIT_PATH"
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
make build-tauri
# Copy engines and bun to appimage
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool
chmod +x ./appimagetool
if [ "${{ inputs.channel }}" != "stable" ]; then
ls ./src-tauri/target/release/bundle/appimage/
cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/bin/bun
APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep .AppImage | head -1)
echo $APP_IMAGE
rm -f $APP_IMAGE
./appimagetool ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir $APP_IMAGE
yarn tauri signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"$APP_IMAGE"
else
cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun
APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
echo $APP_IMAGE
rm -f $APP_IMAGE
./appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE
yarn tauri signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"$APP_IMAGE"
fi
APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
yarn tauri signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"$APP_IMAGE"
env:
RELEASE_CHANNEL: "${{ inputs.channel }}"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}

3
.gitignore vendored
View File

@ -49,4 +49,5 @@ src-tauri/resources/bin
# Helper tools
.opencode
OpenCode.md
archive/
archive/
.cache/

View File

@ -22,6 +22,8 @@ config-yarn:
install-and-build: config-yarn
ifeq ($(OS),Windows_NT)
echo "skip"
else ifeq ($(shell uname -s),Linux)
chmod +x src-tauri/build-utils/*
endif
yarn install
yarn build:core
@ -66,22 +68,23 @@ ifeq ($(OS),Windows_NT)
-powershell -Command "Remove-Item -Recurse -Force ./src-tauri/target"
-powershell -Command "if (Test-Path \"$($env:USERPROFILE)\jan\extensions\") { Remove-Item -Path \"$($env:USERPROFILE)\jan\extensions\" -Recurse -Force }"
else ifeq ($(shell uname -s),Linux)
find . -name "node_modules" -type d -prune -exec rm -rfv '{}' +
find . -name ".next" -type d -exec rm -rfv '{}' +
find . -name "dist" -type d -exec rm -rfv '{}' +
find . -name "build" -type d -exec rm -rfv '{}' +
find . -name "out" -type d -exec rm -rfv '{}' +
find . -name ".turbo" -type d -exec rm -rfv '{}' +
find . -name ".yarn" -type d -exec rm -rfv '{}' +
find . -name "packake-lock.json" -type f -exec rm -rfv '{}' +
find . -name "package-lock.json" -type f -exec rm -rfv '{}' +
rm -rfv ./pre-install/*.tgz
rm -rfv ./extensions/*/*.tgz
rm -rfv ./electron/pre-install/*.tgz
rm -rfv ./src-tauri/resources
rm -rfv ./src-tauri/target
rm -rfv ~/.local/share/Jan/data/extensions
rm -rfv ~/.cache/jan*
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
find . -name ".next" -type d -exec rm -rf '{}' +
find . -name "dist" -type d -exec rm -rf '{}' +
find . -name "build" -type d -exec rm -rf '{}' +
find . -name "out" -type d -exec rm -rf '{}' +
find . -name ".turbo" -type d -exec rm -rf '{}' +
find . -name ".yarn" -type d -exec rm -rf '{}' +
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
find . -name "package-lock.json" -type f -exec rm -rf '{}' +
rm -rf ./pre-install/*.tgz
rm -rf ./extensions/*/*.tgz
rm -rf ./electron/pre-install/*.tgz
rm -rf ./src-tauri/resources
rm -rf ./src-tauri/target
rm -rf "~/jan/extensions"
rm -rf "~/.cache/jan*"
rm -rf "./.cache"
else
find . -name "node_modules" -type d -prune -exec rm -rfv '{}' +
find . -name ".next" -type d -exec rm -rfv '{}' +

View File

@ -7,8 +7,8 @@ module.exports = {
},
runner: './testRunner.js',
transform: {
"^.+\\.tsx?$": [
"ts-jest",
'^.+\\.tsx?$': [
'ts-jest',
{
diagnostics: false,
},

View File

@ -29,7 +29,7 @@
"eslint-plugin-jest": "^27.9.0",
"jest": "^30.0.3",
"jest-junit": "^16.0.0",
"jest-runner": "^29.7.0",
"jest-runner": "^30.0.3",
"pacote": "^21.0.0",
"request": "^2.88.2",
"request-progress": "^3.0.0",

View File

@ -43,41 +43,41 @@ describe('EngineManager', () => {
})
describe('cortex engine migration', () => {
test('should map nitro to cortex engine', () => {
test.skip('should map nitro to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
// @ts-ignore
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.nitro)
expect(retrievedEngine).toBe(cortexEngine)
})
test('should map cortex_llamacpp to cortex engine', () => {
test.skip('should map cortex_llamacpp to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
// @ts-ignore
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_llamacpp)
expect(retrievedEngine).toBe(cortexEngine)
})
test('should map cortex_onnx to cortex engine', () => {
test.skip('should map cortex_onnx to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
// @ts-ignore
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_onnx)
expect(retrievedEngine).toBe(cortexEngine)
})
test('should map cortex_tensorrtllm to cortex engine', () => {
test.skip('should map cortex_tensorrtllm to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
// @ts-ignore
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_tensorrtllm)
expect(retrievedEngine).toBe(cortexEngine)
@ -89,19 +89,19 @@ describe('EngineManager', () => {
const mockEngineManager = new EngineManager()
// @ts-ignore
window.core = { engineManager: mockEngineManager }
const instance = EngineManager.instance()
expect(instance).toBe(mockEngineManager)
// Clean up
// @ts-ignore
delete window.core
})
test('should create a new instance if window.core.engineManager is not available', () => {
// @ts-ignore
delete window.core
const instance = EngineManager.instance()
expect(instance).toBeInstanceOf(EngineManager)
})

View File

@ -23,7 +23,7 @@ describe('fs module', () => {
it('should call writeFileSync with correct arguments', () => {
const args = ['path/to/file', 'data']
fs.writeFileSync(...args)
expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith(...args)
expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith({ args })
})
it('should call writeBlob with correct arguments', async () => {
@ -90,8 +90,7 @@ describe('fs module', () => {
it('should call fileStat with correct arguments', async () => {
const path = 'path/to/file'
const outsideJanDataFolder = true
await fs.fileStat(path, outsideJanDataFolder)
expect(globalThis.core.api.fileStat).toHaveBeenCalledWith(path, outsideJanDataFolder)
await fs.fileStat(path)
expect(globalThis.core.api.fileStat).toHaveBeenCalledWith({ args: path })
})
})

View File

@ -1,30 +1,29 @@
import { Model, ModelSettingParams, ModelRuntimeParams } from '../model'
import { InferenceEngine } from '../engine'
test.skip('testValidModelCreation', () => {
const model: Model = {
object: 'model',
version: '1.0',
format: 'format1',
sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
id: 'model1',
name: 'Test Model',
created: Date.now(),
description: 'A cool model from Huggingface',
settings: { ctx_len: 100, ngl: 50, embedding: true },
parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
engine: InferenceEngine.anthropic,
}
import { Model, ModelSettingParams, ModelRuntimeParams, InferenceEngine } from '../model'
test('testValidModelCreation', () => {
const model: Model = {
object: 'model',
version: '1.0',
format: 'format1',
sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
id: 'model1',
name: 'Test Model',
created: Date.now(),
description: 'A cool model from Huggingface',
settings: { ctx_len: 100, ngl: 50, embedding: true },
parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
engine: InferenceEngine.anthropic
};
expect(model).toBeDefined();
expect(model.object).toBe('model');
expect(model.version).toBe('1.0');
expect(model.sources).toHaveLength(1);
expect(model.sources[0].filename).toBe('model.bin');
expect(model.settings).toBeDefined();
expect(model.parameters).toBeDefined();
expect(model.metadata).toBeDefined();
expect(model.engine).toBe(InferenceEngine.anthropic);
});
expect(model).toBeDefined()
expect(model.object).toBe('model')
expect(model.version).toBe('1.0')
expect(model.sources).toHaveLength(1)
expect(model.sources[0].filename).toBe('model.bin')
expect(model.settings).toBeDefined()
expect(model.parameters).toBeDefined()
expect(model.metadata).toBeDefined()
expect(model.engine).toBe(InferenceEngine.anthropic)
})

View File

@ -1,19 +1,9 @@
import * as SettingComponent from './settingComponent'
import { createSettingComponent } from './settingComponent';
it('should not throw any errors when importing settingComponent', () => {
expect(() => require('./settingComponent')).not.toThrow()
})
it('should throw an error when creating a setting component with invalid controller type', () => {
const props: SettingComponentProps = {
key: 'invalidControllerKey',
title: 'Invalid Controller Title',
description: 'Invalid Controller Description',
controllerType: 'invalid' as any,
controllerProps: {
placeholder: 'Enter text',
value: 'Initial Value',
type: 'text',
textAlign: 'left',
inputActions: ['unobscure'],
},
};
expect(() => createSettingComponent(props)).toThrowError();
});
it('should export SettingComponentProps type', () => {
expect(SettingComponent).toBeDefined()
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -94,7 +94,7 @@ const Hero = () => {
</p>
<div className="w-4/5 mx-auto mt-10 relative">
<ThemeImage
className="absolute object-cover w-full object-center mx-auto h-full top-0 left-0 scale-150"
className="absolute object-cover w-full object-center mx-auto h-full top-0 left-0 scale-125"
source={{
light: '/assets/images/homepage/glow.png',
dark: '/assets/images/homepage/glow.png',

View File

@ -587,6 +587,22 @@ __metadata:
languageName: node
linkType: hard
"@isaacs/balanced-match@npm:^4.0.1":
version: 4.0.1
resolution: "@isaacs/balanced-match@npm:4.0.1"
checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420
languageName: node
linkType: hard
"@isaacs/brace-expansion@npm:^5.0.0":
version: 5.0.0
resolution: "@isaacs/brace-expansion@npm:5.0.0"
dependencies:
"@isaacs/balanced-match": "npm:^4.0.1"
checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977
languageName: node
linkType: hard
"@isaacs/cliui@npm:^8.0.2":
version: 8.0.2
resolution: "@isaacs/cliui@npm:8.0.2"
@ -636,7 +652,7 @@ __metadata:
dependencies:
"@janhq/core": ../../core/package.tgz
cpx: "npm:^1.5.0"
rimraf: "npm:^3.0.2"
rimraf: "npm:^6.0.1"
rolldown: "npm:1.0.0-beta.1"
run-script-os: "npm:^1.1.6"
ts-loader: "npm:^9.5.0"
@ -650,7 +666,7 @@ __metadata:
dependencies:
"@janhq/core": ../../core/package.tgz
cpx: "npm:^1.5.0"
rimraf: "npm:^3.0.2"
rimraf: "npm:^6.0.1"
rolldown: "npm:1.0.0-beta.1"
ts-loader: "npm:^9.5.0"
typescript: "npm:^5.7.2"
@ -659,64 +675,89 @@ __metadata:
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fdownload-extension%40workspace%3Adownload-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f2e0b5&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
dependencies:
rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0"
checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e
checksum: 10c0/6f085adb6211c2d0735c2ebccf38c26b90c78a786d6d327fab3e1a9e10380a522429e19f9b9752f7c5a42eb6d80f37c7022c5146b2e4ca1fada609dccbea4194
languageName: node
linkType: hard
"@janhq/download-extension@workspace:download-extension":
version: 0.0.0-use.local
resolution: "@janhq/download-extension@workspace:download-extension"
dependencies:
"@janhq/core": ../../core/package.tgz
"@tauri-apps/api": "npm:^2.5.0"
cpx: "npm:^1.5.0"
rimraf: "npm:^6.0.1"
rolldown: "npm:1.0.0-beta.1"
run-script-os: "npm:^1.1.6"
typescript: "npm:5.8.3"
vitest: "npm:^3.0.6"
languageName: unknown
linkType: soft
"@janhq/engine-management-extension@workspace:engine-management-extension":
version: 0.0.0-use.local
resolution: "@janhq/engine-management-extension@workspace:engine-management-extension"
@ -1436,6 +1477,13 @@ __metadata:
languageName: node
linkType: hard
"@tauri-apps/api@npm:^2.5.0":
version: 2.6.0
resolution: "@tauri-apps/api@npm:2.6.0"
checksum: 10c0/211353d951c7e3e5298f074ec762b5853ff0cdee261478c27db1e450fcf3d6f2c03a616483abbf9dfc79f13c6dfcfa7db0b790c1384c113951c0d694809f05ef
languageName: node
linkType: hard
"@tybys/wasm-util@npm:^0.9.0":
version: 0.9.0
resolution: "@tybys/wasm-util@npm:0.9.0"
@ -2587,7 +2635,7 @@ __metadata:
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3":
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6":
version: 7.0.6
resolution: "cross-spawn@npm:7.0.6"
dependencies:
@ -3392,6 +3440,16 @@ __metadata:
languageName: node
linkType: hard
"foreground-child@npm:^3.3.1":
version: 3.3.1
resolution: "foreground-child@npm:3.3.1"
dependencies:
cross-spawn: "npm:^7.0.6"
signal-exit: "npm:^4.0.1"
checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3
languageName: node
linkType: hard
"fragment-cache@npm:^0.2.1":
version: 0.2.1
resolution: "fragment-cache@npm:0.2.1"
@ -3583,6 +3641,22 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^11.0.0":
version: 11.0.3
resolution: "glob@npm:11.0.3"
dependencies:
foreground-child: "npm:^3.3.1"
jackspeak: "npm:^4.1.1"
minimatch: "npm:^10.0.3"
minipass: "npm:^7.1.2"
package-json-from-dist: "npm:^1.0.0"
path-scurry: "npm:^2.0.0"
bin:
glob: dist/esm/bin.mjs
checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661
languageName: node
linkType: hard
"glob@npm:^7.0.5, glob@npm:^7.1.3, glob@npm:^7.1.4":
version: 7.2.3
resolution: "glob@npm:7.2.3"
@ -4205,6 +4279,15 @@ __metadata:
languageName: node
linkType: hard
"jackspeak@npm:^4.1.1":
version: 4.1.1
resolution: "jackspeak@npm:4.1.1"
dependencies:
"@isaacs/cliui": "npm:^8.0.2"
checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042
languageName: node
linkType: hard
"jake@npm:^10.8.5":
version: 10.9.2
resolution: "jake@npm:10.9.2"
@ -4829,6 +4912,13 @@ __metadata:
languageName: node
linkType: hard
"lru-cache@npm:^11.0.0":
version: 11.1.0
resolution: "lru-cache@npm:11.1.0"
checksum: 10c0/85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64
languageName: node
linkType: hard
"lru-cache@npm:^5.1.1":
version: 5.1.1
resolution: "lru-cache@npm:5.1.1"
@ -5028,6 +5118,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^10.0.3":
version: 10.0.3
resolution: "minimatch@npm:10.0.3"
dependencies:
"@isaacs/brace-expansion": "npm:^5.0.0"
checksum: 10c0/e43e4a905c5d70ac4cec8530ceaeccb9c544b1ba8ac45238e2a78121a01c17ff0c373346472d221872563204eabe929ad02669bb575cb1f0cc30facab369f70f
languageName: node
linkType: hard
"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
@ -5574,6 +5673,16 @@ __metadata:
languageName: node
linkType: hard
"path-scurry@npm:^2.0.0":
version: 2.0.0
resolution: "path-scurry@npm:2.0.0"
dependencies:
lru-cache: "npm:^11.0.0"
minipass: "npm:^7.1.2"
checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c
languageName: node
linkType: hard
"path-type@npm:^1.0.0":
version: 1.1.0
resolution: "path-type@npm:1.1.0"
@ -5987,6 +6096,18 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:^6.0.1":
version: 6.0.1
resolution: "rimraf@npm:6.0.1"
dependencies:
glob: "npm:^11.0.0"
package-json-from-dist: "npm:^1.0.0"
bin:
rimraf: dist/esm/bin.mjs
checksum: 10c0/b30b6b072771f0d1e73b4ca5f37bb2944ee09375be9db5f558fcd3310000d29dfcfa93cf7734d75295ad5a7486dc8e40f63089ced1722a664539ffc0c3ece8c6
languageName: node
linkType: hard
"rolldown@npm:1.0.0-beta.1":
version: 1.0.0-beta.1
resolution: "rolldown@npm:1.0.0-beta.1"
@ -6979,6 +7100,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:5.8.3":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
languageName: node
linkType: hard
"typescript@npm:^5.3.3, typescript@npm:^5.7.2":
version: 5.7.2
resolution: "typescript@npm:5.7.2"
@ -6999,6 +7130,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>":
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin<compat/typescript>":
version: 5.7.2
resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>::version=5.7.2&hash=5786d5"

View File

@ -1,3 +1,3 @@
module.exports = {
projects: ['<rootDir>/core', '<rootDir>/web', '<rootDir>/joi'],
projects: ['<rootDir>/core'],
}

View File

@ -159,6 +159,7 @@ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
rm -rf ./src-tauri/target 2>/dev/null || true
rm -rf ~/jan/extensions 2>/dev/null || true
rm -rf "~/.cache/jan*" 2>/dev/null || true
rm -rf "./.cache" 2>/dev/null || true
else
# macOS cleanup (matches Makefile)
find . -name "node_modules" -type d -prune -exec rm -rf '{}' + 2>/dev/null || true

View File

@ -12,8 +12,11 @@
"lint": "yarn workspace @janhq/web-app lint",
"dev": "yarn dev:tauri",
"build": "yarn build:web && yarn build:tauri",
"test": "yarn workspace @janhq/web-app test",
"test:coverage": "yarn workspace @janhq/web-app test",
"test": "jest && yarn workspace @janhq/web-app test",
"test:coverage": "yarn test:coverage:jest && yarn test:coverage:vitest && yarn merge:coverage",
"test:coverage:jest": "jest --coverage --coverageDirectory=coverage/jest",
"test:coverage:vitest": "yarn workspace @janhq/web-app test:coverage",
"merge:coverage": "node scripts/merge-coverage.js",
"test:prepare": "yarn build:icon && yarn copy:lib && yarn copy:assets:tauri && yarn build --no-bundle ",
"test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test",
"test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test",
@ -24,9 +27,10 @@
"copy:assets:tauri": "cpx \"pre-install/*.tgz\" \"src-tauri/resources/pre-install/\"",
"download:lib": "node ./scripts/download-lib.mjs",
"download:bin": "node ./scripts/download-bin.mjs",
"build:tauri:linux:win32": "yarn download:bin && yarn build:icon && yarn copy:assets:tauri && yarn tauri build",
"build:tauri:darwin": "yarn build:icon && yarn copy:assets:tauri && yarn tauri build --target universal-apple-darwin",
"build:tauri": "run-script-os",
"build:tauri:win32": "yarn download:bin && yarn tauri build",
"build:tauri:linux": "yarn download:bin && ./src-tauri/build-utils/shim-linuxdeploy.sh yarn tauri build && ./src-tauri/build-utils/buildAppImage.sh",
"build:tauri:darwin": "yarn tauri build --target universal-apple-darwin",
"build:tauri": "yarn install:cortex && yarn build:icon && yarn copy:assets:tauri && run-script-os",
"build:icon": "tauri icon ./src-tauri/icons/icon.png",
"build:core": "cd core && yarn build && yarn pack",
"build:web": "yarn workspace @janhq/web-app build",
@ -39,8 +43,13 @@
"cpx": "^1.5.0",
"cross-env": "^7.0.3",
"husky": "^9.1.5",
"istanbul-api": "^3.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.7",
"jest": "^30.0.3",
"jest-environment-jsdom": "^29.7.0",
"nyc": "^17.1.0",
"rimraf": "^3.0.2",
"run-script-os": "^1.1.6",
"tar": "^4.4.19",

145
scripts/merge-coverage.js Normal file
View File

@ -0,0 +1,145 @@
const { createCoverageMap } = require('istanbul-lib-coverage')
const { createReporter } = require('istanbul-api')
const fs = require('fs')
const path = require('path')
const coverageDir = path.join(__dirname, '../coverage')
const jestCoverage = path.join(coverageDir, 'jest/coverage-final.json')
const vitestCoverage = path.join(coverageDir, 'vitest/coverage-final.json')
const mergedDir = path.join(coverageDir, 'merged')
function normalizePath(filePath, workspace) {
if (workspace === 'jest') {
return `[CORE] ${filePath}`
} else if (workspace === 'vitest') {
return `[WEB-APP] ${filePath}`
}
return filePath
}
async function mergeCoverage() {
const map = createCoverageMap({})
console.log('🔍 Checking coverage files...')
console.log('Jest coverage path:', jestCoverage)
console.log('Vitest coverage path:', vitestCoverage)
console.log('Jest file exists:', fs.existsSync(jestCoverage))
console.log('Vitest file exists:', fs.existsSync(vitestCoverage))
// Load Jest coverage (core workspace)
if (fs.existsSync(jestCoverage)) {
const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
console.log('Jest data keys:', Object.keys(jestData).length)
map.merge(jestData)
console.log('✓ Merged Jest coverage (core workspace)')
} else {
console.log('❌ Jest coverage file not found')
}
// Load Vitest coverage (web-app workspace)
if (fs.existsSync(vitestCoverage)) {
const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
console.log('Vitest data keys:', Object.keys(vitestData).length)
map.merge(vitestData)
console.log('✓ Merged Vitest coverage (web-app workspace)')
} else {
console.log('❌ Vitest coverage file not found')
}
console.log('📊 Total files in coverage map:', map.files().length)
// Create merged directory
if (!fs.existsSync(mergedDir)) {
fs.mkdirSync(mergedDir, { recursive: true })
console.log('✓ Created merged directory')
}
try {
console.log('🔄 Generating reports...')
const context = require('istanbul-lib-report').createContext({
dir: mergedDir,
coverageMap: map,
})
const htmlReporter = require('istanbul-reports').create('html')
const lcovReporter = require('istanbul-reports').create('lcov')
const textReporter = require('istanbul-reports').create('text')
// Generate reports
htmlReporter.execute(context)
lcovReporter.execute(context)
textReporter.execute(context)
console.log('\n📊 Coverage reports merged successfully!')
console.log('📁 HTML report: coverage/merged/index.html')
console.log('📁 LCOV report: coverage/merged/lcov.info')
// Check if files were created
if (fs.existsSync(mergedDir)) {
const mergedFiles = fs.readdirSync(mergedDir)
console.log('📁 Files in merged directory:', mergedFiles)
}
} catch (error) {
console.error('❌ Error generating reports:', error.message)
console.error('Stack trace:', error.stack)
throw error
}
// Generate separate reports for each workspace
await generateWorkspaceReports()
}
async function generateWorkspaceReports() {
// Generate separate core report
if (fs.existsSync(jestCoverage)) {
const coreMap = createCoverageMap({})
const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
coreMap.merge(jestData)
const coreDir = path.join(coverageDir, 'core-only')
if (!fs.existsSync(coreDir)) {
fs.mkdirSync(coreDir, { recursive: true })
}
const coreContext = require('istanbul-lib-report').createContext({
dir: coreDir,
coverageMap: coreMap,
})
const htmlReporter = require('istanbul-reports').create('html')
const textSummaryReporter =
require('istanbul-reports').create('text-summary')
htmlReporter.execute(coreContext)
textSummaryReporter.execute(coreContext)
console.log('📁 Core-only report: coverage/core-only/index.html')
}
// Generate separate web-app report
if (fs.existsSync(vitestCoverage)) {
const webAppMap = createCoverageMap({})
const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
webAppMap.merge(vitestData)
const webAppDir = path.join(coverageDir, 'web-app-only')
if (!fs.existsSync(webAppDir)) {
fs.mkdirSync(webAppDir, { recursive: true })
}
const webAppContext = require('istanbul-lib-report').createContext({
dir: webAppDir,
coverageMap: webAppMap,
})
const htmlReporter = require('istanbul-reports').create('html')
const textSummaryReporter =
require('istanbul-reports').create('text-summary')
htmlReporter.execute(webAppContext)
textSummaryReporter.execute(webAppContext)
console.log('📁 Web-app-only report: coverage/web-app-only/index.html')
}
}
mergeCoverage().catch(console.error)

View File

@ -0,0 +1,33 @@
#!/bin/bash
APPIMAGETOOL="./.cache/build-tools/appimagetool"
RELEASE_CHANNEL=${RELEASE_CHANNEL:-"stable"}
# pull in AppImageTool if it's not pre cached
mkdir -p ./.cache/build-tools
if [ ! -f "${APPIMAGETOOL}" ]; then
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O "${APPIMAGETOOL}"
chmod +x "${APPIMAGETOOL}"
fi
if [ "${RELEASE_CHANNEL}" != "stable" ]; then
APP_DIR=./src-tauri/target/release/bundle/appimage/Jan-${RELEASE_CHANNEL}.AppDir
LIB_DIR=$APP_DIR/usr/lib/Jan-${RELEASE_CHANNEL}/binaries
else
APP_DIR=./src-tauri/target/release/bundle/appimage/Jan.AppDir
LIB_DIR=$APP_DIR/usr/lib/Jan/binaries
fi
# bundle additional resources in the AppDir without pulling in their dependencies
cp ./src-tauri/resources/bin/bun $APP_DIR/usr/bin/bun
mkdir -p $LIB_DIR/engines
cp -f ./src-tauri/binaries/deps/*.so* $LIB_DIR/
cp -f ./src-tauri/binaries/*.so* $LIB_DIR/
cp -rf ./src-tauri/binaries/engines $LIB_DIR/
# remove appimage generated by tauri build
APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
echo $APP_IMAGE
rm -f $APP_IMAGE
# repackage appimage with additional resources
"${APPIMAGETOOL}" $APP_DIR $APP_IMAGE

View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
# wrapper script to pin linuxdeploy version and inject environment variables into the
# build process. While yarn supports injecting environment vairables via env files,
# this applies to all yarn scripts. Using a wrapper allows granular control over
# when environment variables are injected, and avoids tainting the system .cache
# avoid redownloading corepack if possible
export COREPACK_HOME=${COREPACK_HOME:-${XDG_CACHE_HOME:-$HOME/.cache}/node/corepack}
# move cache home to <project root>/.cache
export XDG_CACHE_HOME=${PWD}/.cache
LINUXDEPLOY_VER="1-alpha-20250213-2"
LINUXDEPLOY="$XDG_CACHE_HOME/tauri/linuxdeploy-$LINUXDEPLOY_VER-x86_64.AppImage"
SYMLINK="$XDG_CACHE_HOME/tauri/linuxdeploy-x86_64.AppImage"
mkdir -p "$XDG_CACHE_HOME/tauri"
if [ ! -f "$LINUXDEPLOY" ]; then
GLOB_PATTERN="$XDG_CACHE_HOME/tauri/linuxdeploy-*-x86_64.AppImage"
rm -f $GLOB_PATTERN
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VER/linuxdeploy-x86_64.AppImage" -O "$LINUXDEPLOY"
chmod a+x "$LINUXDEPLOY"
fi
rm -f "$SYMLINK"
ln -s "$LINUXDEPLOY" "$SYMLINK"
"$@"

View File

@ -76,7 +76,6 @@
},
"bundle": {
"active": true,
"targets": ["nsis", "app", "dmg", "deb", "appimage"],
"createUpdaterArtifacts": false,
"icon": [
"icons/32x32.png",
@ -84,22 +83,6 @@
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["resources/pre-install/**/*", "binaries/**/*"],
"externalBin": ["resources/bin/bun", "resources/bin/uv"],
"linux": {
"appimage": {
"bundleMediaFramework": false,
"files": {}
},
"deb": {
"files": {
"usr/bin/bun": "resources/bin/bun"
}
}
},
"windows": {
"signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"
}
]
}
}

View File

@ -0,0 +1,26 @@
{
"bundle": {
"targets": ["deb", "appimage"],
"resources": [
"resources/pre-install/**/*"
],
"externalBin": [
"binaries/cortex-server",
"resources/bin/uv"
],
"linux": {
"appimage": {
"bundleMediaFramework": false,
"files": {}
},
"deb": {
"files": {
"usr/bin/bun": "resources/bin/bun",
"usr/lib/Jan/binaries": "binaries/deps",
"usr/lib/Jan/binaries/engines": "binaries/engines",
"usr/lib/Jan/binaries/libvulkan.so": "binaries/libvulkan.so"
}
}
}
}
}

View File

@ -0,0 +1,15 @@
{
"bundle": {
"targets": ["app", "dmg"],
"resources": [
"resources/pre-install/**/*",
"resources/lib/",
"binaries/**/*"
],
"externalBin": [
"binaries/cortex-server",
"resources/bin/bun",
"resources/bin/uv"
]
}
}

View File

@ -0,0 +1,18 @@
{
"bundle": {
"targets": ["nsis"],
"resources": [
"resources/pre-install/**/*",
"resources/lib/",
"binaries/**/*"
],
"externalBin": [
"binaries/cortex-server",
"resources/bin/bun",
"resources/bin/uv"
],
"windows": {
"signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"
}
}
}

View File

@ -8,7 +8,8 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"test": "vitest"
"test": "vitest --run",
"test:coverage": "vitest --coverage --run"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -45,6 +46,7 @@
"fzf": "^0.5.2",
"i18next": "^25.0.1",
"katex": "^0.16.22",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.522.0",
"motion": "^12.10.5",
@ -81,16 +83,24 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/culori": "^2.1.1",
"@types/istanbul-lib-report": "^3",
"@types/istanbul-reports": "^3",
"@types/lodash.clonedeep": "^4",
"@types/lodash.debounce": "^4",
"@types/node": "^22.14.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "3.2.4",
"clsx": "^2.1.1",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
"istanbul-api": "^3.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.7",
"jsdom": "^26.1.0",
"tailwind-merge": "^3.2.0",
"typescript": "~5.8.3",

View File

@ -14,6 +14,7 @@ const LANGUAGES = [
{ value: 'vn', label: 'Tiếng Việt' },
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
{ value: 'de-DE', label: 'Deutsch' },
]
export default function LanguageSwitcher() {

View File

@ -12,27 +12,30 @@ interface Props {
// Zustand store for thinking block state
type ThinkingBlockState = {
thinkingState: { [id: string]: boolean }
toggleState: (id: string) => void
setThinkingState: (id: string, expanded: boolean) => void
}
const useThinkingStore = create<ThinkingBlockState>((set) => ({
thinkingState: {},
toggleState: (id) =>
setThinkingState: (id, expanded) =>
set((state) => ({
thinkingState: {
...state.thinkingState,
[id]: !state.thinkingState[id],
[id]: expanded,
},
})),
}))
const ThinkingBlock = ({ id, text }: Props) => {
const { thinkingState, toggleState } = useThinkingStore()
const { thinkingState, setThinkingState } = useThinkingStore()
const { streamingContent } = useAppState()
const { t } = useTranslation()
const loading = !text.includes('</think>') && streamingContent
const isExpanded = thinkingState[id] ?? false
const handleClick = () => toggleState(id)
const isExpanded = thinkingState[id] ?? (loading ? true : false)
const handleClick = () => {
const newExpandedState = !isExpanded
setThinkingState(id, newExpandedState)
}
if (!text.replace(/<\/?think>/g, '').trim()) return null

View File

@ -26,7 +26,6 @@ import {
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import { toast } from 'sonner'
import {
Tooltip,
TooltipContent,
@ -75,6 +74,69 @@ const CopyButton = ({ text }: { text: string }) => {
)
}
const EditDialog = ({
message,
setMessage,
}: {
message: string
setMessage: (message: string) => void
}) => {
const { t } = useTranslation()
const [draft, setDraft] = useState(message)
const handleSave = () => {
if (draft !== message) {
setMessage(draft)
}
}
return (
<Dialog>
<DialogTrigger>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex outline-0 items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
<IconPencil size={16} />
</div>
</TooltipTrigger>
<TooltipContent>
<p>{t('edit')}</p>
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent className="w-3/4 h-3/4">
<DialogHeader>
<DialogTitle>{t('common:dialogs.editMessage.title')}</DialogTitle>
<Textarea
value={draft}
onChange={(e) => setDraft(e.target.value)}
className="mt-2 resize-none h-full w-full"
onKeyDown={(e) => {
// Prevent key from being captured by parent components
e.stopPropagation()
}}
/>
<DialogFooter className="mt-2 flex items-center">
<DialogClose asChild>
<Button variant="link" size="sm" className="hover:no-underline">
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button
disabled={draft === message || !draft}
onClick={handleSave}
>
Save
</Button>
</DialogClose>
</DialogFooter>
</DialogHeader>
</DialogContent>
</Dialog>
)
}
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
export const ThreadContent = memo(
(
@ -85,9 +147,9 @@ export const ThreadContent = memo(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
streamTools?: any
contextOverflowModal?: React.ReactNode | null
updateMessage?: (item: ThreadMessage, message: string) => void
}
) => {
const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
const { t } = useTranslation()
// Use useMemo to stabilize the components prop
@ -166,23 +228,6 @@ export const ThreadContent = memo(
}
}, [deleteMessage, getMessages, item])
const editMessage = useCallback(
(messageId: string) => {
const threadMessages = getMessages(item.thread_id)
const index = threadMessages.findIndex((msg) => msg.id === messageId)
if (index === -1) return
// Delete all messages after the edited message
for (let i = threadMessages.length - 1; i >= index; i--) {
deleteMessage(threadMessages[i].thread_id, threadMessages[i].id)
}
sendMessage(message)
},
[deleteMessage, getMessages, item.thread_id, message, sendMessage]
)
const isToolCalls =
item.metadata &&
'tool_calls' in item.metadata &&
@ -209,61 +254,14 @@ export const ThreadContent = memo(
</div>
</div>
<div className="flex items-center justify-end gap-2 text-main-view-fg/60 text-xs mt-2">
<Dialog>
<DialogTrigger>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex outline-0 items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative">
<IconPencil size={16} />
</div>
</TooltipTrigger>
<TooltipContent>
<p>{t('edit')}</p>
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('common:dialogs.editMessage.title')}</DialogTitle>
<Textarea
value={message}
onChange={(e) => {
setMessage(e.target.value)
}}
className="mt-2 resize-none"
onKeyDown={(e) => {
// Prevent key from being captured by parent components
e.stopPropagation()
}}
/>
<DialogFooter className="mt-2 flex items-center">
<DialogClose asChild>
<Button
variant="link"
size="sm"
className="hover:no-underline"
>
Cancel
</Button>
</DialogClose>
<DialogClose asChild>
<Button
disabled={!message}
onClick={() => {
editMessage(item.id)
toast.success(t('common:toast.editMessage.title'), {
id: 'edit-message',
description: t('common:toast.editMessage.description'),
})
}}
>
Save
</Button>
</DialogClose>
</DialogFooter>
</DialogHeader>
</DialogContent>
</Dialog>
<EditDialog
message={item.content?.[0]?.text.value}
setMessage={(message) => {
if (item.updateMessage) {
item.updateMessage(item, message)
}
}}
/>
<Tooltip>
<TooltipTrigger asChild>
<button
@ -360,6 +358,12 @@ export const ThreadContent = memo(
'hidden'
)}
>
<EditDialog
message={item.content?.[0]?.text.value}
setMessage={(message) =>
item.updateMessage && item.updateMessage(item, message)
}
/>
<CopyButton text={item.content?.[0]?.text.value || ''} />
<Tooltip>
<TooltipTrigger asChild>
@ -391,7 +395,9 @@ export const ThreadContent = memo(
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('common:dialogs.messageMetadata.title')}</DialogTitle>
<DialogTitle>
{t('common:dialogs.messageMetadata.title')}
</DialogTitle>
<div className="space-y-2">
<div className="border border-main-view-fg/10 rounded-md overflow-hidden">
<CodeEditor

View File

@ -148,6 +148,14 @@ export const sendCompletion = async (
baseURL: provider.base_url,
// Use Tauri's fetch to avoid CORS issues only for openai-compatible provider
...(providerName === 'openai-compatible' && { fetch: fetchTauri }),
// OpenRouter identification headers for Jan
// ref: https://openrouter.ai/docs/api-reference/overview#headers
...(provider.provider === 'openrouter' && {
defaultHeaders: {
'HTTP-Referer': 'https://jan.ai',
'X-Title': 'Jan',
},
}),
} as ExtendedConfigOptions)
if (
thread.model.id &&
@ -306,10 +314,10 @@ export const extractToolCall = (
* @param calls
* @param builder
* @param message
* @param content
* @param approvedTools - Record of approved tools per thread
* @param showModal - Function to show approval modal, returns true if approved
* @param allowAllMCPPermissions - Global setting to allow all MCP permissions without modal
* @param abortController
* @param approvedTools
* @param showModal
* @param allowAllMCPPermissions
*/
export const postMessageProcessing = async (
calls: ChatCompletionMessageToolCall[],

View File

@ -0,0 +1,32 @@
{
"title": "Assistenten",
"editAssistant": "Assistent bearbeiten",
"deleteAssistant": "Assistenten löschen",
"deleteConfirmation": "Assistenten löschen",
"deleteConfirmationDesc": "Bist Du sicher, daß Du diesen Assistenten löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"cancel": "Abbrechen",
"delete": "Löschen",
"addAssistant": "Assistenten hinzufügen",
"emoji": "Emoji",
"name": "Name",
"enterName": "Namen eingeben",
"description": "Beschreibung (optional)",
"enterDescription": "Beschreibung eingeben",
"instructions": "Anweisungen",
"enterInstructions": "Anweisungen eingeben",
"predefinedParameters": "Vordefinierte Parameter",
"parameters": "Parameter",
"key": "Schlüssel",
"value": "Wert",
"stringValue": "String",
"numberValue": "Number",
"booleanValue": "Boolean",
"jsonValue": "JSON",
"trueValue": "True",
"falseValue": "False",
"jsonValuePlaceholder": "JSON Value",
"save": "Speichern",
"createNew": "Neuen Assistenten anlegen",
"personality": "Persönlichkeit",
"capabilities": "Fähigkeiten"
}

View File

@ -0,0 +1,10 @@
{
"welcome": "Hi, wie geht es Dir?",
"description": "Wie kann ich Dir heute helfen?",
"status": {
"empty": "Keine Chats gefunden"
},
"sendMessage": "Nachricht senden",
"newConversation": "Neue Konversation",
"clearHistory": "Verlauf löschen"
}

View File

@ -0,0 +1,267 @@
{
"assistants": "Assistenten",
"hardware": "Hardware",
"mcp-servers": "Mcp Server",
"local_api_server": "Lokaler API Server",
"https_proxy": "HTTPS Proxy",
"extensions": "Erweiterungen",
"general": "Allgemein",
"settings": "Einstellungen",
"modelProviders": "Modell Anbieter",
"appearance": "Erscheinung",
"privacy": "Privatsphäre",
"keyboardShortcuts": "Shortcuts",
"newChat": "Neuer Chat",
"favorites": "Favoriten",
"recents": "Kürzlich",
"hub": "Hub",
"helpSupport": "Hilfe & Support",
"helpUsImproveJan": "Hilf uns Jan zu verbessern",
"unstarAll": "Alle De-Favorisieren",
"unstar": "De-Favorisieren",
"deleteAll": "Alles löschen",
"star": "Favorisieren",
"rename": "Umbenennen",
"delete": "Löschen",
"copied": "Kopiert!",
"dataFolder": "Daten Ordner",
"others": "Andere",
"language": "Sprache",
"reset": "Zurücksetzen",
"search": "Suchen",
"name": "Name",
"cancel": "Abbrechen",
"create": "Anlegen",
"save": "Speichern",
"edit": "Editieren",
"copy": "Kopieren",
"back": "Zurück",
"close": "Schließen",
"next": "Nächster",
"finish": "Abschließen",
"skip": "Überspringen",
"allow": "Erlauben",
"deny": "Verbieten",
"start": "Start",
"stop": "Stop",
"preview": "Vorschau",
"compactWidth": "Kompakte Breite",
"fullWidth": "Volle Breite",
"dark": "Dunkel",
"light": "Hell",
"system": "System",
"auto": "Automatisch",
"english": "Englisch",
"medium": "Medium",
"newThread": "Neuer Thread",
"noResultsFound": "Keine Ergebnisse gefunden",
"noThreadsYet": "Keine Threads bisher",
"noThreadsYetDesc": "Starte eine neue Unterhaltung, um deinen Threadverlauf hier anzuzeigen.",
"downloads": "Downloads",
"downloading": "Downloading",
"cancelDownload": "Download abbrechen",
"downloadCancelled": "Download wurde abgebrochen",
"downloadComplete": "Download abgeschlossen",
"thinking": "Denke nach...",
"thought": "Gedanke",
"callingTool": "Rufe Werkzeug auf",
"completed": "Abgeschlossen",
"image": "Bild",
"vision": "Vision",
"embeddings": "Einbettungen",
"tools": "Werkzeuge",
"webSearch": "Web Suche",
"reasoning": "Argumentation",
"selectAModel": "Wähle ein Modell",
"noToolsAvailable": "Keine Werkzeuge verfügbar",
"noModelsFoundFor": "Keine Modelle gefunden zu \"{{searchValue}}\"",
"customAvatar": "Benutzerdefinierter Avatar",
"editAssistant": "Assistenten bearbeiten",
"jan": "Jan",
"metadata": "Metadaten",
"regenerate": "Neu generieren",
"threadImage": "Thread Bild",
"editMessage": "Nachricht bearbeiten",
"deleteMessage": "Nachricht löschen",
"deleteThread": "Thread löschen",
"renameThread": "Thread umbenennen",
"threadTitle": "Thread Titel",
"deleteAllThreads": "Alle Threads löschen",
"allThreadsUnfavorited": "Alle Threads defavorisieren",
"deleteAllThreadsConfirm": "Bist Du sicher, daß Du alle Threads löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"addProvider": "Anbieter hinzufügen",
"addOpenAIProvider": "OpenAI Anbieter hinzufügen",
"enterNameForProvider": "Gib einen Namen ein für den Anbieter",
"providerAlreadyExists": "Ein Anbieter mit dem Namen \"{{name}}\" existiert bereits. Bitte wähle einen anderen Namen.",
"adjustFontSize": "Schriftgröße einstellen",
"changeLanguage": "Sprache wechseln",
"editTheme": " Vorlage bearbeiten",
"editCodeBlockStyle": "Code Block Stil bearbeiten",
"editServerHost": "Server Host bearbeiten",
"pickColorWindowBackground": "Fensterhintergrundfarbe wählen",
"pickColorAppMainView": "Farben wählen für das Hauptfenster",
"pickColorAppPrimary": "Wähle primäre App-Farbe",
"pickColorAppAccent": "Wähle hervorgehobene App-Farbe",
"pickColorAppDestructive": "Wähle destruktive App-Farbe",
"apiKeyRequired": "API Key ist erforderlich",
"enterTrustedHosts": "Vertraute Hosts eingeben",
"placeholder": {
"chatInput": "Frage mich etwas..."
},
"confirm": "Bestätige",
"loading": "Lade...",
"error": "Fehler",
"success": "Erfolg",
"warning": "Warnung",
"noResultsFoundDesc": "Wir konnten keinen Chat finden, welcher mit deiner Suche übereinstimmt. Versuche andere Schlüsselworte.",
"searchModels": "Suche Modelle...",
"searchStyles": "Suche Styles...",
"createAssistant": "Assistenten anlegen",
"enterApiKey": "API Key eingeben",
"scrollToBottom": "Zum Ende scrollen",
"addModel": {
"title": "Modell hinzufügen",
"modelId": "Modell ID",
"enterModelId": "Modell ID eingeben",
"addModel": "Modell hinzufügen",
"description": "Neues Modell zum Anbieter hinzufügen",
"exploreModels": "Modelle des Anbieters ansehen"
},
"mcpServers": {
"editServer": "Server bearbeiten",
"addServer": "Server hinzufügen",
"serverName": "Server Name",
"enterServerName": "Server Namen eingeben",
"command": "Kommando",
"enterCommand": "Kommando eingeben",
"arguments": "Argumente",
"argument": "Argument {{index}}",
"envVars": "Umgebungs Variable",
"key": "Schlüssel",
"value": "Wert",
"save": "Speichern"
},
"deleteServer": {
"title": "Server löschen",
"delete": "Löschen"
},
"editJson": {
"errorParse": "Failed to parse JSON",
"errorPaste": "Failed to paste JSON",
"errorFormat": "Invalid JSON format",
"titleAll": "Edit All Servers Configuration",
"placeholder": "Enter JSON configuration...",
"save": "Save"
},
"editModel": {
"title": "Modell bearbeiten: {{modelId}}",
"description": "Konfiguriere die Modelfähigkeiten durch Umschalten der untenstehenden Optionen.",
"capabilities": "Modelfähigkeiten",
"tools": "Werkzeuge",
"vision": "Vision",
"embeddings": "Einbettungen",
"notAvailable": "Nicht verfügbar bisher"
},
"outOfContextError": {
"truncateInput": "Input verkleinern",
"title": "Out of context error",
"description": "Dieser Chat erreicht das KI Speicher Limit. Wir können das Speicherfenster vergrößern (auch Kontextgröße genannt), so daß sich die KI an mehr erinnern kann, aber dies erfordert es mehr Speicher zu verwenden. Um Platz zu schaffen können wir auch den Input verkleinern, was bedeutet, daß die KI einen Teil seiner Chat-Historie vergisst.",
"increaseContextSizeDescription": "Möchtest Du die Kontextgröße erhöhen?",
"increaseContextSize": "Kontextgröße erhöhen"
},
"toolApproval": {
"title": "Anfrage für Werkzeugnutzung",
"description": "Der Assistant möchte <strong>{{toolName}}</strong> verwenden",
"securityNotice": "Erlaube nur Werkzeuge zu nutzen denen Du vertraust. Werkzeuge können auf deine Daten oder System zugreifen.",
"deny": "Ablehnen",
"allowOnce": "Einmal erlauben",
"alwaysAllow": "Immer erlauben"
},
"deleteModel": {
"title": "Modell löschen: {{modelId}}",
"description": "Bist Du sicher, daß Du dieses Modell löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"success": "Modell {{modelId}} wurde permanent gelöscht.",
"cancel": "Abbrechen",
"delete": "Löschen"
},
"deleteProvider": {
"title": "Anbieter löschen",
"description": "Lösche diesen Anbieter und alle seine Modelle. Diese Aktion kann nicht rückgängig gemacht werden.",
"success": "Anbieter {{provider}} wurde permanent gelöscht.",
"confirmTitle": "Anbieter löschen: {{provider}}",
"confirmDescription": "Bist Du sicher, daß Du diesen Anbieter löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"cancel": "Abbrechen",
"delete": "Löschen"
},
"modelSettings": {
"title": "Modell Einstellungen - {{modelId}}",
"description": "Modeleinstellungen konfigurieren, um die Leistung und das Verhalten zu optimieren."
},
"dialogs": {
"changeDataFolder": {
"title": "Speicherort des Datenordners ändern",
"description": "Bist Du sicher den Speicherort des Datenordners zu ändern? Dies wird alle Daten zum neuen Speicherort verschieben und anschließend die Anwendung neu starten.",
"currentLocation": "Aktueller Speicherort:",
"newLocation": "Neuer Speicherort:",
"cancel": "Abbrechen",
"changeLocation": "Speicherort ändern"
},
"deleteAllThreads": {
"title": "Alle Threads löschen",
"description": "Alle Threads werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden."
},
"deleteThread": {
"description": "Bist Du sicher, daß Du diesen Thread löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden."
},
"editMessage": {
"title": "Nachricht bearbeiten"
},
"messageMetadata": {
"title": "Nachricht Metadaten"
}
},
"toast": {
"allThreadsUnfavorited": {
"title": "Alle Threads De-Favorisieren ",
"description": "Alle deine Threads wurden defavorisiert."
},
"deleteAllThreads": {
"title": "Alle Threads löschen",
"description": "Alle deine Threads wurden permanent gelöscht."
},
"renameThread": {
"title": "Thread umbenennen",
"description": "Thread Titel wurde umbenannt zu '{{title}}'"
},
"deleteThread": {
"title": "Thread löschen",
"description": "This thread has been permanently deleted."
},
"editMessage": {
"title": "Nachricht bearbeiten",
"description": "Die Nachricht wurde erfolgreich bearbeitet. Bitte warte auf die Antwort des Models."
},
"appUpdateDownloaded": {
"title": "App Update heruntergeladen",
"description": "Das App-Update wurde erfolgreich heruntergeladen."
},
"appUpdateDownloadFailed": {
"title": "App Update Download fehlgeschlagen",
"description": "Das App-Update konnte nicht heruntergeladen werden. Bitte versuche es noch einmal."
},
"downloadComplete": {
"title": "Download abgeschlossen",
"description": "Das Modell {{modelId}} wurde heruntergeladen"
},
"downloadCancelled": {
"title": "Download abgebrochen",
"description": "Der Download-Prozess wurde abgebrochen"
}
},
"cortexFailureDialog": {
"title": "Problem mit der lokalen KI-Engine",
"description": "Die Lokale KI-Engine (Cortex) konnte nach mehreren Versuchen nicht gestartet werden. Dies verhindert möglicherweise die korrekte Ausführung diverser Funktionalitäten.",
"contactSupport": "Support kontaktieren",
"restartJan": "Jan neu starten"
}
}

View File

@ -0,0 +1,31 @@
{
"sortNewest": "Neueste",
"sortMostDownloaded": "Meist heruntergeladen",
"use": "Nutzen",
"download": "Herunterladen",
"downloaded": "Heruntergeladen",
"loadingModels": "Lade Modelle...",
"noModels": "Keine Modelle gefunden",
"by": "Von",
"downloads": "Downloads",
"variants": "Varianten",
"showVariants": "Zeige Varianten",
"useModel": "Nutze dieses Modell",
"downloadModel": "Modell herunterladen",
"searchPlaceholder": "Suche nach Modellen auf Hugging Face...",
"editTheme": "Bearbeite Erscheinungsbild",
"joyride": {
"recommendedModelTitle": "Empfohlenes Modell",
"recommendedModelContent": "Durchsuche und lade leistungsstarke KI-Modelle verschiedener Anbieter an einem Ort herunter. Wir empfehlen mit Jan-Nano zu beginnen, einem Modell, das für Funktionsaufrufe, Werkzeug-Integration und Forschungsfunktionen optimiert ist. Es eignet sich ideal für die Entwicklung interaktiver KI-Agenten.",
"downloadInProgressTitle": "Download läuft",
"downloadInProgressContent": "Dein Modell wird jetzt heruntergeladen. Verfolge hier den Fortschritt. Sobald der Download abgeschlossen ist, ist es einsatzbereit.",
"downloadModelTitle": "Modell herunterladen",
"downloadModelContent": "Klicke auf den Download Button um das Herunterladen zu beginnen.",
"back": "Zurück",
"close": "Schließen",
"lastWithDownload": "Download",
"last": "Fertig",
"next": "Nächstes",
"skip": "Überspringen"
}
}

View File

@ -0,0 +1,3 @@
{
"noLogs": "Keine Logs verfügbar"
}

View File

@ -0,0 +1,43 @@
{
"editServer": "MCP Server bearbeiten",
"addServer": "MCP Server hinzufügen",
"serverName": "Server Name",
"enterServerName": "Server Namen eingeben",
"command": "Kommando",
"enterCommand": "Kommando eingeben (uvx oder npx)",
"arguments": "Argumente",
"argument": "Argument {{index}}",
"envVars": "Umgebungs Variablen",
"key": "Schlüssel",
"value": "Wert",
"save": "Speichern",
"status": "Status",
"connected": "Verbunden",
"disconnected": "Nicht verbunden",
"deleteServer": {
"title": "MCP Server löschen",
"description": "Bist Du sicher, dass Du den MCP Server {{serverName}} löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.",
"delete": ""
},
"editJson": {
"title": "JSON für den MCP Server bearbeiten: {{serverName}}",
"titleAll": "JSON für alle MCP Server bearbeiten",
"placeholder": "JSON Konfiguration eingeben",
"errorParse": "Fehler beim Parsen der initialen Daten",
"errorPaste": "Ungültiges JSON Format in dem eingefügten Inhalt",
"errorFormat": "Ungültiges JSON Format",
"save": "Speichern"
},
"checkParams": "Bitte überprüfe die Parameter gemäß dem Tutorial.",
"title": "MCP Server",
"experimental": "Experimentell",
"editAllJson": "JSON aller Server bearbeiten",
"findMore": "Finde mehr MCP Server bei",
"allowPermissions": "Erlaube allen MCP Werkzeugen den Zugriff",
"allowPermissionsDesc": "Wenn aktiviert, werden alle MCP-Werkzeug-Aufrufe automatisch genehmigt, ohne dass Berechtigungsdialoge angezeigt werden.",
"noServers": "Keine MCP Server gefunden",
"args": "Argumente",
"env": "Umgebung",
"serverStatusActive": "Server {{serverKey}} erfolgreich aktiviert",
"serverStatusInactive": "Server {{serverKey}} erfolgreich deaktiviert"
}

View File

@ -0,0 +1,7 @@
{
"title": "Out of context error",
"description": "Dieser Chat erreicht das KI Speicher Limit. Wir können das Speicherfenster vergrößern (auch Kontextgröße genannt), so dass sich die KI an mehr erinnern kann, aber dies erfordert es mehr Speicher zu verwenden. Um Platz zu schaffen können wir auch den Input verkleinern, was bedeutet, dass die KI einen Teil seiner Chat-Historie vergisst.",
"increaseContextSizeDescription": "Möchtest Du die Kontextgröße erhöhen?",
"truncateInput": "Input verkleinern",
"increaseContextSize": "Kontextgröße erhöhen"
}

View File

@ -0,0 +1,5 @@
{
"addProvider": "Anbieter hinzufügen",
"addOpenAIProvider": "OpenAI Anbieter hinzufügen",
"enterNameForProvider": "Namen für Anbieter eingeben"
}

View File

@ -0,0 +1,68 @@
{
"joyride": {
"chooseProviderTitle": "Wähle einen Anbieter",
"chooseProviderContent": "Wähle den Anbieter aus, den Du verwenden möchtest, und stelle sicher, daß Du Zugriff auf einen API-Schlüssel dafür hast.",
"getApiKeyTitle": "Hole Dir Deinen API-Schlüssel",
"getApiKeyContent": "Melde Dich bei dem Anbieter an, um Deinen API-Schlüssel zu finden oder zu generieren.",
"insertApiKeyTitle": "Gebe Deinen API-Schlüssel ein",
"insertApiKeyContent": "Füge hier Deinen API-Schlüssel ein, um eine Verbindung zum Anbieter herzustellen und ihn zu aktivieren.",
"back": "Zurück",
"close": "Schließen",
"last": "Abschließen",
"next": "Nächster",
"skip": "Überspringen"
},
"refreshModelsError": "Der Anbieter muss über eine Basis-URL und einen API-Schlüssel verfügen, um Modelle abzurufen.",
"refreshModelsSuccess": "{{count}} neue(s) Modell(e) hinzugefügt von {{provider}}.",
"noNewModels": "Keine neuen Modelle gefunden. Alle verfügbaren Modelle sind bereits hinzugefügt.",
"refreshModelsFailed": "Das Abrufen der Modelle von {{provider}} ist fehlgeschlagen. Bitte überprüfe Deinen API-Schlüssel und die Basis-URL.",
"models": "Modelle",
"refreshing": "Aktualisiere...",
"refresh": "Aktualisieren",
"import": "Importieren",
"importModelSuccess": "Modell von {{provider}} wurde erfolgreich importiert.",
"importModelError": "Modellimport ist fehlgeschlagen:",
"stop": "Stop",
"start": "Start",
"noModelFound": "Kein Modell gefunden",
"noModelFoundDesc": "Verfügbare Modelle werden hier aufgelistet. Wenn Du noch keine Modelle hast, können diese im Hub heruntergeladen werden.",
"configuration": "Konfiguration",
"apiEndpoint": "API Endpunkt",
"testConnection": "Teste Verbindung",
"addModel": {
"title": "Neues Modell hinzufügen",
"description": "Neues Modell zu Anbieter {{provider}} hinzufügen.",
"modelId": "Modell ID",
"enterModelId": "Modell ID eingeben",
"exploreModels": "Sehe Modellliste von {{provider}}",
"addModel": "Modell hinzufügen"
},
"deleteModel": {
"title": "Lösche Modell: {{modelId}}",
"description": "Möchtest Du dieses Modell wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"success": "Modell {{modelId}} wurde permanent gelöscht.",
"cancel": "Abbrechen",
"delete": "Löschen"
},
"deleteProvider": {
"title": "Anbieter löschen",
"description": "Lösche diesen Anbieter und alle seine Modelle. Diese Aktion kann nicht rückgängig gemacht werden.",
"success": "Anbieter {{provider}} wurde permanent gelöscht.",
"confirmTitle": "Lösche Anbieter: {{provider}}",
"confirmDescription": "Möchtest Du diesen Anbieter wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"cancel": "Abbrechen",
"delete": "Löschen"
},
"editModel": {
"title": "Modell bearbeiten: {{modelId}}",
"description": "Konfiguriere die Modellfunktionen, indem Du die folgenden Optionen einstellst.",
"capabilities": "Fähigkeiten",
"tools": "Werkzeuge",
"vision": "Vision",
"embeddings": "Einbettungen",
"notAvailable": "Noch nicht verfügbar"
},
"addProvider": "Anbieter hinzufügen",
"addOpenAIProvider": "OpenAI Anbieter hinzufügen",
"enterNameForProvider": "Namen für den Anbieter eingeben"
}

View File

@ -0,0 +1,248 @@
{
"autoDownload": "Automatisch neue Updates herunterladen",
"checkForUpdates": "Auf Updates prüfen",
"checkForUpdatesDesc": "Prüfe, ob eine neuere Version von Jan verfügbar ist.",
"checkingForUpdates": "Suche nach Updates...",
"noUpdateAvailable": "Du verwendest die neueste Version",
"devVersion": "Entwicklungsversion erkannt",
"updateError": "Fehler beim Suchen nach Updates",
"changeLocation": "Ort ändern",
"copied": "Kopiert",
"copyPath": "Pfad kopieren",
"openLogs": "Logs öffnen",
"revealLogs": "Logs aufzeigen",
"showInFinder": "Zeige im Finder",
"showInFileExplorer": "Zeige in Datei Explorer",
"openContainingFolder": "Enthaltenen Ordner öffnen",
"failedToRelocateDataFolder": "Datenordner konnte nicht verschoben werden",
"failedToRelocateDataFolderDesc": "Der Datenordner konnte nicht verschoben werden. Bitte versuche es erneut.",
"factoryResetTitle": "Auf Werkseinstellungen zurücksetzen",
"factoryResetDesc": "Dadurch werden alle App-Einstellungen auf die Standardeinstellungen zurückgesetzt. Dieser Vorgang kann nicht rückgängig gemacht werden. Wir empfehlen dies nur, wenn die App beschädigt ist.",
"cancel": "Abbrechen",
"reset": "Zurücksetzen",
"resources": "Ressourcen",
"documentation": "Dokumentation",
"documentationDesc": "Erfahre, wie Du Jan verwenden und seine Funktionen erkunden kannst.",
"viewDocs": "Dokumentation ansehen",
"releaseNotes": "Release Notes",
"releaseNotesDesc": "Siehe, was es Neues in der neuesten Version von Jan gibt.",
"viewReleases": "Releases ansehen",
"community": "Community",
"github": "GitHub",
"githubDesc": "Beitragen zu Jan's Entwicklung.",
"discord": "Discord",
"discordDesc": "Trete unserer Community für Unterstützung und Diskussionen bei.",
"support": "Support",
"reportAnIssue": "Melde ein Problem",
"reportAnIssueDesc": "Hast Du einen Bug gefunden? Hilf uns, indem Du ein Problem auf GitHub meldest.",
"reportIssue": "Problem melden",
"credits": "Credits",
"creditsDesc1": "Jan wurde mit ❤️ gebaut vom Menlo Team.",
"creditsDesc2": "Besonderer Dank gilt unseren Open-Source-Abhängigkeiten - insbesondere llama.cpp und Tauri - und unserer großartigen KI-Community.",
"appVersion": "App Version",
"dataFolder": {
"appData": "App Daten",
"appDataDesc": "Standardspeicherort für Nachrichten und andere Benutzerdaten.",
"appLogs": "App Logs",
"appLogsDesc": "Zeige detaillierte Logs der App an."
},
"others": {
"spellCheck": "Rechtschreibprüfung",
"spellCheckDesc": "Aktiviere die Rechtschreibprüfung für Deine Threads.",
"resetFactory": "Auf Werkseinstellungen zurücksetzen",
"resetFactoryDesc": "Setzt die Anwendung in den ursprünglichen Zustand zurück und löscht dabei alle Modelle und den Chatverlauf. Diese Aktion ist irreversibel und wird nur empfohlen, wenn die Anwendung beschädigt ist."
},
"shortcuts": {
"application": "Anwendung",
"newChat": "Neuer Chat",
"newChatDesc": "Neuen Chat anlegen.",
"toggleSidebar": "Seitenleiste umschalten",
"toggleSidebarDesc": "Seitenleiste ein- oder ausblenden.",
"zoomIn": "Vergrößern",
"zoomInDesc": "Erhöhe die Zoomstufe.",
"zoomOut": "Verkleinern",
"zoomOutDesc": "Verringere die Zoomstufe.",
"chat": "Chat",
"sendMessage": "Nachricht senden",
"sendMessageDesc": "Aktuelle Nachricht senden",
"enter": "Eingabe",
"newLine": "Neue Zeile",
"newLineDesc": "Neue Zeile einfügen.",
"shiftEnter": "Shift + Enter",
"navigation": "Navigation",
"goToSettings": "Gehe zu den Einstellungen",
"goToSettingsDesc": "Einstellungen öffnen."
},
"appearance": {
"title": "Erscheinungsbild",
"theme": "Theme",
"themeDesc": "Dem Betriebssystem anpassen.",
"fontSize": "Schriftgröße",
"fontSizeDesc": "Einstellen der App Schriftgröße",
"windowBackground": "Fenster Hintergrund",
"windowBackgroundDesc": "Lege die Hintergrundfarbe des App-Fensters fest.",
"appMainView": "App-Hauptansicht",
"appMainViewDesc": "Lege die Hintergrundfarbe des Hauptinhaltsbereichs fest.",
"primary": "Primär",
"primaryDesc": "Lege die Primärfarbe für UI-Komponenten fest.",
"accent": "Akzent",
"accentDesc": "Lege die Akzentfarbe für UI-Hervorhebungen fest.",
"destructive": "Destruktiv",
"destructiveDesc": "Lege die Farbe für destruktive Aktionen fest.",
"resetToDefault": "Auf Werkseinstellungen zurücksetzen",
"resetToDefaultDesc": "Setzt alle Darstellungseinstellungen auf die Standardeinstellungen zurück.",
"resetAppearanceSuccess": "Erscheinungsbild erfolgreich zurückgesetzt",
"resetAppearanceSuccessDesc": "Alle Darstellungseinstellungen wurden auf die Standardeinstellungen zurückgesetzt.",
"chatWidth": "Chat Breite",
"chatWidthDesc": "Passe die Breite der Chatansicht an.",
"codeBlockTitle": "Code Block",
"codeBlockDesc": "Wähle einen Stil zur Syntaxhervorhebung.",
"showLineNumbers": "Zeilennummern anzeigen",
"showLineNumbersDesc": "Zeilennummern in Codeblöcken anzeigen.",
"resetCodeBlockStyle": "Codeblockstil zurücksetzen",
"resetCodeBlockStyleDesc": "Codeblockstil auf Standard zurücksetzen.",
"resetCodeBlockSuccess": "Codeblockstil erfolgreich zurückgesetzt",
"resetCodeBlockSuccessDesc": "Der Codeblockstil wurde auf die Standardeinstellung zurückgesetzt."
},
"hardware": {
"os": "Betriebssystem",
"name": "Name",
"version": "Version",
"cpu": "CPU",
"model": "Modell",
"architecture": "Architektur",
"cores": "Kerne",
"instructions": "Instruktionen",
"usage": "Nutzung",
"memory": "Speicher",
"totalRam": "RAM Total",
"availableRam": "Verfügbarer RAM",
"vulkan": "Vulkan",
"enableVulkan": "Vulkan aktivieren",
"enableVulkanDesc": "Verwende die Vulkan-API zur GPU-Beschleunigung. Aktiviere Vulkan nicht, wenn Du eine NVIDIA-GPU verwendest, da dies zu Kompatibilitätsproblemen führen kann.",
"gpus": "GPUs",
"noGpus": "Keine GPUs erkannt",
"vram": "VRAM",
"freeOf": "frei von",
"driverVersion": "Treiber Version",
"computeCapability": "Rechenfähigkeit",
"systemMonitor": "System Monitor"
},
"httpsProxy": {
"proxy": "Proxy",
"proxyUrl": "Proxy URL",
"proxyUrlDesc": "Die URL und der Port deines Proxyservers.",
"proxyUrlPlaceholder": "http://proxy.example.com:8080",
"authentication": "Authentifizierung",
"authenticationDesc": "Anmeldeinformationen für den Proxyserver, falls erforderlich.",
"username": "Username",
"password": "Passwort",
"noProxy": "Kein Proxy",
"noProxyDesc": "Eine durch Kommas getrennte Liste von Hosts, um den Proxy zu umgehen.",
"noProxyPlaceholder": "localhost,127.0.0.1,.local",
"sslVerification": "SSL Verifikation",
"ignoreSsl": "SSL Certificates ignorieren",
"ignoreSslDesc": "Erlaube selbstsignierte oder nicht verifizierte Zertifikate. Dies kann für einige Proxys erforderlich sein, verringert aber die Sicherheit. Aktiviere diese Option nur, wenn Du Deinem Proxy vertraust.",
"proxySsl": "Proxy SSL",
"proxySslDesc": "Validieren des SSL-Zertifikats, wenn eine Verbindung mit dem Proxy hergestellt wird.",
"proxyHostSsl": "Proxy Host SSL",
"proxyHostSslDesc": "Validieren des SSL-Zertifikats des Proxy-Hosts.",
"peerSsl": "Peer SSL",
"peerSslDesc": "Validieren des SSL-Zertifikats von Peer-Verbindungen.",
"hostSsl": "Host SSL",
"hostSslDesc": "Validieren der SSL-Zertifikate der Zielhosts."
},
"localApiServer": {
"title": "Lokaler API Server",
"description": "Führe lokal einen OpenAI-kompatiblen Server aus.",
"startServer": "Start Server",
"stopServer": "Stop Server",
"serverLogs": "Server Logs",
"serverLogsDesc": "Zeige detaillierte Logs des lokalen API-Servers an.",
"openLogs": "Logs öffnen",
"serverConfiguration": "Server Konfiguration",
"serverHost": "Server Host",
"serverHostDesc": "Netzwerkadresse für den Server.",
"serverPort": "Server Port",
"serverPortDesc": "Portnummer für den API Server.",
"apiPrefix": "API Prefix",
"apiPrefixDesc": "Pfadprefix für den API Endpunkt.",
"apiKey": "API Schlüssel",
"apiKeyDesc": "Authentifiziere Anfragen mit einem API-Schlüssel.",
"trustedHosts": "Vertrauenswürdige Hosts",
"trustedHostsDesc": "Hosts, die auf den Server zugreifen dürfen, durch Kommas getrennt.",
"advancedSettings": "Erweiterte Einstellungen",
"cors": "Cross-Origin Resource Sharing (CORS)",
"corsDesc": "Erlaube Cross-Origin-Anfragen an den API-Server.",
"verboseLogs": "Ausführliche Server Logs",
"verboseLogsDesc": "Aktiviere detaillierte Server Logs zum Debuggen"
},
"privacy": {
"analytics": "Analytik",
"helpUsImprove": "Hilf uns, uns zu verbessern",
"helpUsImproveDesc": "Um uns bei der Verbesserung von Jan zu unterstützen, kannst Du uns anonyme Daten wie Funktionsnutzung und Benutzerzahlen mitteilen. Wir erfassen niemals Deine Chats oder persönlichen Daten.",
"privacyPolicy": "Du hast die volle Kontrolle über Deine Daten. Erfahre mehr in unserer Datenschutzerklärung.",
"analyticsDesc": "Um Jan zu verbessern, müssen wir verstehen, wie es genutzt wird - aber nur mit deiner Hilfe. Du kannst diese Einstellung jederzeit ändern.",
"privacyPromises": "Deine Auswahl hier ändert nichts an unseren grundlegenden Datenschutzversprechen:",
"promise1": "Deine Gespräche bleiben privat und auf deinem Gerät",
"promise2": "Wir erfassen niemals Deine persönlichen Daten oder Chat-Inhalte",
"promise3": "Der gesamte Datenaustausch erfolgt anonym und aggregiert",
"promise4": "Du kannst dich jederzeit abmelden, ohne die Funktionalität zu verlieren",
"promise5": "Wir sind transparent darüber, was wir sammeln und warum"
},
"general": {
"showInFinder": "Im Finder zeigen",
"showInFileExplorer": "Zeige im Datei Explorer",
"openContainingFolder": "Enthaltenen Ordner öffnen",
"failedToRelocateDataFolder": "Datenordner konnte nicht verschoben werden",
"failedToRelocateDataFolderDesc": "Der Datenordner konnte nicht verschoben werden. Bitte versuche es erneut.",
"devVersion": "Entwicklungsversion erkannt",
"noUpdateAvailable": "Du verwendest die neueste Version",
"updateError": "Fehler beim Suchen nach Updates",
"appVersion": "App Version",
"checkForUpdates": "Auf Updates prüfen",
"checkForUpdatesDesc": "Prüfe, ob eine neuere Version von Jan verfügbar ist.",
"checkingForUpdates": "Suche nach Updates...",
"copied": "Kopiert",
"copyPath": "Pfad kopieren",
"changeLocation": "Ort ändern",
"openLogs": "Logs öffnen",
"revealLogs": "Logs anzeigen",
"factoryResetTitle": "Auf Werkseinstellungen zurücksetzen",
"factoryResetDesc": "Dadurch werden alle App-Einstellungen auf die Standardeinstellungen zurückgesetzt. Dieser Vorgang kann nicht rückgängig gemacht werden. Wir empfehlen dies nur, wenn die App beschädigt ist.",
"cancel": "Abbrechen",
"reset": "Zurücksetzen",
"resources": "Ressourcen",
"documentation": "Dokumentation",
"documentationDesc": "Erfahre, wie Du Jan verwenden und seine Funktionen erkunden kannst.",
"viewDocs": "Dokumentation ansehen",
"releaseNotes": "Release Notes",
"releaseNotesDesc": "Sehe, was es Neues in der neuesten Version von Jan gibt.",
"viewReleases": "Releases anzeigen",
"community": "Community",
"github": "GitHub",
"githubDesc": "Trage zu Jan's Entwicklung bei.",
"discord": "Discord",
"discordDesc": "Trete unserer Community für Unterstützung und Diskussionen bei.",
"support": "Support",
"reportAnIssue": "Melde ein Problem",
"reportAnIssueDesc": "Hast Du einen Bug gefunden? Hilf uns, indem Du ein Problem auf GitHub meldest.",
"reportIssue": "Problem melden",
"credits": "Credits",
"creditsDesc1": "Jan wurde mit ❤️ gebaut vom Menlo Team.",
"creditsDesc2": "Besonderer Dank gilt unseren Open-Source-Abhängigkeiten - insbesondere llama.cpp und Tauri - und unserer großartigen KI-Community."
},
"extensions": {
"title": "Erweiterungen"
},
"dialogs": {
"changeDataFolder": {
"title": "Speicherort des Datenordners ändern",
"description": "Möchtest Du den Speicherort des Datenordners wirklich ändern? Dadurch werden alle Deine Daten an den neuen Speicherort verschoben und die Anwendung neu gestartet.",
"currentLocation": "Aktueller Ort:",
"newLocation": "Neuer Ort:",
"cancel": "Abbrechen",
"changeLocation": "Ort ändern"
}
}
}

View File

@ -0,0 +1,6 @@
{
"welcome": "Willkommen bei Jan",
"description": "Um zu beginnen, musst Du entweder ein lokales KI-Modell herunterladen oder über einen API-Schlüssel eine Verbindung zu einem Cloud-Modell herstellen.",
"localModel": "Lokales Modell einrichten",
"remoteProvider": "Fernen Anbieter einrichten"
}

View File

@ -0,0 +1,28 @@
{
"title": "System Monitor",
"cpuUsage": "CPU Nutzung",
"model": "Modell",
"cores": "Kerne",
"architecture": "Architektur",
"currentUsage": "Aktuelle Nutzung",
"memoryUsage": "Speicher Nutzung",
"totalRam": "RAM Total",
"availableRam": "Verfügbarer RAM",
"usedRam": "Genutzter RAM",
"runningModels": "Laufende Modelle",
"noRunningModels": "Momentan laufen keine Modelle",
"provider": "Anbieter",
"uptime": "Betriebszeit",
"actions": "Aktionen",
"stop": "Stop",
"activeGpus": "Aktive GPUs",
"noGpus": "Keine GPUs detektiert",
"noActiveGpus": "Keine aktiven GPUs. Alle GPUs sind momentan deaktiviert.",
"vramUsage": "VRAM Nutzung",
"driverVersion": "Treiber Version:",
"computeCapability": "Rechenfähigkeit:",
"active": "Aktiv",
"performance": "Leistung",
"resources": "Ressourcen",
"refresh": "Aktualisieren"
}

View File

@ -0,0 +1,11 @@
{
"title": "Werkzeugaufruf angefordert",
"description": "Der Assistent möchte folgendes Werkzeug verwenden: <strong>{{toolName}}</strong>",
"securityNotice": "<strong>Sicherheitshinweis:</strong> Schädliche Werkzeuge oder Konversationsinhalte könnten den Assistenten möglicherweise zu schädlichen Aktionen verleiten. Überprüfe jeden Werkzeug-Aufruf sorgfältig, bevor Du ihn genehmigst.",
"deny": "Verweigern",
"allowOnce": "Einmal erlauben",
"alwaysAllow": "Immer erlauben",
"permissions": "Berechtigungen",
"approve": "Genehmigen",
"reject": "Ablehnen"
}

View File

@ -0,0 +1,10 @@
{
"toolApproval": {
"title": "Werkzeuggenehmigung erforderlich",
"description": "Der Assistent möchte das Werkzeug <strong>{{toolName}}</strong> verwenden.",
"securityNotice": "Dieses Werkzeug möchte eine Aktion ausführen. Bitte überprüfen und genehmigen oder Ablehnen.",
"deny": "Ablehnen",
"allowOnce": "Einmal erlauben",
"alwaysAllow": "Immer erlauben"
}
}

View File

@ -0,0 +1,10 @@
{
"newVersion": "Neue Version {{version}}",
"updateAvailable": "Update Verfügbar",
"nightlyBuild": "Nächtlicher Build",
"showReleaseNotes": "Zeige Release Notes",
"hideReleaseNotes": "Verstecke Release Notes",
"remindMeLater": "Erinnere Mich Später",
"downloading": "Lade herunter...",
"updateNow": "Jetzt Aktualisieren"
}

View File

@ -2,8 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { createFileRoute, useParams } from '@tanstack/react-router'
import { UIEventHandler } from 'react'
import debounce from 'lodash.debounce'
import cloneDeep from 'lodash.clonedeep'
import { cn } from '@/lib/utils'
import { ArrowDown } from 'lucide-react'
import { Play } from 'lucide-react'
import HeaderPage from '@/containers/HeaderPage'
import { useThreads } from '@/hooks/useThreads'
@ -18,7 +20,9 @@ import { useAppState } from '@/hooks/useAppState'
import DropdownAssistant from '@/containers/DropdownAssistant'
import { useAssistant } from '@/hooks/useAssistant'
import { useAppearance } from '@/hooks/useAppearance'
import { ContentType, ThreadMessage } from '@janhq/core'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { useChat } from '@/hooks/useChat'
import { useSmallScreen } from '@/hooks/useMediaQuery'
// as route.threadsDetail
@ -38,6 +42,7 @@ function ThreadDetail() {
const { setMessages } = useMessages()
const { streamingContent } = useAppState()
const { appMainViewBgColor, chatWidth } = useAppearance()
const { sendMessage } = useChat()
const isSmallScreen = useSmallScreen()
const { messages } = useMessages(
@ -180,6 +185,26 @@ function ThreadDetail() {
lastScrollTopRef.current = scrollTop
}
const updateMessage = (item: ThreadMessage, message: string) => {
const newMessages: ThreadMessage[] = messages.map((m) => {
if (m.id === item.id) {
const msg: ThreadMessage = cloneDeep(m)
msg.content = [
{
type: ContentType.Text,
text: {
value: message,
annotations: m.content[0].text?.annotations ?? [],
},
},
]
return msg
}
return m
})
setMessages(threadId, newMessages)
}
// Use a shorter debounce time for more responsive scrolling
const debouncedScroll = debounce(handleDOMScroll)
@ -193,10 +218,22 @@ function ThreadDetail() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// used when there is a sent/added user message and no assistant message (error or manual deletion)
const generateAIResponse = () => {
const latestUserMessage = messages[messages.length - 1]
if (latestUserMessage?.content?.[0]?.text?.value) {
sendMessage(latestUserMessage.content[0].text.value, false)
}
}
const threadModel = useMemo(() => thread?.model, [thread])
if (!messages || !threadModel) return null
const showScrollToBottomBtn = !isAtBottom && hasScrollbar
const showGenerateAIResponseBtn =
messages[messages.length - 1]?.role === 'user' && !streamingContent
return (
<div className="flex flex-col h-full">
<HeaderPage>
@ -243,6 +280,7 @@ function ThreadDetail() {
))
}
index={index}
updateMessage={updateMessage}
/>
</div>
)
@ -266,19 +304,31 @@ function ThreadDetail() {
appMainViewBgColor.a === 1
? 'from-main-view/20 bg-gradient-to-b to-main-view backdrop-blur'
: 'bg-transparent',
!isAtBottom && hasScrollbar && 'visibility-visible opacity-100'
(showScrollToBottomBtn || showGenerateAIResponseBtn) &&
'visibility-visible opacity-100'
)}
>
<div
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
onClick={() => {
scrollToBottom(true)
setIsUserScrolling(false)
}}
>
<p className="text-xs">{t('scrollToBottom')}</p>
<ArrowDown size={12} />
</div>
{showScrollToBottomBtn && (
<div
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
onClick={() => {
scrollToBottom(true)
setIsUserScrolling(false)
}}
>
<p className="text-xs">{t('scrollToBottom')}</p>
<ArrowDown size={12} />
</div>
)}
{showGenerateAIResponseBtn && (
<div
className="bg-main-view-fg/10 px-4 border border-main-view-fg/5 flex items-center justify-center rounded-xl gap-x-2 cursor-pointer pointer-events-auto"
onClick={generateAIResponse}
>
<p className="text-xs">{t('Generate AI Response')}</p>
<Play size={12} />
</div>
)}
</div>
<ChatInput model={threadModel} />
</div>

View File

@ -75,5 +75,13 @@ export default defineConfig(({ mode }) => {
ignored: ['**/src-tauri/**'],
},
},
test: {
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['json', 'lcov'],
reportsDirectory: '../coverage/vitest',
},
},
}
})

1366
yarn.lock

File diff suppressed because it is too large Load Diff