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
env:
JAN_API_BASE: "https://api.jan.ai/jan/v1"
GA_MEASUREMENT_ID: "G-YK53MX8M8M"
CLOUDFLARE_PROJECT_NAME: "jan-server-web"
steps:
- uses: actions/checkout@v4

View File

@ -64,7 +64,7 @@ jobs:
- 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
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
run: |

View File

@ -101,7 +101,7 @@ jobs:
- 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
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
run: |

View File

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

View File

@ -92,31 +92,6 @@ jobs:
run: |
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
run: |
echo "Version: ${{ inputs.new_version }}"
@ -167,7 +142,6 @@ jobs:
- name: Build app
run: |
rustup target add x86_64-apple-darwin
make build
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -30,6 +30,17 @@ endif
yarn build:core
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
yarn download:bin
yarn download:lib
@ -70,11 +81,12 @@ test: lint
cargo test --manifest-path src-tauri/utils/Cargo.toml
# Builds and publishes the app
build-and-publish: install-and-build
build-and-publish: install-and-build install-rust-targets
yarn build
# Build
build: install-and-build
build: install-and-build install-rust-targets
yarn download:bin
yarn download:lib
yarn build

View File

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

View File

@ -3,12 +3,12 @@ title: "Local API server"
version: 0.4.5
description: ""
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"
<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 🎉

View File

@ -3,12 +3,12 @@ title: "Jan Data Folder"
version: 0.4.6
description: ""
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"
<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 🎉

View File

@ -3,12 +3,12 @@ title: "New UI & Codestral Support"
version: 0.5.0
description: "Revamped Jan's UI to make it clearer and more user-friendly"
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"
<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.

View File

@ -3,12 +3,12 @@ title: "Groq API Integration"
version: 0.4.10
description: ""
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"
<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 🎉

View File

@ -3,12 +3,12 @@ title: "New Mistral Extension"
version: 0.4.11
description: "Jan has a new Mistral Extension letting you chat with larger Mistral models via Mistral API"
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"
<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 🎉

View File

@ -3,29 +3,29 @@ title: 'Jan now supports Llama3 and Command R+'
version: 0.4.12
description: "Jan has added compatibility with Llama3 & Command R+"
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"
<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)).
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
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
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).

View File

@ -3,12 +3,12 @@ title: "Jan now supports more GGUF models"
version: 0.4.13
description: "We rebased to llamacpp b2865."
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"
<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!
@ -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.
![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
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)

View File

@ -3,12 +3,12 @@ title: "Jan now compatible with Aya 23 8B & 35B and Phi-3-Medium"
version: 0.4.14
description: "Jan now supports Cohere's Aya 23 8B & 35B and Microsoft's Phi-3-Medium."
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"
<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`.

View File

@ -3,12 +3,12 @@ title: "Jan supports NVIDIA NIM"
version: 0.5.1
description: "Jan has integrated NVIDIA NIM and supports Qwen 2 7B"
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"
<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

View File

@ -3,12 +3,12 @@ title: "Jan supports Claude 3.5 Sonnet"
version: 0.5.2
description: "You can run Claude 3.5 Sonnet in Jan"
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"
<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

View File

@ -3,12 +3,12 @@ title: "v0.5.3 is out with stability improvements!"
version: 0.5.3
description: "You can run Llama 3.1 and Gemma 2 in Jan"
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"
<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

View File

@ -3,12 +3,12 @@ title: "Jan has Stable, Beta and Nightly versions"
version: 0.5.7
description: "This release is mostly focused on bug fixes."
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"
<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 🎉

View File

@ -3,12 +3,12 @@ title: "Model downloads & running issues fixed"
version: 0.5.9
description: "Jan v0.5.9 is here: fixing what needed fixing."
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"
<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

View File

@ -3,12 +3,12 @@ title: "Jan supports Qwen2.5-Coder 14B & 32B"
version: 0.5.8
description: "Jan v0.5.8 is out: Jan supports Qwen2.5-Coder 14B & 32B through Cortex"
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"
<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

View File

@ -3,12 +3,12 @@ title: "Jan v0.5.10 is live"
version: 0.5.10
description: "Jan is faster, smoother, and more reliable."
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"
<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.

View File

@ -3,12 +3,12 @@ title: "Jan v0.5.11 is here!"
version: 0.5.11
description: "Critical issues fixed, Mac installation updated."
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"
<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.

View File

@ -3,12 +3,12 @@ title: "Jan gives you full control over your privacy"
version: 0.5.12
description: "Improved Privacy settings to give full control over analytics"
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"
<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.

View File

@ -3,12 +3,12 @@ title: "Qwen3 support is now more reliable."
version: 0.5.17
description: "Jan v0.5.17 is out: Qwen3 support is now more reliable"
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"
<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

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.3 brings new features and models!"
version: 0.6.3
description: "Unlocking MCP for everyone and bringing our latest model to Jan!"
date: 2025-06-26
ogImage: "/assets/images/changelog/jn128.gif"
ogImage: "https://catalog.jan.ai/docs/jn128.gif"
---
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 🎉

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.5 brings responsive UI and MCP examples!"
version: 0.6.5
description: "New MCP examples, updated pages, and bug fixes!"
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"
<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 🎉

View File

@ -3,12 +3,12 @@ title: "Jan v0.6.6: Enhanced llama.cpp integration and smarter model management"
version: 0.6.6
description: "Major llama.cpp improvements, Hugging Face provider support, and refined MCP experience"
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"
<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 🎉

View File

@ -3,13 +3,13 @@ title: "Jan v0.6.8: Engine fixes, new MCP tutorials, and cleaner docs"
version: 0.6.8
description: "Llama.cpp stability upgrades, Linear/Todoist MCP tutorials, new model pages (Lucy, Janv1), and docs reorganization"
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 { 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 🎉

View File

@ -3,13 +3,13 @@ title: "Jan v0.6.9: Image support, stable MCP, and powerful model tools"
version: 0.6.9
description: "Major multimodal support with image uploads, MCP out of experimental, auto-detect model capabilities, and enhanced tool calling"
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 { 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 🎉

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
<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.
</video>

View File

@ -20,7 +20,7 @@ import { Callout } from 'nextra/components'
# Jan Nano
![Jan Nano](../_assets/jan-nano0.png)
![Jan Nano](https://catalog.jan.ai/docs/jan-nano0.png)
## Why Jan Nano?
@ -81,7 +81,7 @@ Add the serper MCP to Jan via the **Settings** > **MCP Servers** tab.
**Step 6**
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

View File

@ -58,7 +58,7 @@ These benchmarks (EQBench, CreativeWriting, and IFBench) measure the model's abi
### Demo
![Jan-v1 Demo](../_assets/jan_v1_demo.gif)
![Jan-v1 Demo](https://catalog.jan.ai/docs/jan_v1_demo.gif)
### 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
![Lucy Demo](../_assets/lucy_demo.gif)
![Lucy Demo](https://catalog.jan.ai/docs/lucy_demo.gif)
### 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:
<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.
</video>

View File

@ -98,7 +98,7 @@ When you first use Canva tools:
- Canva authentication page appears in your default browser
- 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**
- 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:
![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

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:
![Todoist MCP in action](../../_assets/mcptodoist_extreme.gif)
![Todoist MCP in action](https://catalog.jan.ai/docs/mcptodoist_extreme.gif)
## 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
![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*
![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.
![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

View File

@ -9,6 +9,15 @@ export { default as ConversationalExtensionWeb } from './conversational-web'
export { default as JanProviderWeb } from './jan-provider-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
export type {
WebExtensionRegistry,
@ -17,9 +26,18 @@ export type {
WebExtensionLoader,
ConversationalWebModule,
JanProviderWebModule,
MCPWebModule
MCPWebModule,
} from './types'
// Re-export auth types
export type {
User,
AuthTokens,
AuthProvider,
AuthProviderRegistry,
ProviderType,
} from './shared/auth'
// Extension registry for dynamic loading
export const WEB_EXTENSIONS: WebExtensionRegistry = {
'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