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
@ -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
|
||||||
|
|||||||
@ -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: |
|
||||||
|
|||||||
@ -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: |
|
||||||
|
|||||||
@ -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: '.'
|
||||||
|
|||||||
26
.github/workflows/template-tauri-build-macos.yml
vendored
@ -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 }}
|
||||||
|
|||||||
16
Makefile
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 14 MiB |
|
Before Width: | Height: | Size: 11 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 4.6 MiB |
BIN
docs/public/assets/images/changelog/jan-import-vlm-model.gif
Normal file
|
After Width: | Height: | Size: 6.2 MiB |
|
Before Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 5.0 MiB |
|
Before Width: | Height: | Size: 665 KiB |
|
Before Width: | Height: | Size: 4.9 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 3.7 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 7.3 MiB |
|
Before Width: | Height: | Size: 18 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 5.2 MiB |
|
Before Width: | Height: | Size: 7.4 MiB |
|
Before Width: | Height: | Size: 15 MiB |
|
Before Width: | Height: | Size: 8.5 MiB |
|
Before Width: | Height: | Size: 4.7 MiB |
|
Before Width: | Height: | Size: 9.7 MiB |
|
Before Width: | Height: | Size: 14 MiB |
|
Before Width: | Height: | Size: 5.7 MiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 9.3 MiB |
|
Before Width: | Height: | Size: 18 MiB |
|
Before Width: | Height: | Size: 8.3 MiB |
|
Before Width: | Height: | Size: 8.3 MiB |
|
Before Width: | Height: | Size: 15 MiB |
|
Before Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 14 MiB |
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 Meta’s open-source language model, `Llama3`, through the integration with `llamacpp` (thanks to [@ggerganov](https://github.com/ggerganov)).
|
Jan has added compatibility with Meta’s 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).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Import Huggingface models directly
|
## Import Huggingface models directly
|
||||||
|
|
||||||
Users can now import Huggingface models into Jan. Simply copy the model’s link from Huggingface and paste it into the search bar on Jan Hub.
|
Users can now import Huggingface models into Jan. Simply copy the model’s link from Huggingface and paste it into the search bar on Jan Hub.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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).
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@ -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`.
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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, Jan‑v1), and docs reorganization"
|
description: "Llama.cpp stability upgrades, Linear/Todoist MCP tutorials, new model pages (Lucy, Jan‑v1), 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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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 🎉
|
||||||
|
|
||||||
|
|||||||
@ -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).
|
||||||
|
Before Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 22 MiB |
|
Before Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 7.7 MiB |
|
Before Width: | Height: | Size: 23 MiB |
|
Before Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 3.4 MiB |
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { Callout } from 'nextra/components'
|
|||||||
|
|
||||||
# Jan Nano
|
# Jan Nano
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Queries to Try
|
## Queries to Try
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ These benchmarks (EQBench, CreativeWriting, and IFBench) measure the model's abi
|
|||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Deployment Options
|
### Deployment Options
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,7 @@ To use Lucy's web search capabilities, you'll need a Serper API key. Get one at
|
|||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Deployment Options
|
### Deployment Options
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2. **Team Selection & Permissions**
|
2. **Team Selection & Permissions**
|
||||||
- Select your team (if you have multiple)
|
- Select your team (if you have multiple)
|
||||||
|
|||||||
@ -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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Creative Examples
|
## Creative Examples
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Example Prompts
|
## Example Prompts
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||

|

|
||||||
*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*
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Key Points
|
## Key Points
|
||||||
|
|
||||||
|
|||||||
@ -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'),
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
68
extensions-web/src/shared/auth/api.ts
Normal 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>
|
||||||
|
}
|
||||||
92
extensions-web/src/shared/auth/broadcast.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
extensions-web/src/shared/auth/const.ts
Normal 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
|
||||||
10
extensions-web/src/shared/auth/index.ts
Normal 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'
|
||||||
49
extensions-web/src/shared/auth/providers/api.ts
Normal 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>
|
||||||
|
}
|
||||||
39
extensions-web/src/shared/auth/providers/base.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
extensions-web/src/shared/auth/providers/google.ts
Normal 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
19
extensions-web/src/shared/auth/providers/index.ts
Normal 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'
|
||||||
28
extensions-web/src/shared/auth/providers/types.ts
Normal 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>
|
||||||
|
}
|
||||||
25
extensions-web/src/shared/auth/registry.ts
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
424
extensions-web/src/shared/auth/service.ts
Normal 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
|
||||||
|
}
|
||||||
31
extensions-web/src/shared/auth/types.ts
Normal 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]
|
||||||