Merge remote-tracking branch 'origin/dev' into mobile/dev

# Conflicts:
#	src-tauri/.cargo/config.toml
#	src-tauri/Cargo.toml
#	src-tauri/src/lib.rs
#	web-app/src/containers/__tests__/ChatInput.test.tsx
#	web-app/src/routeTree.gen.ts
#	web-app/src/routes/index.tsx
#	web-app/src/routes/threads/$threadId.tsx
#	yarn.lock
This commit is contained in:
Vanalite 2025-09-22 11:24:20 +07:00
commit 003598204e
219 changed files with 5967 additions and 1876 deletions

View File

@ -14,6 +14,7 @@ jobs:
pull-requests: write pull-requests: write
env: env:
JAN_API_BASE: "https://api.jan.ai/jan/v1" JAN_API_BASE: "https://api.jan.ai/jan/v1"
GA_MEASUREMENT_ID: "G-YK53MX8M8M"
CLOUDFLARE_PROJECT_NAME: "jan-server-web" CLOUDFLARE_PROJECT_NAME: "jan-server-web"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -64,7 +64,7 @@ jobs:
- name: Install Tauri dependencies - name: Install Tauri dependencies
run: | run: |
sudo apt update 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 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 libayatana-appindicator3-dev
- name: Update app version - name: Update app version
run: | run: |

View File

@ -101,7 +101,7 @@ jobs:
- name: Install Tauri dependencies - name: Install Tauri dependencies
run: | run: |
sudo apt update 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 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 libayatana-appindicator3-dev
- name: Update app version base public_provider - name: Update app version base public_provider
run: | run: |

View File

@ -89,7 +89,6 @@ jobs:
- name: Build app - name: Build app
run: | run: |
rustup target add x86_64-apple-darwin
make build make build
env: env:
APP_PATH: '.' APP_PATH: '.'

View File

@ -92,31 +92,6 @@ jobs:
run: | run: |
cargo install ctoml cargo install ctoml
- name: Create bun and uv universal
run: |
mkdir -p ./src-tauri/resources/bin/
cd ./src-tauri/resources/bin/
curl -L -o bun-darwin-x64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-x64.zip
curl -L -o bun-darwin-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-aarch64.zip
unzip bun-darwin-x64.zip
unzip bun-darwin-aarch64.zip
lipo -create -output bun-universal-apple-darwin bun-darwin-x64/bun bun-darwin-aarch64/bun
cp -f bun-darwin-aarch64/bun bun-aarch64-apple-darwin
cp -f bun-darwin-x64/bun bun-x86_64-apple-darwin
cp -f bun-universal-apple-darwin bun
curl -L -o uv-x86_64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86_64-apple-darwin.tar.gz
curl -L -o uv-arm64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz
tar -xzf uv-x86_64.tar.gz
tar -xzf uv-arm64.tar.gz
mv uv-x86_64-apple-darwin uv-x86_64
mv uv-aarch64-apple-darwin uv-aarch64
lipo -create -output uv-universal-apple-darwin uv-x86_64/uv uv-aarch64/uv
cp -f uv-x86_64/uv uv-x86_64-apple-darwin
cp -f uv-aarch64/uv uv-aarch64-apple-darwin
cp -f uv-universal-apple-darwin uv
ls -la
- name: Update app version based on latest release tag with build number - name: Update app version based on latest release tag with build number
run: | run: |
echo "Version: ${{ inputs.new_version }}" echo "Version: ${{ inputs.new_version }}"
@ -167,7 +142,6 @@ jobs:
- name: Build app - name: Build app
run: | run: |
rustup target add x86_64-apple-darwin
make build make build
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -30,6 +30,17 @@ endif
yarn build:core yarn build:core
yarn build:extensions && yarn build:extensions-web yarn build:extensions && yarn build:extensions-web
# Install required Rust targets for macOS universal builds
install-rust-targets:
ifeq ($(shell uname -s),Darwin)
@echo "Detected macOS, installing universal build targets..."
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
@echo "Rust targets installed successfully!"
else
@echo "Not macOS; skipping Rust target installation."
endif
dev: install-and-build dev: install-and-build
yarn download:bin yarn download:bin
yarn download:lib yarn download:lib
@ -70,11 +81,12 @@ test: lint
cargo test --manifest-path src-tauri/utils/Cargo.toml cargo test --manifest-path src-tauri/utils/Cargo.toml
# Builds and publishes the app # Builds and publishes the app
build-and-publish: install-and-build build-and-publish: install-and-build install-rust-targets
yarn build yarn build
# Build # Build
build: install-and-build build: install-and-build install-rust-targets
yarn download:bin
yarn download:lib yarn download:lib
yarn build yarn build

View File

@ -126,16 +126,17 @@ export abstract class BaseExtension implements ExtensionType {
settings.forEach((setting) => { settings.forEach((setting) => {
// Keep setting value // Keep setting value
if (setting.controllerProps && Array.isArray(oldSettings)) if (setting.controllerProps && Array.isArray(oldSettings))
setting.controllerProps.value = oldSettings.find( setting.controllerProps.value =
(e: any) => e.key === setting.key oldSettings.find((e: any) => e.key === setting.key)?.controllerProps?.value ??
)?.controllerProps?.value setting.controllerProps.value
if ('options' in setting.controllerProps) if ('options' in setting.controllerProps)
setting.controllerProps.options = setting.controllerProps.options?.length setting.controllerProps.options = setting.controllerProps.options?.length
? setting.controllerProps.options ? setting.controllerProps.options
: oldSettings.find((e: any) => e.key === setting.key)?.controllerProps?.options : oldSettings.find((e: any) => e.key === setting.key)?.controllerProps?.options
if ('recommended' in setting.controllerProps) { if ('recommended' in setting.controllerProps) {
const oldRecommended = oldSettings.find((e: any) => e.key === setting.key)?.controllerProps?.recommended const oldRecommended = oldSettings.find((e: any) => e.key === setting.key)
if (oldRecommended !== undefined && oldRecommended !== "") { ?.controllerProps?.recommended
if (oldRecommended !== undefined && oldRecommended !== '') {
setting.controllerProps.recommended = oldRecommended setting.controllerProps.recommended = oldRecommended
} }
} }

View File

@ -683,3 +683,17 @@ docs/guides/fine-tuning/what-models-can-be-fine-tuned/ /docs 302
/docs/server-installation/aws /docs/desktop 302 /docs/server-installation/aws /docs/desktop 302
/docs/server-installation/gcp /docs/desktop 302 /docs/server-installation/gcp /docs/desktop 302
/docs/server-installation/azure /docs/desktop 302 /docs/server-installation/azure /docs/desktop 302
/about /docs 302
/api-server /docs/api-server 302
/cdn-cgi/l/email-protection 302
/docs/built-in/tensorrt-llm 302
/docs/desktop/beta /docs 302
/docs/docs/data-folder /docs/data-folder 302
/docs/docs/desktop/linux /docs/desktop/linux 302
/docs/docs/troubleshooting /docs/troubleshooting 302
/docs/local-engines/llama-cpp 302
/docs/models/model-parameters 302
/mcp /docs/mcp 302
/quickstart /docs/quickstart 302
/server-examples/continue-dev /docs/server-examples/continue-dev 302

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 MiB

View File

@ -3,12 +3,12 @@ title: "Faster inference across: Mac, Windows, Linux, and GPUs"
version: 0.4.3 version: 0.4.3
description: "" description: ""
date: 2023-12-21 date: 2023-12-21
ogImage: "/assets/images/changelog/Jan_v0.4.3.gif" ogImage: "https://catalog.jan.ai/docs/Jan_v0.4.3.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Faster inference across= Mac, Windows, Linux, and GPUs" date= "2023-12-21" ogImage= "/assets/images/changelog/Jan_v0.4.3.gif" /> <ChangelogHeader title= "Faster inference across= Mac, Windows, Linux, and GPUs" date= "2023-12-21" ogImage= "https://catalog.jan.ai/docs/Jan_v0.4.3.gif" />
### Highlights 🎉 ### Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "Local API server"
version: 0.4.5 version: 0.4.5
description: "" description: ""
date: 2024-01-29 date: 2024-01-29
ogImage: "/assets/images/changelog/Jan_v0.4.5.gif" ogImage: "https://catalog.jan.ai/docs/Jan_v0.4.5.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Local API server" date= "2024-01-29" ogImage= "/assets/images/changelog/Jan_v0.4.5.gif" /> <ChangelogHeader title= "Local API server" date= "2024-01-29" ogImage= "https://catalog.jan.ai/docs/Jan_v0.4.5.gif" />
### Highlights 🎉 ### Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "Jan Data Folder"
version: 0.4.6 version: 0.4.6
description: "" description: ""
date: 2024-02-05 date: 2024-02-05
ogImage: "/assets/images/changelog/jan_product_update_feature.gif" ogImage: "https://catalog.jan.ai/docs/jan_product_update_feature.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan Data Folder" date= "2024-02-05" ogImage= "/assets/images/changelog/jan_product_update_feature.gif" /> <ChangelogHeader title= "Jan Data Folder" date= "2024-02-05" ogImage= "https://catalog.jan.ai/docs/jan_product_update_feature.gif" />
### Highlights 🎉 ### Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "New UI & Codestral Support"
version: 0.5.0 version: 0.5.0
description: "Revamped Jan's UI to make it clearer and more user-friendly" description: "Revamped Jan's UI to make it clearer and more user-friendly"
date: 2024-06-03 date: 2024-06-03
ogImage: "/assets/images/changelog/jan_v0.5.0.gif" ogImage: "https://catalog.jan.ai/docs/jan_v0.5.0.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "New UI & Codestral Support" date= "2024-06-03" ogImage= "/assets/images/changelog/jan_v0.5.0.gif" /> <ChangelogHeader title= "New UI & Codestral Support" date= "2024-06-03" ogImage= "https://catalog.jan.ai/docs/jan_v0.5.0.gif" />
Revamped Jan's UI to make it clearer and more user-friendly. Revamped Jan's UI to make it clearer and more user-friendly.

View File

@ -3,12 +3,12 @@ title: "Groq API Integration"
version: 0.4.10 version: 0.4.10
description: "" description: ""
date: 2024-04-02 date: 2024-04-02
ogImage: "/assets/images/changelog/jan_update_groq.gif" ogImage: "https://catalog.jan.ai/docs/jan_update_groq.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Groq API Integration" date= "2024-04-02" ogImage= "/assets/images/changelog/jan_update_groq.gif" /> <ChangelogHeader title= "Groq API Integration" date= "2024-04-02" ogImage= "https://catalog.jan.ai/docs/jan_update_groq.gif" />
### Highlights 🎉 ### Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "New Mistral Extension"
version: 0.4.11 version: 0.4.11
description: "Jan has a new Mistral Extension letting you chat with larger Mistral models via Mistral API" description: "Jan has a new Mistral Extension letting you chat with larger Mistral models via Mistral API"
date: 2024-04-15 date: 2024-04-15
ogImage: "/assets/images/changelog/jan_mistral_api.gif" ogImage: "https://catalog.jan.ai/docs/jan_mistral_api.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "New Mistral Extension" date= '2024-04-15' ogImage= "/assets/images/changelog/jan_mistral_api.gif"/> <ChangelogHeader title= "New Mistral Extension" date= '2024-04-15' ogImage= "https://catalog.jan.ai/docs/jan_mistral_api.gif"/>
### Highlights 🎉 ### Highlights 🎉

View File

@ -3,29 +3,29 @@ title: 'Jan now supports Llama3 and Command R+'
version: 0.4.12 version: 0.4.12
description: "Jan has added compatibility with Llama3 & Command R+" description: "Jan has added compatibility with Llama3 & Command R+"
date: 2024-04-25 date: 2024-04-25
ogImage: "/assets/images/changelog/jan_llama3.gif" ogImage: "https://catalog.jan.ai/docs/jan_llama3.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= 'Jan now supports Llama3 and Command R+' date= "2024-04-25" ogImage= '/assets/images/changelog/jan_llama3.gif' /> <ChangelogHeader title= 'Jan now supports Llama3 and Command R+' date= "2024-04-25" ogImage= 'https://catalog.jan.ai/docs/jan_llama3.gif' />
Jan has added compatibility with Metas open-source language model, `Llama3`, through the integration with `llamacpp` (thanks to [@ggerganov](https://github.com/ggerganov)). Jan has added compatibility with Metas open-source language model, `Llama3`, through the integration with `llamacpp` (thanks to [@ggerganov](https://github.com/ggerganov)).
Additionally, `Command R+` is now supported. It is the first open-source model to surpass GPT-4 on the [LMSys leaderboard](https://chat.lmsys.org/?leaderboard). Additionally, `Command R+` is now supported. It is the first open-source model to surpass GPT-4 on the [LMSys leaderboard](https://chat.lmsys.org/?leaderboard).
![Commandr](/assets/images/changelog/jan_cohere_commandr.gif) ![Commandr](https://catalog.jan.ai/docs/jan_cohere_commandr.gif)
## Import Huggingface models directly ## Import Huggingface models directly
Users can now import Huggingface models into Jan. Simply copy the models link from Huggingface and paste it into the search bar on Jan Hub. Users can now import Huggingface models into Jan. Simply copy the models link from Huggingface and paste it into the search bar on Jan Hub.
![HugginFace](/assets/images/changelog/jan_hugging_face.gif) ![HugginFace](https://catalog.jan.ai/docs/jan_hugging_face.gif)
## Enhanced LaTeX understanding ## Enhanced LaTeX understanding
Jan now understands LaTeX, allowing users to process and understand complex mathematical expressions more effectively. Jan now understands LaTeX, allowing users to process and understand complex mathematical expressions more effectively.
![Latex](/assets/images/changelog/jan_update_latex.gif) ![Latex](https://catalog.jan.ai/docs/jan_update_latex.gif)
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.12). For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.12).

View File

@ -3,12 +3,12 @@ title: "Jan now supports more GGUF models"
version: 0.4.13 version: 0.4.13
description: "We rebased to llamacpp b2865." description: "We rebased to llamacpp b2865."
date: 2024-05-20 date: 2024-05-20
ogImage: "/assets/images/changelog/jan_v0.4.13_update.gif" ogImage: "https://catalog.jan.ai/docs/jan_v0.4.13_update.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan now supports more GGUF models" date= '2024-05-20' ogImage= "/assets/images/changelog/jan_v0.4.13_update.gif" /> <ChangelogHeader title= "Jan now supports more GGUF models" date= '2024-05-20' ogImage= "https://catalog.jan.ai/docs/jan_v0.4.13_update.gif" />
With this release, more GGUF models should work now! We rebased to llamacpp b2865! With this release, more GGUF models should work now! We rebased to llamacpp b2865!
@ -20,12 +20,12 @@ Jan now supports `Anthropic API` models `Command R` and `Command R+`, along with
Jan supports `Martian`, a dynamic LLM router that routes between multiple models and allows users to reduce costs by 20% to 97%. Jan also supports `OpenRouter`, helping users select the best model for each query. Jan supports `Martian`, a dynamic LLM router that routes between multiple models and allows users to reduce costs by 20% to 97%. Jan also supports `OpenRouter`, helping users select the best model for each query.
![New_Integrations](/assets/images/changelog/jan_v0.4.13_update.gif) ![New_Integrations](https://catalog.jan.ai/docs/jan_v0.4.13_update.gif)
## GPT-4o Access ## GPT-4o Access
Users can now connect to OpenAI's new model GPT-4o. Users can now connect to OpenAI's new model GPT-4o.
![GPT4o](/assets/images/changelog/jan_v0_4_13_openai_gpt4o.gif) ![GPT4o](https://catalog.jan.ai/docs/jan_v0_4_13_openai_gpt4o.gif)
For more details, see the [GitHub release notes.](https://github.com/menloresearch/jan/releases/tag/v0.4.13) For more details, see the [GitHub release notes.](https://github.com/menloresearch/jan/releases/tag/v0.4.13)

View File

@ -3,12 +3,12 @@ title: "Jan now compatible with Aya 23 8B & 35B and Phi-3-Medium"
version: 0.4.14 version: 0.4.14
description: "Jan now supports Cohere's Aya 23 8B & 35B and Microsoft's Phi-3-Medium." description: "Jan now supports Cohere's Aya 23 8B & 35B and Microsoft's Phi-3-Medium."
date: 2024-05-28 date: 2024-05-28
ogImage: "/assets/images/changelog/jan-v0-4-14-phi3.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0-4-14-phi3.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan now compatible with Aya 23 8B & 35B and Phi-3-Medium" date= "2024-05-28" ogImage= "/assets/images/changelog/jan-v0-4-14-phi3.gif" /> <ChangelogHeader title= "Jan now compatible with Aya 23 8B & 35B and Phi-3-Medium" date= "2024-05-28" ogImage= "https://catalog.jan.ai/docs/jan-v0-4-14-phi3.gif" />
Jan now supports `Cohere`'s new models `Aya 23 (8B)` & `Aya 23 (35B)` and `Microsoft`'s `Phi-3-Medium`. Jan now supports `Cohere`'s new models `Aya 23 (8B)` & `Aya 23 (35B)` and `Microsoft`'s `Phi-3-Medium`.

View File

@ -3,12 +3,12 @@ title: "Jan supports NVIDIA NIM"
version: 0.5.1 version: 0.5.1
description: "Jan has integrated NVIDIA NIM and supports Qwen 2 7B" description: "Jan has integrated NVIDIA NIM and supports Qwen 2 7B"
date: 2024-06-21 date: 2024-06-21
ogImage: "/assets/images/changelog/jan_nvidia_nim_support.gif" ogImage: "https://catalog.jan.ai/docs/jan_nvidia_nim_support.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan supports NVIDIA NIM" date= '2024-06-21' ogImage= "/assets/images/changelog/jan_nvidia_nim_support.gif"/> <ChangelogHeader title= "Jan supports NVIDIA NIM" date= '2024-06-21' ogImage= "https://catalog.jan.ai/docs/jan_nvidia_nim_support.gif"/>
## NVIDIA NIM ## NVIDIA NIM

View File

@ -3,12 +3,12 @@ title: "Jan supports Claude 3.5 Sonnet"
version: 0.5.2 version: 0.5.2
description: "You can run Claude 3.5 Sonnet in Jan" description: "You can run Claude 3.5 Sonnet in Jan"
date: 2024-07-15 date: 2024-07-15
ogImage: "/assets/images/changelog/jan_supports_claude_3_5.gif" ogImage: "https://catalog.jan.ai/docs/jan_supports_claude_3_5.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan supports Claude 3.5 Sonnet" date= "2024-07-15" ogImage= "/assets/images/changelog/jan_supports_claude_3_5.gif" /> <ChangelogHeader title= "Jan supports Claude 3.5 Sonnet" date= "2024-07-15" ogImage= "https://catalog.jan.ai/docs/jan_supports_claude_3_5.gif" />
## Claude 3.5 Sonnet ## Claude 3.5 Sonnet

View File

@ -3,12 +3,12 @@ title: "v0.5.3 is out with stability improvements!"
version: 0.5.3 version: 0.5.3
description: "You can run Llama 3.1 and Gemma 2 in Jan" description: "You can run Llama 3.1 and Gemma 2 in Jan"
date: 2024-08-29 date: 2024-08-29
ogImage: "/assets/images/changelog/janv0.5.3.gif" ogImage: "https://catalog.jan.ai/docs/janv0.5.3.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "v0.5.3 is out with stability improvements!" date="2024-09-01" ogImage= "/assets/images/changelog/janv0.5.3.gif" /> <ChangelogHeader title= "v0.5.3 is out with stability improvements!" date="2024-09-01" ogImage= "https://catalog.jan.ai/docs/janv0.5.3.gif" />
## Llama 3.1 and Gemma 2 Support ## Llama 3.1 and Gemma 2 Support

View File

@ -3,12 +3,12 @@ title: "Jan has Stable, Beta and Nightly versions"
version: 0.5.7 version: 0.5.7
description: "This release is mostly focused on bug fixes." description: "This release is mostly focused on bug fixes."
date: 2024-10-24 date: 2024-10-24
ogImage: "/assets/images/changelog/jan-v0.5.7.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.7.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan is faster now" date="2024-09-01" ogImage= "/assets/images/changelog/jan-v0.5.7.gif" /> <ChangelogHeader title= "Jan is faster now" date="2024-09-01" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.7.gif" />
Highlights 🎉 Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "Model downloads & running issues fixed"
version: 0.5.9 version: 0.5.9
description: "Jan v0.5.9 is here: fixing what needed fixing." description: "Jan v0.5.9 is here: fixing what needed fixing."
date: 2024-11-22 date: 2024-11-22
ogImage: "/assets/images/changelog/jan-v0.5.9.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.9.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan v0.5.9 is here:fixing what needed fixing" date="2024-11-22" ogImage= "/assets/images/changelog/jan-v0.5.9.gif" /> <ChangelogHeader title= "Jan v0.5.9 is here:fixing what needed fixing" date="2024-11-22" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.9.gif" />
Jan v0.5.9 is here: fixing what needed fixing Jan v0.5.9 is here: fixing what needed fixing

View File

@ -3,12 +3,12 @@ title: "Jan supports Qwen2.5-Coder 14B & 32B"
version: 0.5.8 version: 0.5.8
description: "Jan v0.5.8 is out: Jan supports Qwen2.5-Coder 14B & 32B through Cortex" description: "Jan v0.5.8 is out: Jan supports Qwen2.5-Coder 14B & 32B through Cortex"
date: 2024-11-14 date: 2024-11-14
ogImage: "/assets/images/changelog/jan-v0.5.8.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.8.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan supports Qwen2.5-Coder 14B & 32B" date="2024-11-14" ogImage= "/assets/images/changelog/jan-v0.5.7.gif" /> <ChangelogHeader title= "Jan supports Qwen2.5-Coder 14B & 32B" date="2024-11-14" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.7.gif" />
Jan v0.5.8 is out: Jan supports Qwen2.5-Coder 14B & 32B through Cortex Jan v0.5.8 is out: Jan supports Qwen2.5-Coder 14B & 32B through Cortex

View File

@ -3,12 +3,12 @@ title: "Jan v0.5.10 is live"
version: 0.5.10 version: 0.5.10
description: "Jan is faster, smoother, and more reliable." description: "Jan is faster, smoother, and more reliable."
date: 2024-12-03 date: 2024-12-03
ogImage: "/assets/images/changelog/jan-v0.5.10.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.10.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan v0.5.10: Jan is faster, smoother, and more reliable." date="2024-12-03" ogImage= "/assets/images/changelog/jan-v0.5.10.gif" /> <ChangelogHeader title= "Jan v0.5.10: Jan is faster, smoother, and more reliable." date="2024-12-03" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.10.gif" />
Jan v0.5.10 is live: Jan is faster, smoother, and more reliable. Jan v0.5.10 is live: Jan is faster, smoother, and more reliable.

View File

@ -3,12 +3,12 @@ title: "Jan v0.5.11 is here!"
version: 0.5.11 version: 0.5.11
description: "Critical issues fixed, Mac installation updated." description: "Critical issues fixed, Mac installation updated."
date: 2024-12-05 date: 2024-12-05
ogImage: "/assets/images/changelog/jan-v0.5.11.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.11.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan v0.5.11: Jan is faster, smoother, and more reliable." date="2024-12-05" ogImage= "/assets/images/changelog/jan-v0.5.11.gif" /> <ChangelogHeader title= "Jan v0.5.11: Jan is faster, smoother, and more reliable." date="2024-12-05" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.11.gif" />
Jan v0.5.11 is here - critical issues fixed, Mac installation updated. Jan v0.5.11 is here - critical issues fixed, Mac installation updated.

View File

@ -3,12 +3,12 @@ title: "Jan gives you full control over your privacy"
version: 0.5.12 version: 0.5.12
description: "Improved Privacy settings to give full control over analytics" description: "Improved Privacy settings to give full control over analytics"
date: 2024-12-30 date: 2024-12-30
ogImage: "/assets/images/changelog/jan-v0.5.12.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0.5.12.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title= "Jan v0.5.12: Improved Privacy settings to give full control over analytics." date="2024-12-30" ogImage= "/assets/images/changelog/jan-v0.5.12.gif" /> <ChangelogHeader title= "Jan v0.5.12: Improved Privacy settings to give full control over analytics." date="2024-12-30" ogImage= "https://catalog.jan.ai/docs/jan-v0.5.12.gif" />
Jan v0.5.11 is here - critical issues fixed, Mac installation updated. Jan v0.5.11 is here - critical issues fixed, Mac installation updated.

View File

@ -3,12 +3,12 @@ title: "Qwen3 support is now more reliable."
version: 0.5.17 version: 0.5.17
description: "Jan v0.5.17 is out: Qwen3 support is now more reliable" description: "Jan v0.5.17 is out: Qwen3 support is now more reliable"
date: 2025-05-14 date: 2025-05-14
ogImage: "/assets/images/changelog/jan-v0-5-17-gemm3-patch.gif" ogImage: "https://catalog.jan.ai/docs/jan-v0-5-17-gemm3-patch.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title="Qwen3 support is now more reliable" date="2025-05-14" ogImage="/assets/images/changelog/jan-v0-5-17-gemm3-patch.gif" /> <ChangelogHeader title="Qwen3 support is now more reliable" date="2025-05-14" ogImage="https://catalog.jan.ai/docs/jan-v0-5-17-gemm3-patch.gif" />
👋 Jan v0.5.17 is out: Qwen3 support is now more reliable 👋 Jan v0.5.17 is out: Qwen3 support is now more reliable

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.3 brings new features and models!"
version: 0.6.3 version: 0.6.3
description: "Unlocking MCP for everyone and bringing our latest model to Jan!" description: "Unlocking MCP for everyone and bringing our latest model to Jan!"
date: 2025-06-26 date: 2025-06-26
ogImage: "/assets/images/changelog/jn128.gif" ogImage: "https://catalog.jan.ai/docs/jn128.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title="Jan v0.6.3 brings with it MCP and our latest model!" date="2025-06-26" ogImage="/assets/images/changelog/jn128.gif" /> <ChangelogHeader title="Jan v0.6.3 brings with it MCP and our latest model!" date="2025-06-26" ogImage="https://catalog.jan.ai/docs/jn128.gif" />
## Highlights 🎉 ## Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.5 brings responsive UI and MCP examples!"
version: 0.6.5 version: 0.6.5
description: "New MCP examples, updated pages, and bug fixes!" description: "New MCP examples, updated pages, and bug fixes!"
date: 2025-07-17 date: 2025-07-17
ogImage: "/assets/images/changelog/release_v0_6_5.gif" ogImage: "https://catalog.jan.ai/docs/release_v0_6_5.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title="Jan v0.6.5 brings responsive UI and MCP examples!" date="2025-07-17" ogImage="/assets/images/changelog/release_v0_6_5.gif" /> <ChangelogHeader title="Jan v0.6.5 brings responsive UI and MCP examples!" date="2025-07-17" ogImage="https://catalog.jan.ai/docs/release_v0_6_5.gif" />
## Highlights 🎉 ## Highlights 🎉

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.6: Enhanced llama.cpp integration and smarter model management"
version: 0.6.6 version: 0.6.6
description: "Major llama.cpp improvements, Hugging Face provider support, and refined MCP experience" description: "Major llama.cpp improvements, Hugging Face provider support, and refined MCP experience"
date: 2025-07-31 date: 2025-07-31
ogImage: "/assets/images/changelog/changelog0.6.6.gif" ogImage: "https://catalog.jan.ai/docs/changelog0.6.6.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
<ChangelogHeader title="Jan v0.6.6: Enhanced llama.cpp integration and smarter model management" date="2025-01-31" ogImage="/assets/images/changelog/changelog0.6.6.gif" /> <ChangelogHeader title="Jan v0.6.6: Enhanced llama.cpp integration and smarter model management" date="2025-01-31" ogImage="https://catalog.jan.ai/docs/changelog0.6.6.gif" />
## Highlights 🎉 ## Highlights 🎉

View File

@ -3,13 +3,13 @@ title: "Jan v0.6.8: Engine fixes, new MCP tutorials, and cleaner docs"
version: 0.6.8 version: 0.6.8
description: "Llama.cpp stability upgrades, Linear/Todoist MCP tutorials, new model pages (Lucy, Janv1), and docs reorganization" description: "Llama.cpp stability upgrades, Linear/Todoist MCP tutorials, new model pages (Lucy, Janv1), and docs reorganization"
date: 2025-08-14 date: 2025-08-14
ogImage: "/assets/images/changelog/mcplinear2.gif" ogImage: "https://catalog.jan.ai/docs/mcplinear2.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
import { Callout } from 'nextra/components' import { Callout } from 'nextra/components'
<ChangelogHeader title="Jan v0.6.8: Engine fixes, new MCP tutorials, and cleaner docs" date="2025-08-14" ogImage="/assets/images/changelog/mcplinear2.gif" /> <ChangelogHeader title="Jan v0.6.8: Engine fixes, new MCP tutorials, and cleaner docs" date="2025-08-14" ogImage="https://catalog.jan.ai/docs/mcplinear2.gif" />
## Highlights 🎉 ## Highlights 🎉

View File

@ -3,13 +3,13 @@ title: "Jan v0.6.9: Image support, stable MCP, and powerful model tools"
version: 0.6.9 version: 0.6.9
description: "Major multimodal support with image uploads, MCP out of experimental, auto-detect model capabilities, and enhanced tool calling" description: "Major multimodal support with image uploads, MCP out of experimental, auto-detect model capabilities, and enhanced tool calling"
date: 2025-08-28 date: 2025-08-28
ogImage: "/assets/images/changelog/jan-images.gif" ogImage: "https://catalog.jan.ai/docs/jan-images.gif"
--- ---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader" import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
import { Callout } from 'nextra/components' import { Callout } from 'nextra/components'
<ChangelogHeader title="Jan v0.6.9: Image support, stable MCP, and powerful model tools" date="2025-08-28" ogImage="/assets/images/changelog/jan-images.gif" /> <ChangelogHeader title="Jan v0.6.9: Image support, stable MCP, and powerful model tools" date="2025-08-28" ogImage="https://catalog.jan.ai/docs/jan-images.gif" />
## Highlights 🎉 ## Highlights 🎉

View File

@ -0,0 +1,48 @@
---
title: "Jan v0.6.10: Auto Optimize, custom backends, and vision model imports"
version: 0.6.10
description: "New experimental Auto Optimize feature, custom llama.cpp backend support, vision model imports, and critical bug fixes"
date: 2025-09-18
ogImage: "/assets/images/changelog/jan-v0.6.10-auto-optimize.gif"
---
import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
import { Callout } from 'nextra/components'
<ChangelogHeader title="Jan v0.6.10: Auto Optimize, custom backends, and vision model imports" date="2025-09-18" ogImage="/assets/images/changelog/jan-v0.6.10-auto-optimize.gif" />
## Highlights 🎉
- **Auto Optimize**: One-click hardware-aware performance tuning for llama.cpp.
- **Custom Backend Support**: Import and manage your preferred llama.cpp versions.
- **Import Vision Models**: Seamlessly import and use vision-capable models.
### 🚀 Auto Optimize (Experimental)
**Intelligent performance tuning** — Jan can now apply the best llama.cpp settings for your specific hardware:
- **Hardware analysis**: Automatically detects your CPU, GPU, and memory configuration
- **One-click optimization**: Applies optimal parameters with a single click in model settings
<Callout type="info">
Auto Optimize is currently experimental and will be refined based on user feedback. It analyzes your system specs and applies proven configurations for optimal llama.cpp performance.
</Callout>
### 👁️ Vision Model Imports
<img src="/assets/images/changelog/jan-import-vlm-model.gif" alt="Vision Model Import Demo" width="600" />
**Enhanced multimodal support** — Import and use vision models seamlessly:
- **Direct vision model import**: Import vision-capable models from any source
- **Improved compatibility**: Better handling of multimodal model formats
### 🔧 Custom Backend Support
**Import your preferred llama.cpp version** — Full control over your AI backend:
- **Custom llama.cpp versions**: Import and use any llama.cpp build you prefer
- **Version flexibility**: Use bleeding-edge builds or stable releases
- **Backup CDN**: New CDN fallback when GitHub downloads fail
- **User confirmation**: Prompts before auto-updating llama.cpp
Update your Jan or [download the latest](https://jan.ai/).
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.10).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

View File

@ -87,7 +87,7 @@ Jan-Nano-128k has been rigorously evaluated on the SimpleQA benchmark using our
### Demo ### Demo
<video width="100%" controls> <video width="100%" controls>
<source src="/assets/videos/jan-nano-demo.mp4" type="video/mp4" /> <source src="https://catalog.jan.ai/docs/jan-nano-demo.mp4" type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>

View File

@ -20,7 +20,7 @@ import { Callout } from 'nextra/components'
# Jan Nano # Jan Nano
![Jan Nano](../_assets/jan-nano0.png) ![Jan Nano](https://catalog.jan.ai/docs/jan-nano0.png)
## Why Jan Nano? ## Why Jan Nano?
@ -81,7 +81,7 @@ Add the serper MCP to Jan via the **Settings** > **MCP Servers** tab.
**Step 6** **Step 6**
Open up a new chat and ask Jan-Nano to search the web for you. Open up a new chat and ask Jan-Nano to search the web for you.
![Jan Nano](../_assets/jan-nano-demo.gif) ![Jan Nano](https://catalog.jan.ai/docs/jan-nano-demo.gif)
## Queries to Try ## Queries to Try

View File

@ -58,7 +58,7 @@ These benchmarks (EQBench, CreativeWriting, and IFBench) measure the model's abi
### Demo ### Demo
![Jan-v1 Demo](../_assets/jan_v1_demo.gif) ![Jan-v1 Demo](https://catalog.jan.ai/docs/jan_v1_demo.gif)
### Deployment Options ### Deployment Options

View File

@ -55,7 +55,7 @@ To use Lucy's web search capabilities, you'll need a Serper API key. Get one at
### Demo ### Demo
![Lucy Demo](../_assets/lucy_demo.gif) ![Lucy Demo](https://catalog.jan.ai/docs/lucy_demo.gif)
### Deployment Options ### Deployment Options

View File

@ -204,7 +204,7 @@ Generate synthetic data with numpy, move it to a pandas dataframe and create a p
Watch the complete output unfold: Watch the complete output unfold:
<video width="100%" controls> <video width="100%" controls>
<source src="/assets/videos/mcpjupyter.mp4" type="video/mp4" /> <source src="https://catalog.jan.ai/docs/mcpjupyter.mp4" type="video/mp4" />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>

View File

@ -98,7 +98,7 @@ When you first use Canva tools:
- Canva authentication page appears in your default browser - Canva authentication page appears in your default browser
- Log in with your Canva account - Log in with your Canva account
![Canva authentication page](../../_assets/canva2.png) ![Canva authentication page](https://catalog.jan.ai/docs/canva2.png)
2. **Team Selection & Permissions** 2. **Team Selection & Permissions**
- Select your team (if you have multiple) - Select your team (if you have multiple)

View File

@ -128,7 +128,7 @@ You should see all Linear tools in the chat interface:
Watch AI transform mundane tasks into epic narratives: Watch AI transform mundane tasks into epic narratives:
![Linear MCP creating Shakespearean war epic tasks](../../_assets/mcplinear2.gif) ![Linear MCP creating Shakespearean war epic tasks](https://catalog.jan.ai/docs/mcplinear2.gif)
## Creative Examples ## Creative Examples

View File

@ -101,7 +101,7 @@ You should see the Todoist tools in the tools panel:
Now you can manage your todo list through natural conversation: Now you can manage your todo list through natural conversation:
![Todoist MCP in action](../../_assets/mcptodoist_extreme.gif) ![Todoist MCP in action](https://catalog.jan.ai/docs/mcptodoist_extreme.gif)
## Example Prompts ## Example Prompts

View File

@ -103,7 +103,7 @@ Note: `ngl` is the abbreviation of `Number of GPU Layers` with the range from `0
### NVIDIA GeForce RTX 4090 GPU ### NVIDIA GeForce RTX 4090 GPU
![image](./_assets/4090s.png) ![image](https://catalog.jan.ai/docs/4090s.png)
*Jan is built on this Dual-4090 workstation, which recently got upgraded to a nice case* *Jan is built on this Dual-4090 workstation, which recently got upgraded to a nice case*
![image](./_assets/og-4090s.webp) ![image](./_assets/og-4090s.webp)

View File

@ -13,7 +13,7 @@ date: 2025-08-22
This cookbook will transform your Jan-V1 from a basic Q&A tool into a comprehensive research assistant. By the end of this guide, you'll have a custom-configured model that generates detailed reports with proper citations instead of surface-level answers. This cookbook will transform your Jan-V1 from a basic Q&A tool into a comprehensive research assistant. By the end of this guide, you'll have a custom-configured model that generates detailed reports with proper citations instead of surface-level answers.
![Jan-V1 research comparison](./_assets/deep_research_compare_jan.gif) ![Jan-V1 research comparison](https://catalog.jan.ai/docs/deep_research_compare_jan.gif)
## Key Points ## Key Points

View File

@ -9,6 +9,15 @@ export { default as ConversationalExtensionWeb } from './conversational-web'
export { default as JanProviderWeb } from './jan-provider-web' export { default as JanProviderWeb } from './jan-provider-web'
export { default as MCPExtensionWeb } from './mcp-web' export { default as MCPExtensionWeb } from './mcp-web'
// Re-export auth functionality
export {
JanAuthService,
getSharedAuthService,
AUTH_STORAGE_KEYS,
AUTH_EVENTS,
AUTH_BROADCAST_CHANNEL,
} from './shared/auth'
// Re-export types // Re-export types
export type { export type {
WebExtensionRegistry, WebExtensionRegistry,
@ -17,9 +26,18 @@ export type {
WebExtensionLoader, WebExtensionLoader,
ConversationalWebModule, ConversationalWebModule,
JanProviderWebModule, JanProviderWebModule,
MCPWebModule MCPWebModule,
} from './types' } from './types'
// Re-export auth types
export type {
User,
AuthTokens,
AuthProvider,
AuthProviderRegistry,
ProviderType,
} from './shared/auth'
// Extension registry for dynamic loading // Extension registry for dynamic loading
export const WEB_EXTENSIONS: WebExtensionRegistry = { export const WEB_EXTENSIONS: WebExtensionRegistry = {
'conversational-web': () => import('./conversational-web'), 'conversational-web': () => import('./conversational-web'),

View File

@ -1,219 +0,0 @@
/**
* Shared Authentication Service
* Handles guest login and token refresh for Jan API
*/
// JAN_API_BASE is defined in vite.config.ts
declare const JAN_API_BASE: string
export interface AuthTokens {
access_token: string
expires_in: number
}
export interface AuthResponse {
access_token: string
expires_in: number
}
const AUTH_STORAGE_KEY = 'jan_auth_tokens'
const TOKEN_EXPIRY_BUFFER = 60 * 1000 // 1 minute buffer before actual expiry
export class JanAuthService {
private tokens: AuthTokens | null = null
private tokenExpiryTime: number = 0
constructor() {
this.loadTokensFromStorage()
}
private loadTokensFromStorage(): void {
try {
const storedTokens = localStorage.getItem(AUTH_STORAGE_KEY)
if (storedTokens) {
const parsed = JSON.parse(storedTokens)
this.tokens = parsed.tokens
this.tokenExpiryTime = parsed.expiryTime || 0
}
} catch (error) {
console.warn('Failed to load tokens from storage:', error)
this.clearTokens()
}
}
private saveTokensToStorage(): void {
if (this.tokens) {
try {
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify({
tokens: this.tokens,
expiryTime: this.tokenExpiryTime
}))
} catch (error) {
console.error('Failed to save tokens to storage:', error)
}
}
}
private clearTokens(): void {
this.tokens = null
this.tokenExpiryTime = 0
localStorage.removeItem(AUTH_STORAGE_KEY)
}
private isTokenExpired(): boolean {
return Date.now() > (this.tokenExpiryTime - TOKEN_EXPIRY_BUFFER)
}
private calculateExpiryTime(expiresIn: number): number {
return Date.now() + (expiresIn * 1000)
}
private async guestLogin(): Promise<AuthTokens> {
try {
const response = await fetch(`${JAN_API_BASE}/auth/guest-login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include', // Include cookies for session management
})
if (!response.ok) {
throw new Error(`Guest login failed: ${response.status} ${response.statusText}`)
}
const data = await response.json()
// API response is wrapped in result object
const authResponse = data.result || data
// Guest login returns only access_token and expires_in
const tokens: AuthTokens = {
access_token: authResponse.access_token,
expires_in: authResponse.expires_in
}
this.tokens = tokens
this.tokenExpiryTime = this.calculateExpiryTime(tokens.expires_in)
this.saveTokensToStorage()
return tokens
} catch (error) {
console.error('Guest login failed:', error)
throw error
}
}
private async refreshToken(): Promise<AuthTokens> {
try {
const response = await fetch(`${JAN_API_BASE}/auth/refresh-token`, {
method: 'GET',
credentials: 'include', // Cookies will include the refresh token
})
if (!response.ok) {
if (response.status === 401) {
// Refresh token is invalid, clear tokens and do guest login
this.clearTokens()
return this.guestLogin()
}
throw new Error(`Token refresh failed: ${response.status} ${response.statusText}`)
}
const data = await response.json()
// API response is wrapped in result object
const authResponse = data.result || data
// Refresh endpoint returns only access_token and expires_in
const tokens: AuthTokens = {
access_token: authResponse.access_token,
expires_in: authResponse.expires_in
}
this.tokens = tokens
this.tokenExpiryTime = this.calculateExpiryTime(tokens.expires_in)
this.saveTokensToStorage()
return tokens
} catch (error) {
console.error('Token refresh failed:', error)
// If refresh fails, fall back to guest login
this.clearTokens()
return this.guestLogin()
}
}
async getValidAccessToken(): Promise<string> {
// If no tokens exist, do guest login
if (!this.tokens) {
const tokens = await this.guestLogin()
return tokens.access_token
}
// If token is expired or about to expire, refresh it
if (this.isTokenExpired()) {
const tokens = await this.refreshToken()
return tokens.access_token
}
// Return existing valid token
return this.tokens.access_token
}
async getAuthHeader(): Promise<{ Authorization: string }> {
const token = await this.getValidAccessToken()
return {
Authorization: `Bearer ${token}`
}
}
async makeAuthenticatedRequest<T>(
url: string,
options: RequestInit = {}
): Promise<T> {
try {
const authHeader = await this.getAuthHeader()
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...authHeader,
...options.headers,
},
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`)
}
return response.json()
} catch (error) {
console.error('API request failed:', error)
throw error
}
}
logout(): void {
this.clearTokens()
}
}
declare global {
interface Window {
janAuthService?: JanAuthService
}
}
/**
* Gets or creates the shared JanAuthService instance on the window object
* This ensures all extensions use the same auth service instance
*/
export function getSharedAuthService(): JanAuthService {
if (!window.janAuthService) {
window.janAuthService = new JanAuthService()
}
return window.janAuthService
}

View File

@ -0,0 +1,68 @@
/**
* Generic Authentication API Layer
* Generic API calls for authentication (not provider-specific)
*/
import { AuthTokens } from './types'
import { AUTH_ENDPOINTS } from './const'
declare const JAN_API_BASE: string
/**
* Logout user on server
*/
export async function logoutUser(): Promise<void> {
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.LOGOUT}`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
console.warn(`Logout failed with status: ${response.status}`)
}
}
/**
* Guest login
*/
export async function guestLogin(): Promise<AuthTokens> {
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.GUEST_LOGIN}`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(
`Guest login failed: ${response.status} ${response.statusText}`
)
}
return response.json() as Promise<AuthTokens>
}
/**
* Refresh token (works for both guest and authenticated users)
*/
export async function refreshToken(): Promise<AuthTokens> {
const response = await fetch(
`${JAN_API_BASE}${AUTH_ENDPOINTS.REFRESH_TOKEN}`,
{
method: 'GET',
credentials: 'include',
}
)
if (!response.ok) {
throw new Error(
`Token refresh failed: ${response.status} ${response.statusText}`
)
}
return response.json() as Promise<AuthTokens>
}

View File

@ -0,0 +1,92 @@
/**
* Authentication Broadcast Channel Handler
* Manages cross-tab communication for auth state changes
*/
import { AUTH_BROADCAST_CHANNEL, AUTH_EVENTS } from './const'
import type { AuthBroadcastMessage } from './types'
export class AuthBroadcast {
private broadcastChannel: BroadcastChannel | null = null
constructor() {
this.setupBroadcastChannel()
}
/**
* Setup broadcast channel for cross-tab sync
*/
private setupBroadcastChannel(): void {
if (typeof BroadcastChannel !== 'undefined') {
try {
this.broadcastChannel = new BroadcastChannel(AUTH_BROADCAST_CHANNEL)
} catch (error) {
console.warn('BroadcastChannel not available:', error)
}
}
}
/**
* Broadcast auth event to other tabs
*/
broadcastEvent(type: AuthBroadcastMessage): void {
if (this.broadcastChannel) {
try {
const message = { type }
this.broadcastChannel.postMessage(message)
} catch (error) {
console.warn('Failed to broadcast auth event:', error)
}
}
}
/**
* Broadcast login event
*/
broadcastLogin(): void {
this.broadcastEvent(AUTH_EVENTS.LOGIN)
}
/**
* Broadcast logout event
*/
broadcastLogout(): void {
this.broadcastEvent(AUTH_EVENTS.LOGOUT)
}
/**
* Subscribe to auth events
*/
onAuthEvent(
listener: (event: MessageEvent<{ type: AuthBroadcastMessage }>) => void
): () => void {
if (this.broadcastChannel) {
this.broadcastChannel.addEventListener('message', listener)
// Return cleanup function
return () => {
this.broadcastChannel?.removeEventListener('message', listener)
}
}
// Return no-op cleanup if no broadcast channel
return () => {}
}
/**
* Get the broadcast channel for external listeners
*/
getBroadcastChannel(): BroadcastChannel | null {
return this.broadcastChannel
}
/**
* Cleanup broadcast channel
*/
destroy(): void {
if (this.broadcastChannel) {
this.broadcastChannel.close()
this.broadcastChannel = null
}
}
}

View File

@ -0,0 +1,29 @@
/**
* Authentication Constants and Configuration
* Generic constants used across all auth providers
*/
// Storage keys
export const AUTH_STORAGE_KEYS = {
AUTH_PROVIDER: 'jan_auth_provider',
} as const
// Generic API endpoints (provider-agnostic)
export const AUTH_ENDPOINTS = {
ME: '/auth/me',
LOGOUT: '/auth/logout',
GUEST_LOGIN: '/auth/guest-login',
REFRESH_TOKEN: '/auth/refresh-token',
} as const
// Token expiry buffer
export const TOKEN_EXPIRY_BUFFER = 60 * 1000 // 1 minute buffer before expiry
// Broadcast channel for cross-tab communication
export const AUTH_BROADCAST_CHANNEL = 'jan_auth_channel'
// Auth events
export const AUTH_EVENTS = {
LOGIN: 'auth:login',
LOGOUT: 'auth:logout',
} as const

View File

@ -0,0 +1,10 @@
// Main exports
export { JanAuthService, getSharedAuthService } from './service'
export { AuthProviderRegistry } from './registry'
// Type exports
export type { AuthTokens, AuthType, User } from './types'
export type { AuthProvider, ProviderType } from './providers'
// Constant exports
export { AUTH_STORAGE_KEYS, AUTH_EVENTS, AUTH_BROADCAST_CHANNEL } from './const'

View File

@ -0,0 +1,49 @@
/**
* Provider-specific API Layer
* API calls specific to authentication providers
*/
import { AuthTokens, LoginUrlResponse } from './types'
declare const JAN_API_BASE: string
export async function getLoginUrl(endpoint: string): Promise<LoginUrlResponse> {
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(
`Failed to get login URL: ${response.status} ${response.statusText}`
)
}
return response.json() as Promise<LoginUrlResponse>
}
export async function handleOAuthCallback(
endpoint: string,
code: string,
state?: string
): Promise<AuthTokens> {
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ code, state }),
})
if (!response.ok) {
throw new Error(
`OAuth callback failed: ${response.status} ${response.statusText}`
)
}
return response.json() as Promise<AuthTokens>
}

View File

@ -0,0 +1,39 @@
/**
* Base Auth Provider
* Abstract base class that all providers should extend
*/
import { AuthProvider, AuthTokens } from './types'
import { getLoginUrl, handleOAuthCallback } from './api'
export abstract class BaseAuthProvider implements AuthProvider {
abstract readonly id: string
abstract readonly name: string
abstract readonly icon: string
abstract getLoginEndpoint(): string
abstract getCallbackEndpoint(): string
async initiateLogin(): Promise<void> {
try {
// Fetch login URL from API
const data = await getLoginUrl(this.getLoginEndpoint())
// Redirect to the OAuth URL provided by the API
window.location.href = data.url
} catch (error) {
console.error(`Failed to initiate ${this.id} login:`, error)
throw error
}
}
async handleCallback(code: string, state?: string): Promise<AuthTokens> {
try {
// Handle OAuth callback and return token data
return await handleOAuthCallback(this.getCallbackEndpoint(), code, state)
} catch (error) {
console.error(`${this.name} callback handling failed:`, error)
throw error
}
}
}

View File

@ -0,0 +1,20 @@
/**
* Google Auth Provider
* Specific implementation for Google OAuth
*/
import { BaseAuthProvider } from './base'
export class GoogleAuthProvider extends BaseAuthProvider {
readonly id = 'google'
readonly name = 'Google'
readonly icon = 'IconBrandGoogleFilled'
getLoginEndpoint(): string {
return '/auth/google/login'
}
getCallbackEndpoint(): string {
return '/auth/google/callback'
}
}

View File

@ -0,0 +1,19 @@
/**
* Auth Providers Export
* Central place to register and export all available providers
*/
export { BaseAuthProvider } from './base'
export { GoogleAuthProvider } from './google'
// Registry of all available providers
import { GoogleAuthProvider } from './google'
// Instantiate providers
export const PROVIDERS = [new GoogleAuthProvider()] as const
// Generate proper types from providers
export type ProviderType = (typeof PROVIDERS)[number]['id']
// Export types
export type { AuthProvider } from './types'

View File

@ -0,0 +1,28 @@
/**
* Provider Type Definitions
* Interfaces and types for authentication providers
*/
import { AuthTokens } from '../types'
export { AuthTokens } from '../types'
// Login URL response from API
export interface LoginUrlResponse {
object: string
url: string
}
// Provider interface - all providers must implement this
export interface AuthProvider {
readonly id: string
readonly name: string
readonly icon: string
// Provider-specific configuration
getLoginEndpoint(): string
getCallbackEndpoint(): string
// OAuth flow methods
initiateLogin(): Promise<void>
handleCallback(code: string, state?: string): Promise<AuthTokens>
}

View File

@ -0,0 +1,25 @@
/**
* Dynamic Auth Provider Registry
* Provider-agnostic registry that can be extended at runtime
*/
import { PROVIDERS, type AuthProvider, type ProviderType } from './providers'
export class AuthProviderRegistry {
private providers = new Map<ProviderType, AuthProvider>()
constructor() {
// Register all available providers on initialization
for (const provider of PROVIDERS) {
this.providers.set(provider.id, provider)
}
}
getProvider(providerId: ProviderType): AuthProvider | undefined {
return this.providers.get(providerId)
}
getAllProviders(): AuthProvider[] {
return Array.from(this.providers.values())
}
}

View File

@ -0,0 +1,424 @@
/**
* Generic Authentication Service
* Handles authentication flows for any OAuth provider
*/
declare const JAN_API_BASE: string
import { User, AuthState, AuthBroadcastMessage } from './types'
import {
AUTH_STORAGE_KEYS,
AUTH_ENDPOINTS,
TOKEN_EXPIRY_BUFFER,
AUTH_EVENTS,
} from './const'
import { logoutUser, refreshToken, guestLogin } from './api'
import { AuthProviderRegistry } from './registry'
import { AuthBroadcast } from './broadcast'
import type { ProviderType } from './providers'
const authProviderRegistry = new AuthProviderRegistry()
export class JanAuthService {
private accessToken: string | null = null
private tokenExpiryTime: number = 0
private refreshPromise: Promise<void> | null = null
private authBroadcast: AuthBroadcast
private currentUser: User | null = null
private initPromise: Promise<void> | null = null
constructor() {
this.authBroadcast = new AuthBroadcast()
this.setupBroadcastHandlers()
this.initPromise = this.initialize().catch(console.error)
}
/**
* Ensure initialization is complete before proceeding
*/
private async ensureInitialized(): Promise<void> {
if (this.initPromise) {
await this.initPromise
this.initPromise = null
}
}
/**
* Initialize the auth service
* Called on app load to check existing session
*/
async initialize(): Promise<void> {
try {
if (!this.isAuthenticated()) {
// Not authenticated - ensure guest access
await this.ensureGuestAccess()
return
}
// Authenticated - ensure we have a valid token
await this.refreshAccessToken()
} catch (error) {
console.error('Failed to initialize auth:', error)
}
}
/**
* Start OAuth login flow with specified provider
*/
async loginWithProvider(providerId: ProviderType): Promise<void> {
await this.ensureInitialized()
const provider = authProviderRegistry.getProvider(providerId)
if (!provider) {
throw new Error(`Provider ${providerId} is not available`)
}
try {
await provider.initiateLogin()
} catch (error) {
console.error(`Failed to initiate ${providerId} login:`, error)
throw error
}
}
/**
* Handle OAuth callback for any provider
*/
async handleProviderCallback(
providerId: ProviderType,
code: string,
state?: string
): Promise<void> {
await this.ensureInitialized()
const provider = authProviderRegistry.getProvider(providerId)
if (!provider) {
throw new Error(`Provider ${providerId} is not supported`)
}
try {
// Use provider to handle the callback - this returns tokens
const tokens = await provider.handleCallback(code, state)
// Store tokens and set authenticated state
this.accessToken = tokens.access_token
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
this.setAuthProvider(providerId)
this.authBroadcast.broadcastLogin()
} catch (error) {
console.error(`Failed to handle ${providerId} callback:`, error)
throw error
}
}
/**
* Get a valid access token
* Handles both authenticated and guest tokens
*/
async getValidAccessToken(): Promise<string> {
await this.ensureInitialized()
if (
this.accessToken &&
Date.now() < this.tokenExpiryTime - TOKEN_EXPIRY_BUFFER
) {
return this.accessToken
}
if (!this.refreshPromise) {
this.refreshPromise = this.refreshAccessToken().finally(() => {
this.refreshPromise = null
})
}
await this.refreshPromise
if (!this.accessToken) {
throw new Error('Failed to obtain access token')
}
return this.accessToken
}
async refreshAccessToken(): Promise<void> {
try {
const tokens = await refreshToken()
this.accessToken = tokens.access_token
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
} catch (error) {
console.error('Failed to refresh access token:', error)
if (error instanceof Error && error.message.includes('401')) {
await this.handleSessionExpired()
}
throw error
}
}
/**
* Get current authenticated user
*/
async getCurrentUser(): Promise<User | null> {
await this.ensureInitialized()
const authType = this.getAuthState()
if (authType !== AuthState.AUTHENTICATED) {
return null
}
if (this.currentUser) {
return this.currentUser
}
const userProfile = await this.fetchUserProfile()
if (userProfile) {
const user: User = {
id: userProfile.id,
email: userProfile.email,
name: userProfile.name,
picture: userProfile.picture,
object: userProfile.object || 'user',
}
this.currentUser = user
}
return this.currentUser
}
/**
* Logout the current user
*/
async logout(): Promise<void> {
await this.ensureInitialized()
try {
const authType = this.getAuthState()
if (authType === AuthState.AUTHENTICATED) {
await logoutUser()
}
this.clearAuthState()
this.authBroadcast.broadcastLogout()
if (window.location.pathname !== '/') {
window.location.href = '/'
}
} catch (error) {
console.error('Logout failed:', error)
this.clearAuthState()
}
}
/**
* Get enabled authentication providers
*/
getAllProviders(): Array<{ id: string; name: string; icon: string }> {
return authProviderRegistry.getAllProviders().map((provider) => ({
id: provider.id,
name: provider.name,
icon: provider.icon,
}))
}
/**
* Check if user is authenticated with any provider
*/
isAuthenticated(): boolean {
return this.getAuthState() === AuthState.AUTHENTICATED
}
/**
* Check if user is authenticated with specific provider
*/
isAuthenticatedWithProvider(providerId: ProviderType): boolean {
const authType = this.getAuthState()
if (authType !== AuthState.AUTHENTICATED) {
return false
}
return this.getAuthProvider() === providerId
}
/**
* Get current auth type derived from provider
*/
getAuthState(): AuthState {
const provider = this.getAuthProvider()
if (!provider) return AuthState.UNAUTHENTICATED
if (provider === 'guest') return AuthState.GUEST
return AuthState.AUTHENTICATED
}
/**
* Get auth headers for API requests
*/
async getAuthHeader(): Promise<{ Authorization: string }> {
await this.ensureInitialized()
const token = await this.getValidAccessToken()
return {
Authorization: `Bearer ${token}`,
}
}
/**
* Make authenticated API request
*/
async makeAuthenticatedRequest<T>(
url: string,
options: RequestInit = {}
): Promise<T> {
await this.ensureInitialized()
try {
const authHeader = await this.getAuthHeader()
const response = await fetch(url, {
...options,
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...authHeader,
...options.headers,
},
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(
`API request failed: ${response.status} ${response.statusText} - ${errorText}`
)
}
return response.json()
} catch (error) {
console.error('API request failed:', error)
throw error
}
}
/**
* Get the broadcast channel for external listeners
*/
getBroadcastChannel(): BroadcastChannel | null {
return this.authBroadcast.getBroadcastChannel()
}
/**
* Subscribe to auth events
*/
onAuthEvent(
callback: (event: MessageEvent<{ type: AuthBroadcastMessage }>) => void
): () => void {
return this.authBroadcast.onAuthEvent(callback)
}
/**
* Clear all auth state
*/
private clearAuthState(): void {
this.accessToken = null
this.tokenExpiryTime = 0
this.currentUser = null
localStorage.removeItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER)
}
/**
* Ensure guest access is available
*/
private async ensureGuestAccess(): Promise<void> {
try {
this.setAuthProvider('guest')
if (!this.accessToken || Date.now() > this.tokenExpiryTime) {
const tokens = await guestLogin()
this.accessToken = tokens.access_token
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
}
} catch (error) {
console.error('Failed to ensure guest access:', error)
// Remove provider (unauthenticated state)
localStorage.removeItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER)
}
}
/**
* Handle session expired
*/
private async handleSessionExpired(): Promise<void> {
this.logout().catch(console.error)
this.ensureGuestAccess().catch(console.error)
}
/**
* Setup broadcast event handlers
*/
private setupBroadcastHandlers(): void {
this.authBroadcast.onAuthEvent((event) => {
switch (event.data.type) {
case AUTH_EVENTS.LOGIN:
// Another tab logged in, refresh our state
this.initialize().catch(console.error)
break
case AUTH_EVENTS.LOGOUT:
// Another tab logged out, clear our state
this.clearAuthState()
this.ensureGuestAccess().catch(console.error)
break
}
})
}
/**
* Get current auth provider
*/
getAuthProvider(): string | null {
return localStorage.getItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER)
}
/**
* Set auth provider
*/
private setAuthProvider(provider: string): void {
localStorage.setItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER, provider)
}
/**
* Fetch user profile from server
*/
private async fetchUserProfile(): Promise<User | null> {
try {
return await this.makeAuthenticatedRequest<User>(
`${JAN_API_BASE}${AUTH_ENDPOINTS.ME}`
)
} catch (error) {
console.error('Failed to fetch user profile:', error)
if (error instanceof Error && error.message.includes('401')) {
// Authentication failed - handle session expiry
await this.handleSessionExpired()
return null
}
return null
}
}
}
// Singleton instance management
declare global {
interface Window {
janAuthService?: JanAuthService
}
}
/**
* Get or create the shared JanAuthService instance
*/
export function getSharedAuthService(): JanAuthService {
if (!window.janAuthService) {
window.janAuthService = new JanAuthService()
}
return window.janAuthService
}

View File

@ -0,0 +1,31 @@
/**
* Generic Authentication Types
* Provider-agnostic type definitions
*/
import type { ProviderType } from './providers'
import { AUTH_EVENTS } from './const'
export enum AuthState {
GUEST = 'guest',
AUTHENTICATED = 'authenticated',
UNAUTHENTICATED = 'unauthenticated',
}
export type AuthType = ProviderType | 'guest'
export interface AuthTokens {
access_token: string
expires_in: number
object: string
}
export interface User {
id: string
email: string
name: string
object: string
picture?: string
}
export type AuthBroadcastMessage = typeof AUTH_EVENTS[keyof typeof AUTH_EVENTS]

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