Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
154301b3ad | ||
|
|
e7b7ac9e94 | ||
|
|
e531eaa4ad | ||
|
|
23b03da714 | ||
|
|
22be93807d | ||
|
|
653ecdb494 | ||
|
|
15c426aefc | ||
|
|
2fa153ac34 | ||
|
|
62bd91a1e1 | ||
|
|
f7e0e790b6 | ||
|
|
c854c54c0c | ||
|
|
a14872666a | ||
|
|
e9f469b623 | ||
|
|
5a016860aa | ||
|
|
c773abb688 | ||
|
|
2561fcd78a | ||
|
|
28ed5e2af2 | ||
|
|
4c5c8e6aed | ||
|
|
f07e43cfe0 | ||
|
|
e46200868e | ||
|
|
147cab94a8 | ||
|
|
2fb956ccaf | ||
|
|
4dee0a4ba1 | ||
|
|
418a48ab39 | ||
|
|
9bc56f6e30 | ||
|
|
f0ca9cce35 | ||
|
|
746dbc632b | ||
|
|
462b05e612 | ||
|
|
946b347f44 | ||
|
|
b23e88f078 | ||
|
|
476fdd6040 | ||
|
|
fa8b3664cb | ||
|
|
8b687619b2 | ||
|
|
176ad07f1d | ||
|
|
7b5060c9be | ||
|
|
584daa9682 | ||
|
|
31f9501d8e | ||
|
|
c096929d8b | ||
|
|
01050f3103 | ||
|
|
45d57dd34d | ||
|
|
f4066e6e5a | ||
|
|
a2fbce698f | ||
|
|
fc784620e0 | ||
|
|
340042682a | ||
|
|
6dd2d2d6c1 | ||
|
|
7762cea10a | ||
|
|
610b741db2 | ||
|
|
814034d3d7 | ||
|
|
839672b82f | ||
|
|
03762c3634 | ||
|
|
59c76bcb1c | ||
|
|
1905f9a9ce | ||
|
|
ff93dc3c5c | ||
|
|
510c4a5188 | ||
|
|
c7d1a3c65d | ||
|
|
999b7b3cd8 | ||
|
|
f224d18d7f | ||
|
|
1bf5c770cf | ||
|
|
613bc85a13 | ||
|
|
b1abc97bda | ||
|
|
eec94c47dd | ||
|
|
b2632a005c | ||
|
|
706dad2687 | ||
|
|
e5be683a97 | ||
|
|
e7fcc809e7 | ||
|
|
26006c143e | ||
|
|
28afafaad7 | ||
|
|
dd1b3c98bf | ||
|
|
3919cd0306 | ||
|
|
7d615b4163 | ||
|
|
4828f34fec | ||
|
|
61c3fd4b5a | ||
|
|
816d60b22a | ||
|
|
310ca7cb23 | ||
|
|
fa397038ef | ||
|
|
dabc49567c | ||
|
|
d8dcba3552 | ||
|
|
f4efd479d5 | ||
|
|
a72c74dbf9 | ||
|
|
6c4dd85e6f | ||
|
|
9bfec5c7b3 | ||
|
|
fe2c2a8687 | ||
|
|
b23aa68254 | ||
|
|
0588cb34c6 | ||
|
|
b32e3ebd60 | ||
|
|
cc77ae3430 | ||
|
|
ac6eda063a | ||
|
|
f160d83ca9 | ||
|
|
13c7ad707e | ||
|
|
17dced03c0 | ||
|
|
39b1ba4691 | ||
|
|
8c7ad408a9 | ||
|
|
f0c4784b7b | ||
|
|
83fc68e27d | ||
|
|
be9a6c0254 | ||
|
|
1acdb77ad1 | ||
|
|
51e7a08118 | ||
|
|
aa0c4b0d1b | ||
|
|
80ee8fd2b2 | ||
|
|
291482cc16 | ||
|
|
154bc17778 | ||
|
|
cc5130c1af | ||
|
|
2d9f20ffb6 | ||
|
|
3e332eceae | ||
|
|
73b241c16f | ||
|
|
8ed68d9c19 | ||
|
|
b5e57a429a | ||
|
|
62fa0ffa57 | ||
|
|
481e9c1130 | ||
|
|
93652ce884 | ||
|
|
fa61163350 | ||
|
|
cb9eb6d238 | ||
|
|
41a93690a1 | ||
|
|
b309d34274 | ||
|
|
ca485b4a35 | ||
|
|
252336d95c | ||
|
|
1d620df625 | ||
|
|
e346b293f6 | ||
|
|
8b448d1c0b | ||
|
|
b628b3d9ab | ||
|
|
cef351bfd0 | ||
|
|
5adaf62975 | ||
|
|
c797283409 | ||
|
|
c4af638a17 | ||
|
|
4da0fd1ca3 | ||
|
|
bdd8549d3e | ||
|
|
24ff36d424 | ||
|
|
524ac11294 | ||
|
|
1747e0ad41 | ||
|
|
08d527366e | ||
|
|
70c6582a58 | ||
|
|
9720ad368e | ||
|
|
d3b4144c5b | ||
|
|
e6f366d373 | ||
|
|
4fbc7873ca | ||
|
|
f537429d2c | ||
|
|
f6f9813ef2 | ||
|
|
eccaa282e0 | ||
|
|
87db633b7d | ||
|
|
ab2bc11465 | ||
|
|
8e10f27cc2 | ||
|
|
9f72debc17 | ||
|
|
1b9efee52c | ||
|
|
fb8b61c567 | ||
|
|
0f0ba43b7f | ||
|
|
6a4aaaec87 | ||
|
|
a5574eaacb | ||
|
|
771d097309 | ||
|
|
e0ab77cb24 | ||
|
|
7a36ed238c | ||
|
|
99d1713517 | ||
|
|
d102165028 | ||
|
|
199623b414 | ||
|
|
2679b19e32 | ||
|
|
c5a5968bf8 |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,5 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Jan Discussions
|
- name: Jan Discussions
|
||||||
url: https://github.com/orgs/menloresearch/discussions/categories/q-a
|
url: https://github.com/orgs/janhq/discussions/categories/q-a
|
||||||
about: Get help, discuss features & roadmap, and share your projects
|
about: Get help, discuss features & roadmap, and share your projects
|
||||||
|
|||||||
4
.github/workflows/jan-server-web-ci-dev.yml
vendored
4
.github/workflows/jan-server-web-ci-dev.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
build-and-preview:
|
build-and-preview:
|
||||||
runs-on: [ubuntu-24-04-docker]
|
runs-on: [ubuntu-24-04-docker]
|
||||||
env:
|
env:
|
||||||
JAN_API_BASE: "https://api-dev.jan.ai/v1"
|
MENLO_PLATFORM_BASE_URL: "https://api-dev.jan.ai/v1"
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
contents: write
|
contents: write
|
||||||
@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: |
|
run: |
|
||||||
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||||
|
|
||||||
- name: Push docker image
|
- name: Push docker image
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
|||||||
4
.github/workflows/jan-server-web-ci-prod.yml
vendored
4
.github/workflows/jan-server-web-ci-prod.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
deployments: write
|
deployments: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
env:
|
env:
|
||||||
JAN_API_BASE: "https://api.jan.ai/v1"
|
MENLO_PLATFORM_BASE_URL: "https://api.jan.ai/v1"
|
||||||
GA_MEASUREMENT_ID: "G-YK53MX8M8M"
|
GA_MEASUREMENT_ID: "G-YK53MX8M8M"
|
||||||
CLOUDFLARE_PROJECT_NAME: "jan-server-web"
|
CLOUDFLARE_PROJECT_NAME: "jan-server-web"
|
||||||
steps:
|
steps:
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: make config-yarn && yarn install && yarn build:core && make build-web-app
|
run: make config-yarn && yarn install && yarn build:core && make build-web-app
|
||||||
env:
|
env:
|
||||||
JAN_API_BASE: ${{ env.JAN_API_BASE }}
|
MENLO_PLATFORM_BASE_URL: ${{ env.MENLO_PLATFORM_BASE_URL }}
|
||||||
GA_MEASUREMENT_ID: ${{ env.GA_MEASUREMENT_ID }}
|
GA_MEASUREMENT_ID: ${{ env.GA_MEASUREMENT_ID }}
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages Production
|
- name: Publish to Cloudflare Pages Production
|
||||||
|
|||||||
4
.github/workflows/jan-server-web-ci-stag.yml
vendored
4
.github/workflows/jan-server-web-ci-stag.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
build-and-preview:
|
build-and-preview:
|
||||||
runs-on: [ubuntu-24-04-docker]
|
runs-on: [ubuntu-24-04-docker]
|
||||||
env:
|
env:
|
||||||
JAN_API_BASE: "https://api-stag.jan.ai/v1"
|
MENLO_PLATFORM_BASE_URL: "https://api-stag.jan.ai/v1"
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
contents: write
|
contents: write
|
||||||
@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
run: |
|
run: |
|
||||||
docker build --build-arg JAN_API_BASE=${{ env.JAN_API_BASE }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
docker build --build-arg MENLO_PLATFORM_BASE_URL=${{ env.MENLO_PLATFORM_BASE_URL }} -t ${{ steps.vars.outputs.FULL_IMAGE }} .
|
||||||
|
|
||||||
- name: Push docker image
|
- name: Push docker image
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
|||||||
108
.github/workflows/jan-tauri-build-nightly.yaml
vendored
108
.github/workflows/jan-tauri-build-nightly.yaml
vendored
@ -168,62 +168,62 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
||||||
AWS_EC2_METADATA_DISABLED: 'true'
|
AWS_EC2_METADATA_DISABLED: 'true'
|
||||||
|
|
||||||
noti-discord-nightly-and-update-url-readme:
|
# noti-discord-nightly-and-update-url-readme:
|
||||||
needs:
|
# needs:
|
||||||
[
|
# [
|
||||||
build-macos,
|
# build-macos,
|
||||||
build-windows-x64,
|
# build-windows-x64,
|
||||||
build-linux-x64,
|
# build-linux-x64,
|
||||||
get-update-version,
|
# get-update-version,
|
||||||
set-public-provider,
|
# set-public-provider,
|
||||||
sync-temp-to-latest,
|
# sync-temp-to-latest,
|
||||||
]
|
# ]
|
||||||
secrets: inherit
|
# secrets: inherit
|
||||||
if: github.event_name == 'schedule'
|
# if: github.event_name == 'schedule'
|
||||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
with:
|
# with:
|
||||||
ref: refs/heads/dev
|
# ref: refs/heads/dev
|
||||||
build_reason: Nightly
|
# build_reason: Nightly
|
||||||
push_to_branch: dev
|
# push_to_branch: dev
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
noti-discord-pre-release-and-update-url-readme:
|
# noti-discord-pre-release-and-update-url-readme:
|
||||||
needs:
|
# needs:
|
||||||
[
|
# [
|
||||||
build-macos,
|
# build-macos,
|
||||||
build-windows-x64,
|
# build-windows-x64,
|
||||||
build-linux-x64,
|
# build-linux-x64,
|
||||||
get-update-version,
|
# get-update-version,
|
||||||
set-public-provider,
|
# set-public-provider,
|
||||||
sync-temp-to-latest,
|
# sync-temp-to-latest,
|
||||||
]
|
# ]
|
||||||
secrets: inherit
|
# secrets: inherit
|
||||||
if: github.event_name == 'push'
|
# if: github.event_name == 'push'
|
||||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
with:
|
# with:
|
||||||
ref: refs/heads/dev
|
# ref: refs/heads/dev
|
||||||
build_reason: Pre-release
|
# build_reason: Pre-release
|
||||||
push_to_branch: dev
|
# push_to_branch: dev
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
noti-discord-manual-and-update-url-readme:
|
# noti-discord-manual-and-update-url-readme:
|
||||||
needs:
|
# needs:
|
||||||
[
|
# [
|
||||||
build-macos,
|
# build-macos,
|
||||||
build-windows-x64,
|
# build-windows-x64,
|
||||||
build-linux-x64,
|
# build-linux-x64,
|
||||||
get-update-version,
|
# get-update-version,
|
||||||
set-public-provider,
|
# set-public-provider,
|
||||||
sync-temp-to-latest,
|
# sync-temp-to-latest,
|
||||||
]
|
# ]
|
||||||
secrets: inherit
|
# secrets: inherit
|
||||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
|
# if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
|
||||||
uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
# uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
|
||||||
with:
|
# with:
|
||||||
ref: refs/heads/dev
|
# ref: refs/heads/dev
|
||||||
build_reason: Manual
|
# build_reason: Manual
|
||||||
push_to_branch: dev
|
# push_to_branch: dev
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
# new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
comment-pr-build-url:
|
comment-pr-build-url:
|
||||||
needs:
|
needs:
|
||||||
|
|||||||
6
.github/workflows/jan-tauri-build.yaml
vendored
6
.github/workflows/jan-tauri-build.yaml
vendored
@ -82,11 +82,11 @@ jobs:
|
|||||||
VERSION=${{ needs.get-update-version.outputs.new_version }}
|
VERSION=${{ needs.get-update-version.outputs.new_version }}
|
||||||
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
|
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
|
||||||
LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}"
|
LINUX_SIGNATURE="${{ needs.build-linux-x64.outputs.APPIMAGE_SIG }}"
|
||||||
LINUX_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}"
|
LINUX_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}"
|
||||||
WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}"
|
WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}"
|
||||||
WINDOWS_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}"
|
WINDOWS_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}"
|
||||||
DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}"
|
DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}"
|
||||||
DARWIN_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}"
|
DARWIN_URL="https://github.com/janhq/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}"
|
||||||
|
|
||||||
jq --arg version "$VERSION" \
|
jq --arg version "$VERSION" \
|
||||||
--arg pub_date "$PUB_DATE" \
|
--arg pub_date "$PUB_DATE" \
|
||||||
|
|||||||
@ -29,7 +29,7 @@ jobs:
|
|||||||
local max_retries=3
|
local max_retries=3
|
||||||
local tag
|
local tag
|
||||||
while [ $retries -lt $max_retries ]; do
|
while [ $retries -lt $max_retries ]; do
|
||||||
tag=$(curl -s https://api.github.com/repos/menloresearch/jan/releases/latest | jq -r .tag_name)
|
tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name)
|
||||||
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
||||||
echo $tag
|
echo $tag
|
||||||
return
|
return
|
||||||
|
|||||||
@ -50,6 +50,6 @@ jobs:
|
|||||||
- macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg
|
- macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg
|
||||||
- Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb
|
- Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb
|
||||||
- Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage
|
- Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage
|
||||||
- Github action run: https://github.com/menloresearch/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
- Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
||||||
env:
|
env:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|||||||
@ -49,6 +49,8 @@ jobs:
|
|||||||
# Update tauri.conf.json
|
# Update tauri.conf.json
|
||||||
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = false' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = false' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
||||||
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
||||||
|
jq '.bundle.windows.nsis.template = "tauri.bundle.windows.nsis.template"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
||||||
|
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
||||||
jq '.bundle.windows.signCommand = "echo External build - skipping signature: %1"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
jq '.bundle.windows.signCommand = "echo External build - skipping signature: %1"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
||||||
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
||||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json
|
jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json
|
||||||
@ -80,6 +82,36 @@ jobs:
|
|||||||
echo "---------./src-tauri/Cargo.toml---------"
|
echo "---------./src-tauri/Cargo.toml---------"
|
||||||
cat ./src-tauri/Cargo.toml
|
cat ./src-tauri/Cargo.toml
|
||||||
|
|
||||||
|
generate_build_version() {
|
||||||
|
### Examble
|
||||||
|
### input 0.5.6 output will be 0.5.6 and 0.5.6.0
|
||||||
|
### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2
|
||||||
|
### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213
|
||||||
|
local new_version="$1"
|
||||||
|
local base_version
|
||||||
|
local t_value
|
||||||
|
# Check if it has a "-"
|
||||||
|
if [[ "$new_version" == *-* ]]; then
|
||||||
|
base_version="${new_version%%-*}" # part before -
|
||||||
|
suffix="${new_version#*-}" # part after -
|
||||||
|
# Check if it is rcX-beta
|
||||||
|
if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then
|
||||||
|
t_value="${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
t_value="$suffix"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
base_version="$new_version"
|
||||||
|
t_value="0"
|
||||||
|
fi
|
||||||
|
# Export two values
|
||||||
|
new_base_version="$base_version"
|
||||||
|
new_build_version="${base_version}.${t_value}"
|
||||||
|
}
|
||||||
|
generate_build_version ${{ inputs.new_version }}
|
||||||
|
sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
|
||||||
if [ "${{ inputs.channel }}" != "stable" ]; then
|
if [ "${{ inputs.channel }}" != "stable" ]; then
|
||||||
jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
||||||
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
||||||
@ -103,7 +135,14 @@ jobs:
|
|||||||
chmod +x .github/scripts/rename-workspace.sh
|
chmod +x .github/scripts/rename-workspace.sh
|
||||||
.github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }}
|
.github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }}
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
|
sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
else
|
||||||
|
sed -i "s/jan_productname/Jan/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_mainbinaryname/jan/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
fi
|
fi
|
||||||
|
echo "---------nsis.template---------"
|
||||||
|
cat ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
- name: Build app
|
- name: Build app
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -98,9 +98,15 @@ jobs:
|
|||||||
# Update tauri.conf.json
|
# Update tauri.conf.json
|
||||||
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
|
||||||
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
|
||||||
|
jq '.bundle.windows.nsis.template = "tauri.bundle.windows.nsis.template"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
||||||
|
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
||||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json
|
jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json web-app/package.json
|
mv /tmp/package.json web-app/package.json
|
||||||
|
|
||||||
|
# Add sign commands to tauri.windows.conf.json
|
||||||
|
jq '.bundle.windows.signCommand = "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
||||||
|
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
||||||
|
|
||||||
# Update tauri plugin versions
|
# Update tauri plugin versions
|
||||||
|
|
||||||
jq --arg version "${{ inputs.new_version }}" '.version = $version' ./src-tauri/plugins/tauri-plugin-hardware/package.json > /tmp/package.json
|
jq --arg version "${{ inputs.new_version }}" '.version = $version' ./src-tauri/plugins/tauri-plugin-hardware/package.json > /tmp/package.json
|
||||||
@ -127,9 +133,35 @@ jobs:
|
|||||||
echo "---------./src-tauri/Cargo.toml---------"
|
echo "---------./src-tauri/Cargo.toml---------"
|
||||||
cat ./src-tauri/Cargo.toml
|
cat ./src-tauri/Cargo.toml
|
||||||
|
|
||||||
# Add sign commands to tauri.windows.conf.json
|
generate_build_version() {
|
||||||
jq '.bundle.windows.signCommand = "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"' ./src-tauri/tauri.windows.conf.json > /tmp/tauri.windows.conf.json
|
### Example
|
||||||
mv /tmp/tauri.windows.conf.json ./src-tauri/tauri.windows.conf.json
|
### input 0.5.6 output will be 0.5.6 and 0.5.6.0
|
||||||
|
### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2
|
||||||
|
### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213
|
||||||
|
local new_version="$1"
|
||||||
|
local base_version
|
||||||
|
local t_value
|
||||||
|
# Check if it has a "-"
|
||||||
|
if [[ "$new_version" == *-* ]]; then
|
||||||
|
base_version="${new_version%%-*}" # part before -
|
||||||
|
suffix="${new_version#*-}" # part after -
|
||||||
|
# Check if it is rcX-beta
|
||||||
|
if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then
|
||||||
|
t_value="${BASH_REMATCH[1]}"
|
||||||
|
else
|
||||||
|
t_value="$suffix"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
base_version="$new_version"
|
||||||
|
t_value="0"
|
||||||
|
fi
|
||||||
|
# Export two values
|
||||||
|
new_base_version="$base_version"
|
||||||
|
new_build_version="${base_version}.${t_value}"
|
||||||
|
}
|
||||||
|
generate_build_version ${{ inputs.new_version }}
|
||||||
|
sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
|
||||||
echo "---------tauri.windows.conf.json---------"
|
echo "---------tauri.windows.conf.json---------"
|
||||||
cat ./src-tauri/tauri.windows.conf.json
|
cat ./src-tauri/tauri.windows.conf.json
|
||||||
@ -163,7 +195,14 @@ jobs:
|
|||||||
chmod +x .github/scripts/rename-workspace.sh
|
chmod +x .github/scripts/rename-workspace.sh
|
||||||
.github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }}
|
.github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }}
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
|
sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
else
|
||||||
|
sed -i "s/jan_productname/Jan/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
sed -i "s/jan_mainbinaryname/jan/g" ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
fi
|
fi
|
||||||
|
echo "---------nsis.template---------"
|
||||||
|
cat ./src-tauri/tauri.bundle.windows.nsis.template
|
||||||
|
|
||||||
- name: Install AzureSignTool
|
- name: Install AzureSignTool
|
||||||
run: |
|
run: |
|
||||||
@ -234,8 +273,6 @@ jobs:
|
|||||||
# Upload for tauri updater
|
# Upload for tauri updater
|
||||||
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}
|
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}
|
||||||
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }}.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}.sig
|
aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }}.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}.sig
|
||||||
|
|
||||||
aws s3 cp ./src-tauri/target/release/bundle/msi/${{ steps.metadata.outputs.MSI_FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.MSI_FILE_NAME }}
|
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
|
||||||
@ -252,13 +289,3 @@ jobs:
|
|||||||
asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }}
|
asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }}
|
||||||
asset_name: ${{ steps.metadata.outputs.FILE_NAME }}
|
asset_name: ${{ steps.metadata.outputs.FILE_NAME }}
|
||||||
asset_content_type: application/octet-stream
|
asset_content_type: application/octet-stream
|
||||||
- name: Upload release assert if public provider is github
|
|
||||||
if: inputs.public_provider == 'github'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
uses: actions/upload-release-asset@v1.0.1
|
|
||||||
with:
|
|
||||||
upload_url: ${{ inputs.upload_url }}
|
|
||||||
asset_path: ./src-tauri/target/release/bundle/msi/${{ steps.metadata.outputs.MSI_FILE_NAME }}
|
|
||||||
asset_name: ${{ steps.metadata.outputs.MSI_FILE_NAME }}
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
|
|||||||
@ -143,7 +143,7 @@ jan/
|
|||||||
|
|
||||||
**Option 1: The Easy Way (Make)**
|
**Option 1: The Easy Way (Make)**
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/menloresearch/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
@ -152,8 +152,8 @@ make dev
|
|||||||
|
|
||||||
### Reporting Bugs
|
### Reporting Bugs
|
||||||
|
|
||||||
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/menloresearch/jan/issues)
|
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/janhq/jan/issues)
|
||||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/menloresearch/jan/issues/new)
|
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/janhq/jan/issues/new)
|
||||||
- Include your system specs and error logs - it helps a ton
|
- Include your system specs and error logs - it helps a ton
|
||||||
|
|
||||||
### Suggesting Enhancements
|
### Suggesting Enhancements
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
# Stage 1: Build stage with Node.js and Yarn v4
|
# Stage 1: Build stage with Node.js and Yarn v4
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
ARG JAN_API_BASE=https://api-dev.jan.ai/v1
|
ARG MENLO_PLATFORM_BASE_URL=https://api-dev.menlo.ai/v1
|
||||||
ENV JAN_API_BASE=$JAN_API_BASE
|
ENV MENLO_PLATFORM_BASE_URL=$MENLO_PLATFORM_BASE_URL
|
||||||
|
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
|
|||||||
1
Makefile
1
Makefile
@ -117,7 +117,6 @@ lint: install-and-build
|
|||||||
test: lint
|
test: lint
|
||||||
yarn download:bin
|
yarn download:bin
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
yarn download:windows-installer
|
|
||||||
endif
|
endif
|
||||||
yarn test
|
yarn test
|
||||||
yarn copy:assets:tauri
|
yarn copy:assets:tauri
|
||||||
|
|||||||
16
README.md
16
README.md
@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/menloresearch/jan"/>
|
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/janhq/jan"/>
|
||||||
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/menloresearch/jan"/>
|
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/janhq/jan"/>
|
||||||
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/menloresearch/jan"/>
|
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/janhq/jan"/>
|
||||||
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/menloresearch/jan"/>
|
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/janhq/jan"/>
|
||||||
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
|
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<a href="https://www.jan.ai/docs/desktop">Getting Started</a>
|
<a href="https://www.jan.ai/docs/desktop">Getting Started</a>
|
||||||
- <a href="https://discord.gg/Exe46xPMbK">Community</a>
|
- <a href="https://discord.gg/Exe46xPMbK">Community</a>
|
||||||
- <a href="https://jan.ai/changelog">Changelog</a>
|
- <a href="https://jan.ai/changelog">Changelog</a>
|
||||||
- <a href="https://github.com/menloresearch/jan/issues">Bug reports</a>
|
- <a href="https://github.com/janhq/jan/issues">Bug reports</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Jan is bringing the best of open-source AI in an easy-to-use product. Download and run LLMs with **full control** and **privacy**.
|
Jan is bringing the best of open-source AI in an easy-to-use product. Download and run LLMs with **full control** and **privacy**.
|
||||||
@ -48,7 +48,7 @@ The easiest way to get started is by downloading one of the following versions f
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
Download from [jan.ai](https://jan.ai/) or [GitHub Releases](https://github.com/menloresearch/jan/releases).
|
Download from [jan.ai](https://jan.ai/) or [GitHub Releases](https://github.com/janhq/jan/releases).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ For those who enjoy the scenic route:
|
|||||||
### Run with Make
|
### Run with Make
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/menloresearch/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
@ -128,7 +128,7 @@ Contributions welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full spiel
|
|||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
- **Bugs**: [GitHub Issues](https://github.com/menloresearch/jan/issues)
|
- **Bugs**: [GitHub Issues](https://github.com/janhq/jan/issues)
|
||||||
- **Business**: hello@jan.ai
|
- **Business**: hello@jan.ai
|
||||||
- **Jobs**: hr@jan.ai
|
- **Jobs**: hr@jan.ai
|
||||||
- **General Discussion**: [Discord](https://discord.gg/FTk2MvZwJH)
|
- **General Discussion**: [Discord](https://discord.gg/FTk2MvZwJH)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Core dependencies
|
# Core dependencies
|
||||||
cua-computer[all]~=0.3.5
|
cua-computer[all]~=0.3.5
|
||||||
cua-agent[all]~=0.3.0
|
cua-agent[all]~=0.3.0
|
||||||
cua-agent @ git+https://github.com/menloresearch/cua.git@compute-agent-0.3.0-patch#subdirectory=libs/python/agent
|
cua-agent @ git+https://github.com/janhq/cua.git@compute-agent-0.3.0-patch#subdirectory=libs/python/agent
|
||||||
|
|
||||||
# ReportPortal integration
|
# ReportPortal integration
|
||||||
reportportal-client~=5.6.5
|
reportportal-client~=5.6.5
|
||||||
|
|||||||
@ -25,8 +25,8 @@ export RANLIB_aarch64_linux_android="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x
|
|||||||
# Additional environment variables for Rust cross-compilation
|
# Additional environment variables for Rust cross-compilation
|
||||||
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
|
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
|
||||||
|
|
||||||
# Only set global CC and AR for Android builds (when TAURI_ANDROID_BUILD is set)
|
# Only set global CC and AR for Android builds (when IS_ANDROID is set)
|
||||||
if [ "$TAURI_ANDROID_BUILD" = "true" ]; then
|
if [ "$IS_ANDROID" = "true" ]; then
|
||||||
export CC="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
|
export CC="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang"
|
||||||
export AR="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar"
|
export AR="$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-ar"
|
||||||
echo "Global CC and AR set for Android build"
|
echo "Global CC and AR set for Android build"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import * as core from '@janhq/core'
|
|||||||
|
|
||||||
## Build an Extension
|
## Build an Extension
|
||||||
|
|
||||||
1. Download an extension template, for example, [https://github.com/menloresearch/extension-template](https://github.com/menloresearch/extension-template).
|
1. Download an extension template, for example, [https://github.com/janhq/extension-template](https://github.com/janhq/extension-template).
|
||||||
|
|
||||||
2. Update the source code:
|
2. Update the source code:
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
"@vitest/coverage-v8": "^2.1.8",
|
"@vitest/coverage-v8": "^2.1.8",
|
||||||
"@vitest/ui": "^2.1.8",
|
"@vitest/ui": "^2.1.8",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"happy-dom": "^15.11.6",
|
"happy-dom": "^20.0.0",
|
||||||
"pacote": "^21.0.0",
|
"pacote": "^21.0.0",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
|||||||
@ -11,6 +11,8 @@ export enum ExtensionTypeEnum {
|
|||||||
HuggingFace = 'huggingFace',
|
HuggingFace = 'huggingFace',
|
||||||
Engine = 'engine',
|
Engine = 'engine',
|
||||||
Hardware = 'hardware',
|
Hardware = 'hardware',
|
||||||
|
RAG = 'rag',
|
||||||
|
VectorDB = 'vectorDB',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionType {
|
export interface ExtensionType {
|
||||||
|
|||||||
@ -182,6 +182,7 @@ export interface SessionInfo {
|
|||||||
port: number // llama-server output port (corrected from portid)
|
port: number // llama-server output port (corrected from portid)
|
||||||
model_id: string //name of the model
|
model_id: string //name of the model
|
||||||
model_path: string // path of the loaded model
|
model_path: string // path of the loaded model
|
||||||
|
is_embedding: boolean
|
||||||
api_key: string
|
api_key: string
|
||||||
mmproj_path?: string
|
mmproj_path?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,3 +23,8 @@ export { MCPExtension } from './mcp'
|
|||||||
* Base AI Engines.
|
* Base AI Engines.
|
||||||
*/
|
*/
|
||||||
export * from './engines'
|
export * from './engines'
|
||||||
|
|
||||||
|
export { RAGExtension, RAG_INTERNAL_SERVER } from './rag'
|
||||||
|
export type { AttachmentInput, IngestAttachmentsResult } from './rag'
|
||||||
|
export { VectorDBExtension } from './vector-db'
|
||||||
|
export type { SearchMode, VectorDBStatus, VectorChunkInput, VectorSearchResult, AttachmentFileInfo, VectorDBFileInput, VectorDBIngestOptions } from './vector-db'
|
||||||
|
|||||||
36
core/src/browser/extensions/rag.ts
Normal file
36
core/src/browser/extensions/rag.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
|
import type { MCPTool, MCPToolCallResult } from '../../types'
|
||||||
|
import type { AttachmentFileInfo } from './vector-db'
|
||||||
|
|
||||||
|
export interface AttachmentInput {
|
||||||
|
path: string
|
||||||
|
name?: string
|
||||||
|
type?: string
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IngestAttachmentsResult {
|
||||||
|
filesProcessed: number
|
||||||
|
chunksInserted: number
|
||||||
|
files: AttachmentFileInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RAG_INTERNAL_SERVER = 'rag-internal'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAG extension base: exposes RAG tools and orchestration API.
|
||||||
|
*/
|
||||||
|
export abstract class RAGExtension extends BaseExtension {
|
||||||
|
type(): ExtensionTypeEnum | undefined {
|
||||||
|
return ExtensionTypeEnum.RAG
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getTools(): Promise<MCPTool[]>
|
||||||
|
/**
|
||||||
|
* Lightweight list of tool names for quick routing/lookup.
|
||||||
|
*/
|
||||||
|
abstract getToolNames(): Promise<string[]>
|
||||||
|
abstract callTool(toolName: string, args: Record<string, unknown>): Promise<MCPToolCallResult>
|
||||||
|
|
||||||
|
abstract ingestAttachments(threadId: string, files: AttachmentInput[]): Promise<IngestAttachmentsResult>
|
||||||
|
}
|
||||||
82
core/src/browser/extensions/vector-db.ts
Normal file
82
core/src/browser/extensions/vector-db.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
|
|
||||||
|
export type SearchMode = 'auto' | 'ann' | 'linear'
|
||||||
|
|
||||||
|
export interface VectorDBStatus {
|
||||||
|
ann_available: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorChunkInput {
|
||||||
|
text: string
|
||||||
|
embedding: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorSearchResult {
|
||||||
|
id: string
|
||||||
|
text: string
|
||||||
|
score?: number
|
||||||
|
file_id: string
|
||||||
|
chunk_file_order: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttachmentFileInfo {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
path?: string
|
||||||
|
type?: string
|
||||||
|
size?: number
|
||||||
|
chunk_count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// High-level input types for file ingestion
|
||||||
|
export interface VectorDBFileInput {
|
||||||
|
path: string
|
||||||
|
name?: string
|
||||||
|
type?: string
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorDBIngestOptions {
|
||||||
|
chunkSize: number
|
||||||
|
chunkOverlap: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector DB extension base: abstraction over local vector storage and search.
|
||||||
|
*/
|
||||||
|
export abstract class VectorDBExtension extends BaseExtension {
|
||||||
|
type(): ExtensionTypeEnum | undefined {
|
||||||
|
return ExtensionTypeEnum.VectorDB
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getStatus(): Promise<VectorDBStatus>
|
||||||
|
abstract createCollection(threadId: string, dimension: number): Promise<void>
|
||||||
|
abstract insertChunks(
|
||||||
|
threadId: string,
|
||||||
|
fileId: string,
|
||||||
|
chunks: VectorChunkInput[]
|
||||||
|
): Promise<void>
|
||||||
|
abstract ingestFile(
|
||||||
|
threadId: string,
|
||||||
|
file: VectorDBFileInput,
|
||||||
|
opts: VectorDBIngestOptions
|
||||||
|
): Promise<AttachmentFileInfo>
|
||||||
|
abstract searchCollection(
|
||||||
|
threadId: string,
|
||||||
|
query_embedding: number[],
|
||||||
|
limit: number,
|
||||||
|
threshold: number,
|
||||||
|
mode?: SearchMode,
|
||||||
|
fileIds?: string[]
|
||||||
|
): Promise<VectorSearchResult[]>
|
||||||
|
abstract deleteChunks(threadId: string, ids: string[]): Promise<void>
|
||||||
|
abstract deleteFile(threadId: string, fileId: string): Promise<void>
|
||||||
|
abstract deleteCollection(threadId: string): Promise<void>
|
||||||
|
abstract listAttachments(threadId: string, limit?: number): Promise<AttachmentFileInfo[]>
|
||||||
|
abstract getChunks(
|
||||||
|
threadId: string,
|
||||||
|
fileId: string,
|
||||||
|
startOrder: number,
|
||||||
|
endOrder: number
|
||||||
|
): Promise<VectorSearchResult[]>
|
||||||
|
}
|
||||||
@ -12,6 +12,8 @@ export type SettingComponentProps = {
|
|||||||
extensionName?: string
|
extensionName?: string
|
||||||
requireModelReload?: boolean
|
requireModelReload?: boolean
|
||||||
configType?: ConfigType
|
configType?: ConfigType
|
||||||
|
titleKey?: string
|
||||||
|
descriptionKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfigType = 'runtime' | 'setting'
|
export type ConfigType = 'runtime' | 'setting'
|
||||||
|
|||||||
@ -18,7 +18,7 @@ We try to **keep routes consistent** to maintain SEO.
|
|||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
Refer to the [Contributing Guide](https://github.com/menloresearch/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
||||||
|
|
||||||
### Pre-requisites and Installation
|
### Pre-requisites and Installation
|
||||||
|
|
||||||
|
|||||||
BIN
docs/public/assets/images/changelog/jan-release-v0.7.0.jpeg
Normal file
BIN
docs/public/assets/images/changelog/jan-release-v0.7.0.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
@ -1581,7 +1581,7 @@
|
|||||||
},
|
},
|
||||||
"cover": {
|
"cover": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "https://raw.githubusercontent.com/menloresearch/jan/main/models/trinity-v1.2-7b/cover.png"
|
"example": "https://raw.githubusercontent.com/janhq/jan/main/models/trinity-v1.2-7b/cover.png"
|
||||||
},
|
},
|
||||||
"engine": {
|
"engine": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export const APIReference = () => {
|
|||||||
<ApiReferenceReact
|
<ApiReferenceReact
|
||||||
configuration={{
|
configuration={{
|
||||||
spec: {
|
spec: {
|
||||||
url: 'https://raw.githubusercontent.com/menloresearch/docs/main/public/openapi/jan.json',
|
url: 'https://raw.githubusercontent.com/janhq/docs/main/public/openapi/jan.json',
|
||||||
},
|
},
|
||||||
theme: 'alternate',
|
theme: 'alternate',
|
||||||
hideModels: true,
|
hideModels: true,
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const Changelog = () => {
|
|||||||
<p className="text-base mt-2 leading-relaxed">
|
<p className="text-base mt-2 leading-relaxed">
|
||||||
Latest release updates from the Jan team. Check out our
|
Latest release updates from the Jan team. Check out our
|
||||||
<a
|
<a
|
||||||
href="https://github.com/orgs/menloresearch/projects/30"
|
href="https://github.com/orgs/janhq/projects/30"
|
||||||
className="text-blue-600 dark:text-blue-400 cursor-pointer"
|
className="text-blue-600 dark:text-blue-400 cursor-pointer"
|
||||||
>
|
>
|
||||||
Roadmap
|
Roadmap
|
||||||
@ -150,7 +150,7 @@ const Changelog = () => {
|
|||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/menloresearch/jan/releases"
|
href="https://github.com/janhq/jan/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center"
|
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export default function CardDownload({ lastRelease }: Props) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...system,
|
...system,
|
||||||
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
||||||
size: asset ? formatFileSize(asset.size) : undefined,
|
size: asset ? formatFileSize(asset.size) : undefined,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -139,7 +139,7 @@ const DropdownDownload = ({ lastRelease }: Props) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...system,
|
...system,
|
||||||
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
||||||
size: asset ? formatFileSize(asset.size) : undefined,
|
size: asset ? formatFileSize(asset.size) : undefined,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const BuiltWithLove = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
|
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 "
|
className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 "
|
||||||
>
|
>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const Hero = () => {
|
|||||||
<div className="mt-10 text-center">
|
<div className="mt-10 text-center">
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/menloresearch/jan/releases"
|
href="https://github.com/janhq/jan/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="hidden lg:inline-block"
|
className="hidden lg:inline-block"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -95,7 +95,7 @@ const Home = () => {
|
|||||||
<div className="container mx-auto relative z-10">
|
<div className="container mx-auto relative z-10">
|
||||||
<div className="flex justify-center items-center mt-14 lg:mt-20 px-4">
|
<div className="flex justify-center items-center mt-14 lg:mt-20 px-4">
|
||||||
<a
|
<a
|
||||||
href={`https://github.com/menloresearch/jan/releases/tag/${lastVersion}`}
|
href={`https://github.com/janhq/jan/releases/tag/${lastVersion}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="bg-black/40 px-3 lg:px-4 rounded-full h-10 inline-flex items-center max-w-full animate-fade-in delay-100"
|
className="bg-black/40 px-3 lg:px-4 rounded-full h-10 inline-flex items-center max-w-full animate-fade-in delay-100"
|
||||||
@ -270,7 +270,7 @@ const Home = () => {
|
|||||||
data-delay="600"
|
data-delay="600"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@ -387,7 +387,7 @@ const Home = () => {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
className="hidden md:block"
|
className="hidden md:block"
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@ -413,7 +413,7 @@ const Home = () => {
|
|||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
className="md:hidden mt-4 block w-full"
|
className="md:hidden mt-4 block w-full"
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -95,7 +95,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
|
|||||||
})}
|
})}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan/releases/latest"
|
href="https://github.com/janhq/jan/releases/latest"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@ -141,7 +141,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
|
|||||||
<FaLinkedinIn className="size-5" />
|
<FaLinkedinIn className="size-5" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="rounded-lg flex items-center justify-center"
|
className="rounded-lg flex items-center justify-center"
|
||||||
@ -156,7 +156,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
|
|||||||
{/* Mobile Download Button and Hamburger */}
|
{/* Mobile Download Button and Hamburger */}
|
||||||
<div className="lg:hidden flex items-center gap-3">
|
<div className="lg:hidden flex items-center gap-3">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan/releases/latest"
|
href="https://github.com/janhq/jan/releases/latest"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
@ -278,7 +278,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
|
|||||||
<FaLinkedinIn className="size-5" />
|
<FaLinkedinIn className="size-5" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan"
|
href="https://github.com/janhq/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-black rounded-lg flex items-center justify-center"
|
className="text-black rounded-lg flex items-center justify-center"
|
||||||
@ -296,7 +296,7 @@ const Navbar = ({ noScroll }: { noScroll?: boolean }) => {
|
|||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/menloresearch/jan/releases/latest"
|
href="https://github.com/janhq/jan/releases/latest"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -120,7 +120,7 @@ export function DropdownButton({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...option,
|
...option,
|
||||||
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${fileName}`,
|
href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${fileName}`,
|
||||||
size: asset ? formatFileSize(asset.size) : 'N/A',
|
size: asset ? formatFileSize(asset.size) : 'N/A',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -18,7 +18,7 @@ description: Development setup, workflow, and contribution guidelines for Jan Se
|
|||||||
|
|
||||||
1. **Clone Repository**
|
1. **Clone Repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/menloresearch/jan-server
|
git clone https://github.com/janhq/jan-server
|
||||||
cd jan-server
|
cd jan-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ Jan Server currently supports minikube for local development. Production Kuberne
|
|||||||
|
|
||||||
1. **Clone the repository**
|
1. **Clone the repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/menloresearch/jan-server
|
git clone https://github.com/janhq/jan-server
|
||||||
cd jan-server
|
cd jan-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -24,4 +24,4 @@ Fixes 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.5).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.5).
|
||||||
@ -24,4 +24,4 @@ Jan now supports Mistral's new model Codestral. Thanks [Bartowski](https://huggi
|
|||||||
|
|
||||||
More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov)
|
More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov)
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.0).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.0).
|
||||||
|
|||||||
@ -28,4 +28,4 @@ Jan now understands LaTeX, allowing users to process and understand complex math
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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/janhq/jan/releases/tag/v0.4.12).
|
||||||
|
|||||||
@ -28,4 +28,4 @@ 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/janhq/jan/releases/tag/v0.4.13)
|
||||||
|
|||||||
@ -16,4 +16,4 @@ More GGUF models can run in Jan - we rebased to llama.cpp b2961.
|
|||||||
|
|
||||||
Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models.
|
Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.14).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.4.14).
|
||||||
|
|||||||
@ -26,4 +26,4 @@ We've updated to llama.cpp b3088 for better performance - thanks to [GG](https:/
|
|||||||
- Reduced chat font weight (back to normal!)
|
- Reduced chat font weight (back to normal!)
|
||||||
- Restored the maximize button
|
- Restored the maximize button
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.1).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.1).
|
||||||
|
|||||||
@ -32,4 +32,4 @@ We've restored the tooltip hover functionality, which makes it easier to access
|
|||||||
|
|
||||||
The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix.
|
The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.2).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.2).
|
||||||
|
|||||||
@ -23,4 +23,4 @@ We've been working on stability issues over the last few weeks. Jan is now more
|
|||||||
- Fixed the GPU memory utilization bar
|
- Fixed the GPU memory utilization bar
|
||||||
- Some UX and copy improvements
|
- Some UX and copy improvements
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.3).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.3).
|
||||||
|
|||||||
@ -32,4 +32,4 @@ Switching between threads used to reset your instruction settings. That’s fixe
|
|||||||
### Minor UI Tweaks & Bug Fixes
|
### Minor UI Tweaks & Bug Fixes
|
||||||
We’ve also resolved issues with the input slider on the right panel and tackled several smaller bugs to keep everything running smoothly.
|
We’ve also resolved issues with the input slider on the right panel and tackled several smaller bugs to keep everything running smoothly.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.4).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.4).
|
||||||
|
|||||||
@ -23,4 +23,4 @@ Fixes 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.7).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.7).
|
||||||
@ -22,4 +22,4 @@ Jan v0.5.9 is here: fixing what needed fixing
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.9).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.9).
|
||||||
@ -22,4 +22,4 @@ and various UI/UX enhancements 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.8).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.8).
|
||||||
@ -19,4 +19,4 @@ Jan v0.5.10 is live: Jan is faster, smoother, and more reliable.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.10).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.10).
|
||||||
@ -23,4 +23,4 @@ Jan v0.5.11 is here - critical issues fixed, Mac installation updated.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.11).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.11).
|
||||||
@ -25,4 +25,4 @@ Jan v0.5.11 is here - critical issues fixed, Mac installation updated.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.12).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.12).
|
||||||
@ -20,4 +20,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.13).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.13).
|
||||||
|
|||||||
@ -33,4 +33,4 @@ Llama
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.14).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.14).
|
||||||
|
|||||||
@ -25,4 +25,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.15).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.15).
|
||||||
|
|||||||
@ -26,4 +26,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.16).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.16).
|
||||||
|
|||||||
@ -20,4 +20,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.17).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.17).
|
||||||
|
|||||||
@ -18,4 +18,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.1).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.1).
|
||||||
@ -18,4 +18,4 @@ import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.3).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.3).
|
||||||
@ -23,4 +23,4 @@ new MCP examples.
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.5).
|
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.5).
|
||||||
@ -116,4 +116,4 @@ integrations. Stay tuned!
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For the complete list of changes, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.6).
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.6).
|
||||||
|
|||||||
@ -89,4 +89,4 @@ We're continuing to optimize performance for large models, expand MCP integratio
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For the complete list of changes, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.7).
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.7).
|
||||||
|
|||||||
@ -74,4 +74,4 @@ v0.6.8 focuses on stability and real workflows: major llama.cpp hardening, two n
|
|||||||
|
|
||||||
Update your Jan or [download the latest](https://jan.ai/).
|
Update your Jan or [download the latest](https://jan.ai/).
|
||||||
|
|
||||||
For the complete list of changes, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.6.8).
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.6.8).
|
||||||
|
|||||||
28
docs/src/pages/changelog/2025-10-02-jan-projects.mdx
Normal file
28
docs/src/pages/changelog/2025-10-02-jan-projects.mdx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: "Jan v0.7.0: Jan Projects"
|
||||||
|
version: 0.7.0
|
||||||
|
description: "Jan v0.7.0 introduces Projects, model renaming, llama.cpp auto-tuning, model stats, and Azure support."
|
||||||
|
date: 2025-10-02
|
||||||
|
ogImage: "/assets/images/changelog/jan-release-v0.7.0.jpeg"
|
||||||
|
---
|
||||||
|
|
||||||
|
import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
||||||
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
|
<ChangelogHeader title="Jan v0.7.0" date="2025-10-01" ogImage="/assets/images/changelog/jan-release-v0.7.0.jpeg" />
|
||||||
|
|
||||||
|
## Jan v0.7.0: Jan Projects
|
||||||
|
|
||||||
|
Jan v0.7.0 is live! This release focuses on helping you organize your workspace and better understand how models run.
|
||||||
|
|
||||||
|
### What’s new
|
||||||
|
- **Projects**: Group related chats under one project for a cleaner workflow.
|
||||||
|
- **Rename models**: Give your models custom names for easier identification.
|
||||||
|
- **Model context stats**: See context usage when a model runs.
|
||||||
|
- **Auto-loaded cloud models**: Cloud model names now appear automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Update your Jan or [download the latest version](https://jan.ai/).
|
||||||
|
|
||||||
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.7.0).
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: "Jan v0.7.1: Fixes Windows Version Revert & OpenRouter Models"
|
||||||
|
version: 0.7.1
|
||||||
|
description: "Jan v0.7.1 focuses on bug fixes, including a windows version revert and improvements to OpenRouter models."
|
||||||
|
date: 2025-10-03
|
||||||
|
---
|
||||||
|
|
||||||
|
import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
||||||
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
|
<ChangelogHeader title="Jan v0.7.1" date="2025-10-03" />
|
||||||
|
|
||||||
|
### Bug Fixes: Windows Version Revert & OpenRouter Models
|
||||||
|
|
||||||
|
#### Two quick fixes:
|
||||||
|
- Jan no longer reverts to an older version on load
|
||||||
|
- OpenRouter can now add models again
|
||||||
|
- Add headers for anthropic request to fetch models
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Update your Jan or [download the latest version](https://jan.ai/).
|
||||||
|
|
||||||
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.7.1).
|
||||||
|
|
||||||
|
|
||||||
25
docs/src/pages/changelog/2025-10-16-jan-security-update.mdx
Normal file
25
docs/src/pages/changelog/2025-10-16-jan-security-update.mdx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: "Jan v0.7.2: Security Update"
|
||||||
|
version: 0.7.2
|
||||||
|
description: "Jan v0.7.2 updates the happy-dom dependency to v20.0.0 to address a recently disclosed sandbox vulnerability."
|
||||||
|
date: 2025-10-16
|
||||||
|
---
|
||||||
|
|
||||||
|
import ChangelogHeader from "@/components/Changelog/ChangelogHeader"
|
||||||
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
|
<ChangelogHeader title="Jan v0.7.2" date="2025-10-16" />
|
||||||
|
|
||||||
|
## Jan v0.7.2: Security Update (happy-dom v20)
|
||||||
|
|
||||||
|
This release focuses on **security and stability improvements**.
|
||||||
|
It updates the `happy-dom` dependency to the latest version to address a recently disclosed vulnerability.
|
||||||
|
|
||||||
|
### Security Fix
|
||||||
|
- Updated `happy-dom` to **^20.0.0**, preventing untrusted JavaScript executed within HAPPY DOM from accessing process-level functions and executing arbitrary code outside the intended sandbox.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Update your Jan or [download the latest version](https://jan.ai/).
|
||||||
|
|
||||||
|
For the complete list of changes, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.7.2).
|
||||||
@ -41,7 +41,7 @@ Jan is an open-source replacement for ChatGPT:
|
|||||||
|
|
||||||
Jan is a full [product suite](https://en.wikipedia.org/wiki/Software_suite) that offers an alternative to Big AI:
|
Jan is a full [product suite](https://en.wikipedia.org/wiki/Software_suite) that offers an alternative to Big AI:
|
||||||
- [Jan Desktop](/docs/desktop/quickstart): macOS, Windows, and Linux apps with offline mode
|
- [Jan Desktop](/docs/desktop/quickstart): macOS, Windows, and Linux apps with offline mode
|
||||||
- [Jan Web](https://chat.jan.ai): Jan on browser, a direct alternative to chatgpt.com
|
- [Jan Web](https://chat.menlo.ai): Jan on browser, a direct alternative to chatgpt.com
|
||||||
- Jan Mobile: iOS and Android apps (Coming Soon)
|
- Jan Mobile: iOS and Android apps (Coming Soon)
|
||||||
- [Jan Server](/docs/server): deploy locally, in your cloud, or on-prem
|
- [Jan Server](/docs/server): deploy locally, in your cloud, or on-prem
|
||||||
- [Jan Models](/docs/models): Open-source models optimized for deep research, tool use, and reasoning
|
- [Jan Models](/docs/models): Open-source models optimized for deep research, tool use, and reasoning
|
||||||
|
|||||||
@ -135,5 +135,5 @@ Min-p: 0.0
|
|||||||
|
|
||||||
## 🤝 Community & Support
|
## 🤝 Community & Support
|
||||||
- **Discussions**: [HuggingFace Community](https://huggingface.co/Menlo/Jan-nano-128k/discussions)
|
- **Discussions**: [HuggingFace Community](https://huggingface.co/Menlo/Jan-nano-128k/discussions)
|
||||||
- **Issues**: [GitHub Repository](https://github.com/menloresearch/deep-research/issues)
|
- **Issues**: [GitHub Repository](https://github.com/janhq/deep-research/issues)
|
||||||
- **Discord**: Join our research community for tips and best practices
|
- **Discord**: Join our research community for tips and best practices
|
||||||
|
|||||||
@ -9,7 +9,7 @@ Jan Server is a comprehensive self-hosted AI server platform that provides OpenA
|
|||||||
|
|
||||||
Jan Server is a Kubernetes-native platform consisting of multiple microservices that work together to provide a complete AI infrastructure solution. It offers:
|
Jan Server is a Kubernetes-native platform consisting of multiple microservices that work together to provide a complete AI infrastructure solution. It offers:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Key Features
|
### Key Features
|
||||||
- **OpenAI-Compatible API**: Full compatibility with OpenAI's chat completion API
|
- **OpenAI-Compatible API**: Full compatibility with OpenAI's chat completion API
|
||||||
|
|||||||
@ -3,7 +3,7 @@ title: Development
|
|||||||
description: Development setup, workflow, and contribution guidelines for Jan Server.
|
description: Development setup, workflow, and contribution guidelines for Jan Server.
|
||||||
---
|
---
|
||||||
## Core Domain Models
|
## Core Domain Models
|
||||||

|

|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@ -42,7 +42,7 @@ description: Development setup, workflow, and contribution guidelines for Jan Se
|
|||||||
|
|
||||||
1. **Clone Repository**
|
1. **Clone Repository**
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/menloresearch/jan-server
|
git clone https://github.com/janhq/jan-server
|
||||||
cd jan-server
|
cd jan-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ Jan Server is a Kubernetes-native platform consisting of multiple microservices
|
|||||||
- **Monitoring & Profiling**: Built-in performance monitoring and health checks
|
- **Monitoring & Profiling**: Built-in performance monitoring and health checks
|
||||||
|
|
||||||
## System Architecture
|
## System Architecture
|
||||||

|

|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
### Jan API Gateway
|
### Jan API Gateway
|
||||||
|
|||||||
@ -19,7 +19,7 @@ keywords:
|
|||||||
import Download from "@/components/Download"
|
import Download from "@/components/Download"
|
||||||
|
|
||||||
export const getStaticProps = async() => {
|
export const getStaticProps = async() => {
|
||||||
const resRelease = await fetch('https://api.github.com/repos/menloresearch/jan/releases/latest')
|
const resRelease = await fetch('https://api.github.com/repos/janhq/jan/releases/latest')
|
||||||
const release = await resRelease.json()
|
const release = await resRelease.json()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -19,9 +19,9 @@ keywords:
|
|||||||
import Home from "@/components/Home"
|
import Home from "@/components/Home"
|
||||||
|
|
||||||
export const getStaticProps = async() => {
|
export const getStaticProps = async() => {
|
||||||
const resReleaseLatest = await fetch('https://api.github.com/repos/menloresearch/jan/releases/latest')
|
const resReleaseLatest = await fetch('https://api.github.com/repos/janhq/jan/releases/latest')
|
||||||
const resRelease = await fetch('https://api.github.com/repos/menloresearch/jan/releases?per_page=500')
|
const resRelease = await fetch('https://api.github.com/repos/janhq/jan/releases?per_page=500')
|
||||||
const resRepo = await fetch('https://api.github.com/repos/menloresearch/jan')
|
const resRepo = await fetch('https://api.github.com/repos/janhq/jan')
|
||||||
const repo = await resRepo.json()
|
const repo = await resRepo.json()
|
||||||
const latestRelease = await resReleaseLatest.json()
|
const latestRelease = await resReleaseLatest.json()
|
||||||
const release = await resRelease.json()
|
const release = await resRelease.json()
|
||||||
|
|||||||
@ -14,12 +14,12 @@ import CTABlog from '@/components/Blog/CTA'
|
|||||||
|
|
||||||
Jan now supports [NVIDIA TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM) in addition to [llama.cpp](https://github.com/ggerganov/llama.cpp), making Jan multi-engine and ultra-fast for users with Nvidia GPUs.
|
Jan now supports [NVIDIA TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM) in addition to [llama.cpp](https://github.com/ggerganov/llama.cpp), making Jan multi-engine and ultra-fast for users with Nvidia GPUs.
|
||||||
|
|
||||||
We've been excited for TensorRT-LLM for a while, and [had a lot of fun implementing it](https://github.com/menloresearch/nitro-tensorrt-llm). As part of the process, we've run some benchmarks, to see how TensorRT-LLM fares on consumer hardware (e.g. [4090s](https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/), [3090s](https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/)) we commonly see in the [Jan's hardware community](https://discord.com/channels/1107178041848909847/1201834752206974996).
|
We've been excited for TensorRT-LLM for a while, and [had a lot of fun implementing it](https://github.com/janhq/nitro-tensorrt-llm). As part of the process, we've run some benchmarks, to see how TensorRT-LLM fares on consumer hardware (e.g. [4090s](https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/), [3090s](https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/)) we commonly see in the [Jan's hardware community](https://discord.com/channels/1107178041848909847/1201834752206974996).
|
||||||
|
|
||||||
<Callout type="info" >
|
<Callout type="info" >
|
||||||
**Give it a try!** Jan's TensorRT-LLM extension is available in Jan v0.4.9. We precompiled some TensorRT-LLM models for you to try: `Mistral 7b`, `TinyLlama-1.1b`, `TinyJensen-1.1b` 😂
|
**Give it a try!** Jan's TensorRT-LLM extension is available in Jan v0.4.9. We precompiled some TensorRT-LLM models for you to try: `Mistral 7b`, `TinyLlama-1.1b`, `TinyJensen-1.1b` 😂
|
||||||
|
|
||||||
Bugs or feedback? Let us know on [GitHub](https://github.com/menloresearch/jan) or via [Discord](https://discord.com/channels/1107178041848909847/1201832734704795688).
|
Bugs or feedback? Let us know on [GitHub](https://github.com/janhq/jan) or via [Discord](https://discord.com/channels/1107178041848909847/1201832734704795688).
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
<Callout type="info" >
|
<Callout type="info" >
|
||||||
|
|||||||
@ -70,34 +70,34 @@ brief survey of how other players approach deep research:
|
|||||||
| Kimi | Interactive synthesis | 50–100 | 30–60+ | PDF, Interactive website | Free |
|
| Kimi | Interactive synthesis | 50–100 | 30–60+ | PDF, Interactive website | Free |
|
||||||
|
|
||||||
In our testing, we used the following prompt to assess the quality of the generated report by
|
In our testing, we used the following prompt to assess the quality of the generated report by
|
||||||
the providers above. You can refer to the reports generated [here](https://github.com/menloresearch/prompt-experiments).
|
the providers above. You can refer to the reports generated [here](https://github.com/janhq/prompt-experiments).
|
||||||
|
|
||||||
```
|
```
|
||||||
Generate a comprehensive report about the state of AI in the past week. Include all
|
Generate a comprehensive report about the state of AI in the past week. Include all
|
||||||
new model releases and notable architectural improvements from a variety of sources.
|
new model releases and notable architectural improvements from a variety of sources.
|
||||||
```
|
```
|
||||||
|
|
||||||
[Google's generated report](https://github.com/menloresearch/prompt-experiments/blob/main/Gemini%202.5%20Flash%20Report.pdf) was the most verbose, with a whopping 23 pages that reads
|
[Google's generated report](https://github.com/janhq/prompt-experiments/blob/main/Gemini%202.5%20Flash%20Report.pdf) was the most verbose, with a whopping 23 pages that reads
|
||||||
like a professional intelligence briefing. It opens with an executive summary,
|
like a professional intelligence briefing. It opens with an executive summary,
|
||||||
systematically categorizes developments, and provides forward-looking strategic
|
systematically categorizes developments, and provides forward-looking strategic
|
||||||
insights—connecting OpenAI's open-weight release to broader democratization trends
|
insights—connecting OpenAI's open-weight release to broader democratization trends
|
||||||
and linking infrastructure investments to competitive positioning.
|
and linking infrastructure investments to competitive positioning.
|
||||||
|
|
||||||
[OpenAI](https://github.com/menloresearch/prompt-experiments/blob/main/OpenAI%20Deep%20Research.pdf) produced the most citation-heavy output with 134 references throughout 10 pages
|
[OpenAI](https://github.com/janhq/prompt-experiments/blob/main/OpenAI%20Deep%20Research.pdf) produced the most citation-heavy output with 134 references throughout 10 pages
|
||||||
(albeit most of them being from the same source).
|
(albeit most of them being from the same source).
|
||||||
|
|
||||||
[Perplexity](https://github.com/menloresearch/prompt-experiments/blob/main/Perplexity%20Deep%20Research.pdf) delivered the most actionable 6-page report that maximizes information
|
[Perplexity](https://github.com/janhq/prompt-experiments/blob/main/Perplexity%20Deep%20Research.pdf) delivered the most actionable 6-page report that maximizes information
|
||||||
density while maintaining scannability. Despite being the shortest, it captures all
|
density while maintaining scannability. Despite being the shortest, it captures all
|
||||||
major developments with sufficient context for decision-making.
|
major developments with sufficient context for decision-making.
|
||||||
|
|
||||||
[Claude](https://github.com/menloresearch/prompt-experiments/blob/main/Claude%20Deep%20Research.pdf) produced a comprehensive analysis that interestingly ignored the time constraint,
|
[Claude](https://github.com/janhq/prompt-experiments/blob/main/Claude%20Deep%20Research.pdf) produced a comprehensive analysis that interestingly ignored the time constraint,
|
||||||
covering an 8-month period from January-August 2025 instead of the requested week (Jul 31-Aug
|
covering an 8-month period from January-August 2025 instead of the requested week (Jul 31-Aug
|
||||||
7th 2025). Rather than cataloging recent events, Claude traced the evolution of trends over months.
|
7th 2025). Rather than cataloging recent events, Claude traced the evolution of trends over months.
|
||||||
|
|
||||||
[Grok](https://github.com/menloresearch/prompt-experiments/blob/main/Grok%203%20Deep%20Research.pdf) produced a well-structured but relatively shallow 5-page academic-style report that
|
[Grok](https://github.com/janhq/prompt-experiments/blob/main/Grok%203%20Deep%20Research.pdf) produced a well-structured but relatively shallow 5-page academic-style report that
|
||||||
read more like an event catalog than strategic analysis.
|
read more like an event catalog than strategic analysis.
|
||||||
|
|
||||||
[Kimi](https://github.com/menloresearch/prompt-experiments/blob/main/Kimi%20AI%20Deep%20Research.pdf) produced a comprehensive 13-page report with systematic organization covering industry developments, research breakthroughs, and policy changes, but notably lacks proper citations throughout most of the content despite claiming to use 50-100 sources.
|
[Kimi](https://github.com/janhq/prompt-experiments/blob/main/Kimi%20AI%20Deep%20Research.pdf) produced a comprehensive 13-page report with systematic organization covering industry developments, research breakthroughs, and policy changes, but notably lacks proper citations throughout most of the content despite claiming to use 50-100 sources.
|
||||||
|
|
||||||
### Understanding Search Strategies
|
### Understanding Search Strategies
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import CTABlog from '@/components/Blog/CTA'
|
|||||||
|
|
||||||
## Abstract
|
## Abstract
|
||||||
|
|
||||||
We present a straightforward approach to customizing small, open-source models using fine-tuning and RAG that outperforms GPT-3.5 for specialized use cases. With it, we achieved superior Q&A results of [technical documentation](https://nitro.jan.ai/docs) for a small codebase [codebase](https://github.com/menloresearch/nitro).
|
We present a straightforward approach to customizing small, open-source models using fine-tuning and RAG that outperforms GPT-3.5 for specialized use cases. With it, we achieved superior Q&A results of [technical documentation](https://nitro.jan.ai/docs) for a small codebase [codebase](https://github.com/janhq/nitro).
|
||||||
|
|
||||||
In short, (1) extending a general foundation model like [Mistral](https://huggingface.co/mistralai/Mistral-7B-v0.1) with strong math and coding, and (2) training it over a high-quality, synthetic dataset generated from the intended corpus, and (3) adding RAG capabilities, can lead to significant accuracy improvements.
|
In short, (1) extending a general foundation model like [Mistral](https://huggingface.co/mistralai/Mistral-7B-v0.1) with strong math and coding, and (2) training it over a high-quality, synthetic dataset generated from the intended corpus, and (3) adding RAG capabilities, can lead to significant accuracy improvements.
|
||||||
|
|
||||||
@ -93,11 +93,11 @@ This final model can be found [here on Huggingface](https://huggingface.co/jan-h
|
|||||||
|
|
||||||
As an additional step, we also added [Retrieval Augmented Generation (RAG)](https://blogs.nvidia.com/blog/what-is-retrieval-augmented-generation/) as an experiment parameter.
|
As an additional step, we also added [Retrieval Augmented Generation (RAG)](https://blogs.nvidia.com/blog/what-is-retrieval-augmented-generation/) as an experiment parameter.
|
||||||
|
|
||||||
A simple RAG setup was done using **[Llamaindex](https://www.llamaindex.ai/)** and the **[bge-en-base-v1.5 embedding](https://huggingface.co/BAAI/bge-base-en-v1.5)** model for efficient documentation retrieval and question-answering. You can find the RAG implementation [here](https://github.com/menloresearch/open-foundry/blob/main/rag-is-not-enough/rag/nitro_rag.ipynb).
|
A simple RAG setup was done using **[Llamaindex](https://www.llamaindex.ai/)** and the **[bge-en-base-v1.5 embedding](https://huggingface.co/BAAI/bge-base-en-v1.5)** model for efficient documentation retrieval and question-answering. You can find the RAG implementation [here](https://github.com/janhq/open-foundry/blob/main/rag-is-not-enough/rag/nitro_rag.ipynb).
|
||||||
|
|
||||||
## Benchmarking the Results
|
## Benchmarking the Results
|
||||||
|
|
||||||
We curated a new set of [50 multiple-choice questions](https://github.com/menloresearch/open-foundry/blob/main/rag-is-not-enough/rag/mcq_nitro.csv) (MCQ) based on the Nitro docs. The questions had varying levels of difficulty and had trick components that challenged the model's ability to discern misleading information.
|
We curated a new set of [50 multiple-choice questions](https://github.com/janhq/open-foundry/blob/main/rag-is-not-enough/rag/mcq_nitro.csv) (MCQ) based on the Nitro docs. The questions had varying levels of difficulty and had trick components that challenged the model's ability to discern misleading information.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -121,7 +121,7 @@ We conclude that this combination of model merging + finetuning + RAG yields pro
|
|||||||
|
|
||||||
Anecdotally, we’ve had some success using this model in practice to onboard new team members to the Nitro codebase.
|
Anecdotally, we’ve had some success using this model in practice to onboard new team members to the Nitro codebase.
|
||||||
|
|
||||||
A full research report with more statistics can be found [here](https://github.com/menloresearch/open-foundry/blob/main/rag-is-not-enough/README.md).
|
A full research report with more statistics can be found [here](https://github.com/janhq/open-foundry/blob/main/rag-is-not-enough/README.md).
|
||||||
|
|
||||||
# References
|
# References
|
||||||
|
|
||||||
|
|||||||
@ -203,7 +203,7 @@ When to choose ChatGPT Plus instead:
|
|||||||
|
|
||||||
Ready to try gpt-oss?
|
Ready to try gpt-oss?
|
||||||
- Download Jan: [https://jan.ai/](https://jan.ai/)
|
- Download Jan: [https://jan.ai/](https://jan.ai/)
|
||||||
- View source code: [https://github.com/menloresearch/jan](https://github.com/menloresearch/jan)
|
- View source code: [https://github.com/janhq/jan](https://github.com/janhq/jan)
|
||||||
- Need help? Check our [local AI guide](/post/run-ai-models-locally) for beginners
|
- Need help? Check our [local AI guide](/post/run-ai-models-locally) for beginners
|
||||||
|
|
||||||
<CTABlog />
|
<CTABlog />
|
||||||
@ -4,7 +4,7 @@ title: Support - Jan
|
|||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
- Bugs & requests: file a GitHub ticket [here](https://github.com/menloresearch/jan/issues)
|
- Bugs & requests: file a GitHub ticket [here](https://github.com/janhq/jan/issues)
|
||||||
- For discussion: join our Discord [here](https://discord.gg/FTk2MvZwJH)
|
- For discussion: join our Discord [here](https://discord.gg/FTk2MvZwJH)
|
||||||
- For business inquiries: email hello@jan.ai
|
- For business inquiries: email hello@jan.ai
|
||||||
- For jobs: please email hr@jan.ai
|
- For jobs: please email hr@jan.ai
|
||||||
@ -31,7 +31,7 @@ const config: DocsThemeConfig = {
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
docsRepositoryBase: 'https://github.com/menloresearch/jan/tree/dev/docs',
|
docsRepositoryBase: 'https://github.com/janhq/jan/tree/dev/docs',
|
||||||
feedback: {
|
feedback: {
|
||||||
content: 'Question? Give us feedback →',
|
content: 'Question? Give us feedback →',
|
||||||
labels: 'feedback',
|
labels: 'feedback',
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
ListConversationItemsResponse
|
ListConversationItemsResponse
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
|
|
||||||
export class RemoteApi {
|
export class RemoteApi {
|
||||||
private authService: JanAuthService
|
private authService: JanAuthService
|
||||||
@ -28,7 +28,7 @@ export class RemoteApi {
|
|||||||
async createConversation(
|
async createConversation(
|
||||||
data: Conversation
|
data: Conversation
|
||||||
): Promise<ConversationResponse> {
|
): Promise<ConversationResponse> {
|
||||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATIONS}`
|
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATIONS}`
|
||||||
|
|
||||||
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
||||||
url,
|
url,
|
||||||
@ -43,12 +43,12 @@ export class RemoteApi {
|
|||||||
conversationId: string,
|
conversationId: string,
|
||||||
data: Conversation
|
data: Conversation
|
||||||
): Promise<ConversationResponse> {
|
): Promise<ConversationResponse> {
|
||||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||||
|
|
||||||
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
return this.authService.makeAuthenticatedRequest<ConversationResponse>(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
method: 'PATCH',
|
method: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -70,7 +70,7 @@ export class RemoteApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryString = queryParams.toString()
|
const queryString = queryParams.toString()
|
||||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATIONS}${queryString ? `?${queryString}` : ''}`
|
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATIONS}${queryString ? `?${queryString}` : ''}`
|
||||||
|
|
||||||
return this.authService.makeAuthenticatedRequest<ListConversationsResponse>(
|
return this.authService.makeAuthenticatedRequest<ListConversationsResponse>(
|
||||||
url,
|
url,
|
||||||
@ -114,7 +114,7 @@ export class RemoteApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteConversation(conversationId: string): Promise<void> {
|
async deleteConversation(conversationId: string): Promise<void> {
|
||||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_BY_ID(conversationId)}`
|
||||||
|
|
||||||
await this.authService.makeAuthenticatedRequest(
|
await this.authService.makeAuthenticatedRequest(
|
||||||
url,
|
url,
|
||||||
@ -141,7 +141,7 @@ export class RemoteApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const queryString = queryParams.toString()
|
const queryString = queryParams.toString()
|
||||||
const url = `${JAN_API_BASE}${CONVERSATION_API_ROUTES.CONVERSATION_ITEMS(conversationId)}${queryString ? `?${queryString}` : ''}`
|
const url = `${MENLO_PLATFORM_BASE_URL}${CONVERSATION_API_ROUTES.CONVERSATION_ITEMS(conversationId)}${queryString ? `?${queryString}` : ''}`
|
||||||
|
|
||||||
return this.authService.makeAuthenticatedRequest<ListConversationItemsResponse>(
|
return this.authService.makeAuthenticatedRequest<ListConversationItemsResponse>(
|
||||||
url,
|
url,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export interface ConversationResponse {
|
|||||||
id: string
|
id: string
|
||||||
object: 'conversation'
|
object: 'conversation'
|
||||||
title?: string
|
title?: string
|
||||||
created_at: number
|
created_at: number | string
|
||||||
metadata: ConversationMetadata
|
metadata: ConversationMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +50,7 @@ export interface ConversationItemAnnotation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ConversationItemContent {
|
export interface ConversationItemContent {
|
||||||
|
type?: string
|
||||||
file?: {
|
file?: {
|
||||||
file_id?: string
|
file_id?: string
|
||||||
mime_type?: string
|
mime_type?: string
|
||||||
@ -62,23 +63,50 @@ export interface ConversationItemContent {
|
|||||||
file_id?: string
|
file_id?: string
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
image_file?: {
|
||||||
|
file_id?: string
|
||||||
|
mime_type?: string
|
||||||
|
}
|
||||||
input_text?: string
|
input_text?: string
|
||||||
output_text?: {
|
output_text?: {
|
||||||
annotations?: ConversationItemAnnotation[]
|
annotations?: ConversationItemAnnotation[]
|
||||||
text?: string
|
text?: string
|
||||||
}
|
}
|
||||||
reasoning_content?: string
|
|
||||||
text?: {
|
text?: {
|
||||||
value?: string
|
value?: string
|
||||||
|
text?: string
|
||||||
}
|
}
|
||||||
type?: string
|
reasoning_content?: string
|
||||||
|
tool_calls?: Array<{
|
||||||
|
id?: string
|
||||||
|
type?: string
|
||||||
|
function?: {
|
||||||
|
name?: string
|
||||||
|
arguments?: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
tool_call_id?: string
|
||||||
|
tool_result?: {
|
||||||
|
content?: Array<{
|
||||||
|
type?: string
|
||||||
|
text?: string
|
||||||
|
output_text?: {
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
output_text?: {
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text_result?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConversationItem {
|
export interface ConversationItem {
|
||||||
content?: ConversationItemContent[]
|
content?: ConversationItemContent[]
|
||||||
created_at: number
|
created_at: number | string
|
||||||
id: string
|
id: string
|
||||||
object: string
|
object: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
role: string
|
role: string
|
||||||
status?: string
|
status?: string
|
||||||
type?: string
|
type?: string
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Thread, ThreadAssistantInfo, ThreadMessage, ContentType } from '@janhq/core'
|
import { Thread, ThreadAssistantInfo, ThreadMessage, ContentType } from '@janhq/core'
|
||||||
import { Conversation, ConversationResponse, ConversationItem } from './types'
|
import { Conversation, ConversationResponse, ConversationItem, ConversationItemContent, ConversationMetadata } from './types'
|
||||||
import { DEFAULT_ASSISTANT } from './const'
|
import { DEFAULT_ASSISTANT } from './const'
|
||||||
|
|
||||||
export class ObjectParser {
|
export class ObjectParser {
|
||||||
@ -7,7 +7,7 @@ export class ObjectParser {
|
|||||||
const modelName = thread.assistants?.[0]?.model?.id || undefined
|
const modelName = thread.assistants?.[0]?.model?.id || undefined
|
||||||
const modelProvider = thread.assistants?.[0]?.model?.engine || undefined
|
const modelProvider = thread.assistants?.[0]?.model?.engine || undefined
|
||||||
const isFavorite = thread.metadata?.is_favorite?.toString() || 'false'
|
const isFavorite = thread.metadata?.is_favorite?.toString() || 'false'
|
||||||
let metadata = {}
|
let metadata: ConversationMetadata = {}
|
||||||
if (modelName && modelProvider) {
|
if (modelName && modelProvider) {
|
||||||
metadata = {
|
metadata = {
|
||||||
model_id: modelName,
|
model_id: modelName,
|
||||||
@ -23,15 +23,14 @@ export class ObjectParser {
|
|||||||
|
|
||||||
static conversationToThread(conversation: ConversationResponse): Thread {
|
static conversationToThread(conversation: ConversationResponse): Thread {
|
||||||
const assistants: ThreadAssistantInfo[] = []
|
const assistants: ThreadAssistantInfo[] = []
|
||||||
if (
|
const metadata: ConversationMetadata = conversation.metadata || {}
|
||||||
conversation.metadata?.model_id &&
|
|
||||||
conversation.metadata?.model_provider
|
if (metadata.model_id && metadata.model_provider) {
|
||||||
) {
|
|
||||||
assistants.push({
|
assistants.push({
|
||||||
...DEFAULT_ASSISTANT,
|
...DEFAULT_ASSISTANT,
|
||||||
model: {
|
model: {
|
||||||
id: conversation.metadata.model_id,
|
id: metadata.model_id,
|
||||||
engine: conversation.metadata.model_provider,
|
engine: metadata.model_provider,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -44,16 +43,18 @@ export class ObjectParser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFavorite = conversation.metadata?.is_favorite === 'true'
|
const isFavorite = metadata.is_favorite === 'true'
|
||||||
|
const createdAtMs = parseTimestamp(conversation.created_at)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: conversation.id,
|
id: conversation.id,
|
||||||
title: conversation.title || '',
|
title: conversation.title || '',
|
||||||
assistants,
|
assistants,
|
||||||
created: conversation.created_at,
|
created: createdAtMs,
|
||||||
updated: conversation.created_at,
|
updated: createdAtMs,
|
||||||
model: {
|
model: {
|
||||||
id: conversation.metadata.model_id,
|
id: metadata.model_id,
|
||||||
provider: conversation.metadata.model_provider,
|
provider: metadata.model_provider,
|
||||||
},
|
},
|
||||||
isFavorite,
|
isFavorite,
|
||||||
metadata: { is_favorite: isFavorite },
|
metadata: { is_favorite: isFavorite },
|
||||||
@ -65,74 +66,70 @@ export class ObjectParser {
|
|||||||
threadId: string
|
threadId: string
|
||||||
): ThreadMessage {
|
): ThreadMessage {
|
||||||
// Extract text content and metadata from the item
|
// Extract text content and metadata from the item
|
||||||
let textContent = ''
|
const textSegments: string[] = []
|
||||||
let reasoningContent = ''
|
const reasoningSegments: string[] = []
|
||||||
const imageUrls: string[] = []
|
const imageUrls: string[] = []
|
||||||
let toolCalls: any[] = []
|
let toolCalls: any[] = []
|
||||||
let finishReason = ''
|
|
||||||
|
|
||||||
if (item.content && item.content.length > 0) {
|
if (item.content && item.content.length > 0) {
|
||||||
for (const content of item.content) {
|
for (const content of item.content) {
|
||||||
// Handle text content
|
extractContentByType(content, {
|
||||||
if (content.text?.value) {
|
onText: (value) => {
|
||||||
textContent = content.text.value
|
if (value) {
|
||||||
}
|
textSegments.push(value)
|
||||||
// Handle output_text for assistant messages
|
}
|
||||||
if (content.output_text?.text) {
|
},
|
||||||
textContent = content.output_text.text
|
onReasoning: (value) => {
|
||||||
}
|
if (value) {
|
||||||
// Handle reasoning content
|
reasoningSegments.push(value)
|
||||||
if (content.reasoning_content) {
|
}
|
||||||
reasoningContent = content.reasoning_content
|
},
|
||||||
}
|
onImage: (url) => {
|
||||||
// Handle image content
|
if (url) {
|
||||||
if (content.image?.url) {
|
imageUrls.push(url)
|
||||||
imageUrls.push(content.image.url)
|
}
|
||||||
}
|
},
|
||||||
// Extract finish_reason
|
onToolCalls: (calls) => {
|
||||||
if (content.finish_reason) {
|
toolCalls = calls.map((toolCall) => {
|
||||||
finishReason = content.finish_reason
|
const callId = toolCall.id || 'unknown'
|
||||||
}
|
const rawArgs = toolCall.function?.arguments
|
||||||
}
|
const normalizedArgs =
|
||||||
}
|
typeof rawArgs === 'string'
|
||||||
|
? rawArgs
|
||||||
// Handle tool calls parsing for assistant messages
|
: JSON.stringify(rawArgs ?? {})
|
||||||
if (item.role === 'assistant' && finishReason === 'tool_calls') {
|
return {
|
||||||
try {
|
id: callId,
|
||||||
// Tool calls are embedded as JSON string in textContent
|
tool_call_id: callId,
|
||||||
const toolCallMatch = textContent.match(/\[.*\]/)
|
tool: {
|
||||||
if (toolCallMatch) {
|
id: callId,
|
||||||
const toolCallsData = JSON.parse(toolCallMatch[0])
|
function: {
|
||||||
toolCalls = toolCallsData.map((toolCall: any) => ({
|
name: toolCall.function?.name || 'unknown',
|
||||||
tool: {
|
arguments: normalizedArgs,
|
||||||
id: toolCall.id || 'unknown',
|
},
|
||||||
function: {
|
type: toolCall.type || 'function',
|
||||||
name: toolCall.function?.name || 'unknown',
|
},
|
||||||
arguments: toolCall.function?.arguments || '{}'
|
response: {
|
||||||
},
|
error: '',
|
||||||
type: toolCall.type || 'function'
|
content: [],
|
||||||
},
|
},
|
||||||
response: {
|
state: 'pending',
|
||||||
error: '',
|
}
|
||||||
content: []
|
})
|
||||||
},
|
},
|
||||||
state: 'ready'
|
})
|
||||||
}))
|
|
||||||
// Remove tool calls JSON from text content, keep only reasoning
|
|
||||||
textContent = ''
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse tool calls:', error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format final content with reasoning if present
|
// Format final content with reasoning if present
|
||||||
let finalTextValue = ''
|
let finalTextValue = ''
|
||||||
if (reasoningContent) {
|
if (reasoningSegments.length > 0) {
|
||||||
finalTextValue = `<think>${reasoningContent}</think>`
|
finalTextValue += `<think>${reasoningSegments.join('\n')}</think>`
|
||||||
}
|
}
|
||||||
if (textContent) {
|
if (textSegments.length > 0) {
|
||||||
finalTextValue += textContent
|
if (finalTextValue) {
|
||||||
|
finalTextValue += '\n'
|
||||||
|
}
|
||||||
|
finalTextValue += textSegments.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build content array for ThreadMessage
|
// Build content array for ThreadMessage
|
||||||
@ -157,22 +154,26 @@ export class ObjectParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build metadata
|
// Build metadata
|
||||||
const metadata: any = {}
|
const metadata: any = { ...(item.metadata || {}) }
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
metadata.tool_calls = toolCalls
|
metadata.tool_calls = toolCalls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createdAtMs = parseTimestamp(item.created_at)
|
||||||
|
|
||||||
// Map status from server format to frontend format
|
// Map status from server format to frontend format
|
||||||
const mappedStatus = item.status === 'completed' ? 'ready' : item.status || 'ready'
|
const mappedStatus = item.status === 'completed' ? 'ready' : item.status || 'ready'
|
||||||
|
|
||||||
|
const role = item.role === 'user' || item.role === 'assistant' ? item.role : 'assistant'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
id: item.id,
|
id: item.id,
|
||||||
object: 'thread.message',
|
object: 'thread.message',
|
||||||
thread_id: threadId,
|
thread_id: threadId,
|
||||||
role: item.role as 'user' | 'assistant',
|
role,
|
||||||
content: messageContent,
|
content: messageContent,
|
||||||
created_at: item.created_at * 1000, // Convert to milliseconds
|
created_at: createdAtMs,
|
||||||
completed_at: 0,
|
completed_at: 0,
|
||||||
status: mappedStatus,
|
status: mappedStatus,
|
||||||
metadata,
|
metadata,
|
||||||
@ -201,25 +202,46 @@ export const combineConversationItemsToMessages = (
|
|||||||
): ThreadMessage[] => {
|
): ThreadMessage[] => {
|
||||||
const messages: ThreadMessage[] = []
|
const messages: ThreadMessage[] = []
|
||||||
const toolResponseMap = new Map<string, any>()
|
const toolResponseMap = new Map<string, any>()
|
||||||
|
const sortedItems = [...items].sort(
|
||||||
|
(a, b) => parseTimestamp(a.created_at) - parseTimestamp(b.created_at)
|
||||||
|
)
|
||||||
|
|
||||||
// First pass: collect tool responses
|
// First pass: collect tool responses
|
||||||
for (const item of items) {
|
for (const item of sortedItems) {
|
||||||
if (item.role === 'tool') {
|
if (item.role === 'tool') {
|
||||||
const toolContent = item.content?.[0]?.text?.value || ''
|
for (const content of item.content ?? []) {
|
||||||
toolResponseMap.set(item.id, {
|
const toolCallId = content.tool_call_id || item.id
|
||||||
error: '',
|
const toolResultText =
|
||||||
content: [
|
content.tool_result?.output_text?.text ||
|
||||||
{
|
(Array.isArray(content.tool_result?.content)
|
||||||
type: 'text',
|
? content.tool_result?.content
|
||||||
text: toolContent
|
?.map((entry) => entry.text || entry.output_text?.text)
|
||||||
}
|
.filter((text): text is string => Boolean(text))
|
||||||
]
|
.join('\n')
|
||||||
})
|
: undefined)
|
||||||
|
const toolContent =
|
||||||
|
content.text?.text ||
|
||||||
|
content.text?.value ||
|
||||||
|
content.output_text?.text ||
|
||||||
|
content.input_text ||
|
||||||
|
content.text_result ||
|
||||||
|
toolResultText ||
|
||||||
|
''
|
||||||
|
toolResponseMap.set(toolCallId, {
|
||||||
|
error: '',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: toolContent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: build messages and merge tool responses
|
// Second pass: build messages and merge tool responses
|
||||||
for (const item of items) {
|
for (const item of sortedItems) {
|
||||||
// Skip tool messages as they will be merged into assistant messages
|
// Skip tool messages as they will be merged into assistant messages
|
||||||
if (item.role === 'tool') {
|
if (item.role === 'tool') {
|
||||||
continue
|
continue
|
||||||
@ -228,14 +250,35 @@ export const combineConversationItemsToMessages = (
|
|||||||
const message = ObjectParser.conversationItemToThreadMessage(item, threadId)
|
const message = ObjectParser.conversationItemToThreadMessage(item, threadId)
|
||||||
|
|
||||||
// If this is an assistant message with tool calls, merge tool responses
|
// If this is an assistant message with tool calls, merge tool responses
|
||||||
if (message.role === 'assistant' && message.metadata?.tool_calls && Array.isArray(message.metadata.tool_calls)) {
|
if (
|
||||||
|
message.role === 'assistant' &&
|
||||||
|
message.metadata?.tool_calls &&
|
||||||
|
Array.isArray(message.metadata.tool_calls)
|
||||||
|
) {
|
||||||
const toolCalls = message.metadata.tool_calls as any[]
|
const toolCalls = message.metadata.tool_calls as any[]
|
||||||
let toolResponseIndex = 0
|
|
||||||
|
|
||||||
for (const [responseId, responseData] of toolResponseMap.entries()) {
|
for (const toolCall of toolCalls) {
|
||||||
if (toolResponseIndex < toolCalls.length) {
|
const callId = toolCall.tool_call_id || toolCall.id || toolCall.tool?.id
|
||||||
toolCalls[toolResponseIndex].response = responseData
|
let responseKey: string | undefined
|
||||||
toolResponseIndex++
|
let response: any = null
|
||||||
|
|
||||||
|
if (callId && toolResponseMap.has(callId)) {
|
||||||
|
responseKey = callId
|
||||||
|
response = toolResponseMap.get(callId)
|
||||||
|
} else {
|
||||||
|
const iterator = toolResponseMap.entries().next()
|
||||||
|
if (!iterator.done) {
|
||||||
|
responseKey = iterator.value[0]
|
||||||
|
response = iterator.value[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
toolCall.response = response
|
||||||
|
toolCall.state = 'succeeded'
|
||||||
|
if (responseKey) {
|
||||||
|
toolResponseMap.delete(responseKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,3 +288,79 @@ export const combineConversationItemsToMessages = (
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseTimestamp = (value: number | string | undefined): number => {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
// Distinguish between seconds and milliseconds
|
||||||
|
return value > 1e12 ? value : value * 1000
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const parsed = Date.parse(value)
|
||||||
|
return Number.isNaN(parsed) ? Date.now() : parsed
|
||||||
|
}
|
||||||
|
return Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractContentByType = (
|
||||||
|
content: ConversationItemContent,
|
||||||
|
handlers: {
|
||||||
|
onText: (value: string) => void
|
||||||
|
onReasoning: (value: string) => void
|
||||||
|
onImage: (url: string) => void
|
||||||
|
onToolCalls: (calls: NonNullable<ConversationItemContent['tool_calls']>) => void
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const type = content.type || ''
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'input_text':
|
||||||
|
handlers.onText(content.input_text || '')
|
||||||
|
break
|
||||||
|
case 'text':
|
||||||
|
handlers.onText(content.text?.text || content.text?.value || '')
|
||||||
|
break
|
||||||
|
case 'output_text':
|
||||||
|
handlers.onText(content.output_text?.text || '')
|
||||||
|
break
|
||||||
|
case 'reasoning_content':
|
||||||
|
handlers.onReasoning(content.reasoning_content || '')
|
||||||
|
break
|
||||||
|
case 'image':
|
||||||
|
case 'image_url':
|
||||||
|
if (content.image?.url) {
|
||||||
|
handlers.onImage(content.image.url)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'tool_calls':
|
||||||
|
if (content.tool_calls && Array.isArray(content.tool_calls)) {
|
||||||
|
handlers.onToolCalls(content.tool_calls)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'tool_result':
|
||||||
|
if (content.tool_result?.output_text?.text) {
|
||||||
|
handlers.onText(content.tool_result.output_text.text)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// Fallback for legacy fields without explicit type
|
||||||
|
if (content.text?.value || content.text?.text) {
|
||||||
|
handlers.onText(content.text.value || content.text.text || '')
|
||||||
|
}
|
||||||
|
if (content.text_result) {
|
||||||
|
handlers.onText(content.text_result)
|
||||||
|
}
|
||||||
|
if (content.output_text?.text) {
|
||||||
|
handlers.onText(content.output_text.text)
|
||||||
|
}
|
||||||
|
if (content.reasoning_content) {
|
||||||
|
handlers.onReasoning(content.reasoning_content)
|
||||||
|
}
|
||||||
|
if (content.image?.url) {
|
||||||
|
handlers.onImage(content.image.url)
|
||||||
|
}
|
||||||
|
if (content.tool_calls && Array.isArray(content.tool_calls)) {
|
||||||
|
handlers.onToolCalls(content.tool_calls)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getSharedAuthService, JanAuthService } from '../shared'
|
import { getSharedAuthService, JanAuthService } from '../shared'
|
||||||
import { JanModel, janProviderStore } from './store'
|
|
||||||
import { ApiError } from '../shared/types/errors'
|
import { ApiError } from '../shared/types/errors'
|
||||||
|
import { JAN_API_ROUTES } from './const'
|
||||||
|
import { JanModel, janProviderStore } from './store'
|
||||||
|
|
||||||
// JAN_API_BASE is defined in vite.config.ts
|
// MENLO_PLATFORM_BASE_URL is defined in vite.config.ts
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const TEMPORARY_CHAT_ID = 'temporary-chat'
|
const TEMPORARY_CHAT_ID = 'temporary-chat'
|
||||||
@ -19,12 +20,7 @@ const TEMPORARY_CHAT_ID = 'temporary-chat'
|
|||||||
*/
|
*/
|
||||||
function getChatCompletionConfig(request: JanChatCompletionRequest, stream: boolean = false) {
|
function getChatCompletionConfig(request: JanChatCompletionRequest, stream: boolean = false) {
|
||||||
const isTemporaryChat = request.conversation_id === TEMPORARY_CHAT_ID
|
const isTemporaryChat = request.conversation_id === TEMPORARY_CHAT_ID
|
||||||
|
const endpoint = `${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.CHAT_COMPLETIONS}`
|
||||||
// For temporary chats, use the stateless /chat/completions endpoint
|
|
||||||
// For regular conversations, use the stateful /conv/chat/completions endpoint
|
|
||||||
const endpoint = isTemporaryChat
|
|
||||||
? `${JAN_API_BASE}/chat/completions`
|
|
||||||
: `${JAN_API_BASE}/conv/chat/completions`
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...request,
|
...request,
|
||||||
@ -44,9 +40,30 @@ function getChatCompletionConfig(request: JanChatCompletionRequest, stream: bool
|
|||||||
return { endpoint, payload, isTemporaryChat }
|
return { endpoint, payload, isTemporaryChat }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JanModelsResponse {
|
interface JanModelSummary {
|
||||||
|
id: string
|
||||||
object: string
|
object: string
|
||||||
data: JanModel[]
|
owned_by: string
|
||||||
|
created?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JanModelsResponse {
|
||||||
|
object: string
|
||||||
|
data: JanModelSummary[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JanModelCatalogResponse {
|
||||||
|
id: string
|
||||||
|
supported_parameters?: {
|
||||||
|
names?: string[]
|
||||||
|
default?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
extras?: {
|
||||||
|
supported_parameters?: string[]
|
||||||
|
default_parameters?: Record<string, unknown>
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
[key: string]: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JanChatMessage {
|
export interface JanChatMessage {
|
||||||
@ -112,6 +129,8 @@ export interface JanChatCompletionChunk {
|
|||||||
export class JanApiClient {
|
export class JanApiClient {
|
||||||
private static instance: JanApiClient
|
private static instance: JanApiClient
|
||||||
private authService: JanAuthService
|
private authService: JanAuthService
|
||||||
|
private modelsCache: JanModel[] | null = null
|
||||||
|
private modelsFetchPromise: Promise<JanModel[]> | null = null
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.authService = getSharedAuthService()
|
this.authService = getSharedAuthService()
|
||||||
@ -124,25 +143,64 @@ export class JanApiClient {
|
|||||||
return JanApiClient.instance
|
return JanApiClient.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
async getModels(): Promise<JanModel[]> {
|
async getModels(options?: { forceRefresh?: boolean }): Promise<JanModel[]> {
|
||||||
try {
|
try {
|
||||||
|
const forceRefresh = options?.forceRefresh ?? false
|
||||||
|
|
||||||
|
if (forceRefresh) {
|
||||||
|
this.modelsCache = null
|
||||||
|
} else if (this.modelsCache) {
|
||||||
|
return this.modelsCache
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modelsFetchPromise) {
|
||||||
|
return this.modelsFetchPromise
|
||||||
|
}
|
||||||
|
|
||||||
janProviderStore.setLoadingModels(true)
|
janProviderStore.setLoadingModels(true)
|
||||||
janProviderStore.clearError()
|
janProviderStore.clearError()
|
||||||
|
|
||||||
const response = await this.authService.makeAuthenticatedRequest<JanModelsResponse>(
|
this.modelsFetchPromise = (async () => {
|
||||||
`${JAN_API_BASE}/conv/models`
|
const response = await this.authService.makeAuthenticatedRequest<JanModelsResponse>(
|
||||||
)
|
`${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.MODELS}`
|
||||||
|
)
|
||||||
|
|
||||||
const models = response.data || []
|
const summaries = response.data || []
|
||||||
janProviderStore.setModels(models)
|
|
||||||
|
const models: JanModel[] = await Promise.all(
|
||||||
return models
|
summaries.map(async (summary) => {
|
||||||
|
const supportedParameters = await this.fetchSupportedParameters(summary.id)
|
||||||
|
const capabilities = this.deriveCapabilitiesFromParameters(supportedParameters)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: summary.id,
|
||||||
|
object: summary.object,
|
||||||
|
owned_by: summary.owned_by,
|
||||||
|
created: summary.created,
|
||||||
|
capabilities,
|
||||||
|
supportedParameters,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
this.modelsCache = models
|
||||||
|
janProviderStore.setModels(models)
|
||||||
|
|
||||||
|
return models
|
||||||
|
})()
|
||||||
|
|
||||||
|
return await this.modelsFetchPromise
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.modelsCache = null
|
||||||
|
this.modelsFetchPromise = null
|
||||||
|
|
||||||
const errorMessage = error instanceof ApiError ? error.message :
|
const errorMessage = error instanceof ApiError ? error.message :
|
||||||
error instanceof Error ? error.message : 'Failed to fetch models'
|
error instanceof Error ? error.message : 'Failed to fetch models'
|
||||||
janProviderStore.setError(errorMessage)
|
janProviderStore.setError(errorMessage)
|
||||||
janProviderStore.setLoadingModels(false)
|
janProviderStore.setLoadingModels(false)
|
||||||
throw error
|
throw error
|
||||||
|
} finally {
|
||||||
|
this.modelsFetchPromise = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +312,7 @@ export class JanApiClient {
|
|||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
janProviderStore.setAuthenticated(true)
|
janProviderStore.setAuthenticated(true)
|
||||||
// Fetch initial models
|
// Fetch initial models (cached for subsequent calls)
|
||||||
await this.getModels()
|
await this.getModels()
|
||||||
console.log('Jan API client initialized successfully')
|
console.log('Jan API client initialized successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -266,6 +324,52 @@ export class JanApiClient {
|
|||||||
janProviderStore.setInitializing(false)
|
janProviderStore.setInitializing(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchSupportedParameters(modelId: string): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const endpoint = `${MENLO_PLATFORM_BASE_URL}${JAN_API_ROUTES.MODEL_CATALOGS}/${this.encodeModelIdForCatalog(modelId)}`
|
||||||
|
const catalog = await this.authService.makeAuthenticatedRequest<JanModelCatalogResponse>(endpoint)
|
||||||
|
return this.extractSupportedParameters(catalog)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to fetch catalog metadata for model "${modelId}":`, error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private encodeModelIdForCatalog(modelId: string): string {
|
||||||
|
return modelId
|
||||||
|
.split('/')
|
||||||
|
.map((segment) => encodeURIComponent(segment))
|
||||||
|
.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractSupportedParameters(catalog: JanModelCatalogResponse | null | undefined): string[] {
|
||||||
|
if (!catalog) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryNames = catalog.supported_parameters?.names
|
||||||
|
if (Array.isArray(primaryNames) && primaryNames.length > 0) {
|
||||||
|
return [...new Set(primaryNames)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraNames = catalog.extras?.supported_parameters
|
||||||
|
if (Array.isArray(extraNames) && extraNames.length > 0) {
|
||||||
|
return [...new Set(extraNames)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
private deriveCapabilitiesFromParameters(parameters: string[]): string[] {
|
||||||
|
const capabilities = new Set<string>()
|
||||||
|
|
||||||
|
if (parameters.includes('tools')) {
|
||||||
|
capabilities.add('tools')
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(capabilities)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const janApiClient = JanApiClient.getInstance()
|
export const janApiClient = JanApiClient.getInstance()
|
||||||
|
|||||||
7
extensions-web/src/jan-provider-web/const.ts
Normal file
7
extensions-web/src/jan-provider-web/const.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const JAN_API_ROUTES = {
|
||||||
|
MODELS: '/models',
|
||||||
|
CHAT_COMPLETIONS: '/chat/completions',
|
||||||
|
MODEL_CATALOGS: '/models/catalogs',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const MODEL_PROVIDER_STORAGE_KEY = 'model-provider'
|
||||||
122
extensions-web/src/jan-provider-web/helpers.ts
Normal file
122
extensions-web/src/jan-provider-web/helpers.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import type { JanModel } from './store'
|
||||||
|
import { MODEL_PROVIDER_STORAGE_KEY } from './const'
|
||||||
|
|
||||||
|
type StoredModel = {
|
||||||
|
id?: string
|
||||||
|
capabilities?: unknown
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoredProvider = {
|
||||||
|
provider?: string
|
||||||
|
models?: StoredModel[]
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoredState = {
|
||||||
|
state?: {
|
||||||
|
providers?: StoredProvider[]
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
version?: number
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeCapabilities = (capabilities: unknown): string[] => {
|
||||||
|
if (!Array.isArray(capabilities)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...new Set(capabilities.filter((item): item is string => typeof item === 'string'))].sort(
|
||||||
|
(a, b) => a.localeCompare(b)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize Jan models stored in localStorage with the latest server state.
|
||||||
|
* Returns true if the stored data was modified (including being cleared).
|
||||||
|
*/
|
||||||
|
export function syncJanModelsLocalStorage(
|
||||||
|
remoteModels: JanModel[],
|
||||||
|
storageKey: string = MODEL_PROVIDER_STORAGE_KEY
|
||||||
|
): boolean {
|
||||||
|
const rawStorage = localStorage.getItem(storageKey)
|
||||||
|
if (!rawStorage) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let storedState: StoredState
|
||||||
|
try {
|
||||||
|
storedState = JSON.parse(rawStorage) as StoredState
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to parse Jan model storage; clearing entry.', error)
|
||||||
|
localStorage.removeItem(storageKey)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const providers = storedState?.state?.providers
|
||||||
|
if (!Array.isArray(providers)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteModelMap = new Map(remoteModels.map((model) => [model.id, model]))
|
||||||
|
let storageUpdated = false
|
||||||
|
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (provider.provider !== 'jan' || !Array.isArray(provider.models)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedModels: StoredModel[] = []
|
||||||
|
|
||||||
|
for (const model of provider.models) {
|
||||||
|
const modelId = typeof model.id === 'string' ? model.id : null
|
||||||
|
if (!modelId) {
|
||||||
|
storageUpdated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteModel = remoteModelMap.get(modelId)
|
||||||
|
if (!remoteModel) {
|
||||||
|
console.log(`Removing unknown Jan model from localStorage: ${modelId}`)
|
||||||
|
storageUpdated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedCapabilities = normalizeCapabilities(model.capabilities)
|
||||||
|
const remoteCapabilities = normalizeCapabilities(remoteModel.capabilities)
|
||||||
|
|
||||||
|
const capabilitiesMatch =
|
||||||
|
storedCapabilities.length === remoteCapabilities.length &&
|
||||||
|
storedCapabilities.every((cap, index) => cap === remoteCapabilities[index])
|
||||||
|
|
||||||
|
if (!capabilitiesMatch) {
|
||||||
|
console.log(
|
||||||
|
`Updating capabilities for Jan model ${modelId}:`,
|
||||||
|
storedCapabilities,
|
||||||
|
'=>',
|
||||||
|
remoteCapabilities
|
||||||
|
)
|
||||||
|
updatedModels.push({
|
||||||
|
...model,
|
||||||
|
capabilities: remoteModel.capabilities,
|
||||||
|
})
|
||||||
|
storageUpdated = true
|
||||||
|
} else {
|
||||||
|
updatedModels.push(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatedModels.length !== provider.models.length) {
|
||||||
|
storageUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.models = updatedModels
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageUpdated) {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(storedState))
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageUpdated
|
||||||
|
}
|
||||||
@ -14,12 +14,10 @@ import {
|
|||||||
ImportOptions,
|
ImportOptions,
|
||||||
} from '@janhq/core' // cspell: disable-line
|
} from '@janhq/core' // cspell: disable-line
|
||||||
import { janApiClient, JanChatMessage } from './api'
|
import { janApiClient, JanChatMessage } from './api'
|
||||||
|
import { syncJanModelsLocalStorage } from './helpers'
|
||||||
import { janProviderStore } from './store'
|
import { janProviderStore } from './store'
|
||||||
import { ApiError } from '../shared/types/errors'
|
import { ApiError } from '../shared/types/errors'
|
||||||
|
|
||||||
// Jan models support tools via MCP
|
|
||||||
const JAN_MODEL_CAPABILITIES = ['tools'] as const
|
|
||||||
|
|
||||||
export default class JanProviderWeb extends AIEngine {
|
export default class JanProviderWeb extends AIEngine {
|
||||||
readonly provider = 'jan'
|
readonly provider = 'jan'
|
||||||
private activeSessions: Map<string, SessionInfo> = new Map()
|
private activeSessions: Map<string, SessionInfo> = new Map()
|
||||||
@ -28,11 +26,11 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
console.log('Loading Jan Provider Extension...')
|
console.log('Loading Jan Provider Extension...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check and clear invalid Jan models (capabilities mismatch)
|
// Initialize authentication
|
||||||
this.validateJanModelsLocalStorage()
|
|
||||||
|
|
||||||
// Initialize authentication and fetch models
|
|
||||||
await janApiClient.initialize()
|
await janApiClient.initialize()
|
||||||
|
// Check and sync stored Jan models against latest catalog data
|
||||||
|
await this.validateJanModelsLocalStorage()
|
||||||
|
|
||||||
console.log('Jan Provider Extension loaded successfully')
|
console.log('Jan Provider Extension loaded successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load Jan Provider Extension:', error)
|
console.error('Failed to load Jan Provider Extension:', error)
|
||||||
@ -43,46 +41,17 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify Jan models capabilities in localStorage
|
// Verify Jan models capabilities in localStorage
|
||||||
private validateJanModelsLocalStorage() {
|
private async validateJanModelsLocalStorage(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log("Validating Jan models in localStorage...")
|
console.log('Validating Jan models in localStorage...')
|
||||||
const storageKey = 'model-provider'
|
|
||||||
const data = localStorage.getItem(storageKey)
|
|
||||||
if (!data) return
|
|
||||||
|
|
||||||
const parsed = JSON.parse(data)
|
const remoteModels = await janApiClient.getModels()
|
||||||
if (!parsed?.state?.providers) return
|
const storageUpdated = syncJanModelsLocalStorage(remoteModels)
|
||||||
|
|
||||||
// Check if any Jan model has incorrect capabilities
|
if (storageUpdated) {
|
||||||
let hasInvalidModel = false
|
console.log(
|
||||||
|
'Synchronized Jan models in localStorage with server capabilities; reloading...'
|
||||||
for (const provider of parsed.state.providers) {
|
)
|
||||||
if (provider.provider === 'jan' && provider.models) {
|
|
||||||
for (const model of provider.models) {
|
|
||||||
console.log(`Checking Jan model: ${model.id}`, model.capabilities)
|
|
||||||
if (JSON.stringify(model.capabilities) !== JSON.stringify(JAN_MODEL_CAPABILITIES)) {
|
|
||||||
hasInvalidModel = true
|
|
||||||
console.log(`Found invalid Jan model: ${model.id}, clearing localStorage`)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasInvalidModel) break
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any invalid model found, just clear the storage
|
|
||||||
if (hasInvalidModel) {
|
|
||||||
// Force clear the storage
|
|
||||||
localStorage.removeItem(storageKey)
|
|
||||||
// Verify it's actually removed
|
|
||||||
const afterRemoval = localStorage.getItem(storageKey)
|
|
||||||
// If still present, try setting to empty state
|
|
||||||
if (afterRemoval) {
|
|
||||||
// Try alternative clearing method
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify({ state: { providers: [] }, version: parsed.version || 3 }))
|
|
||||||
}
|
|
||||||
console.log('Cleared model-provider from localStorage due to invalid Jan capabilities')
|
|
||||||
// Force a page reload to ensure clean state
|
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -119,7 +88,7 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
path: undefined, // Remote model, no local path
|
path: undefined, // Remote model, no local path
|
||||||
owned_by: model.owned_by,
|
owned_by: model.owned_by,
|
||||||
object: model.object,
|
object: model.object,
|
||||||
capabilities: [...JAN_MODEL_CAPABILITIES],
|
capabilities: [...model.capabilities],
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
)
|
)
|
||||||
@ -140,7 +109,7 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
path: undefined, // Remote model, no local path
|
path: undefined, // Remote model, no local path
|
||||||
owned_by: model.owned_by,
|
owned_by: model.owned_by,
|
||||||
object: model.object,
|
object: model.object,
|
||||||
capabilities: [...JAN_MODEL_CAPABILITIES],
|
capabilities: [...model.capabilities],
|
||||||
}))
|
}))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to list Jan models:', error)
|
console.error('Failed to list Jan models:', error)
|
||||||
@ -159,6 +128,7 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
port: 443, // HTTPS port
|
port: 443, // HTTPS port
|
||||||
model_id: modelId,
|
model_id: modelId,
|
||||||
model_path: `remote:${modelId}`, // Indicate this is a remote model
|
model_path: `remote:${modelId}`, // Indicate this is a remote model
|
||||||
|
is_embedding: false, // assume false here, TODO: might need further implementation
|
||||||
api_key: '', // API key handled by auth service
|
api_key: '', // API key handled by auth service
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,8 +163,12 @@ export default class JanProviderWeb extends AIEngine {
|
|||||||
console.error(`Failed to unload Jan session ${sessionId}:`, error)
|
console.error(`Failed to unload Jan session ${sessionId}:`, error)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof ApiError ? error.message :
|
error:
|
||||||
error instanceof Error ? error.message : 'Unknown error',
|
error instanceof ApiError
|
||||||
|
? error.message
|
||||||
|
: error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: 'Unknown error',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export interface JanModel {
|
|||||||
id: string
|
id: string
|
||||||
object: string
|
object: string
|
||||||
owned_by: string
|
owned_by: string
|
||||||
|
created?: number
|
||||||
|
capabilities: string[]
|
||||||
|
supportedParameters?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JanProviderState {
|
export interface JanProviderState {
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import { JanMCPOAuthProvider } from './oauth-provider'
|
|||||||
import { WebSearchButton } from './components'
|
import { WebSearchButton } from './components'
|
||||||
import type { ComponentType } from 'react'
|
import type { ComponentType } from 'react'
|
||||||
|
|
||||||
// JAN_API_BASE is defined in vite.config.ts (defaults to 'https://api-dev.jan.ai/jan/v1')
|
// MENLO_PLATFORM_BASE_URL is defined in vite.config.ts (defaults to 'https://api-dev.menlo.ai/jan/v1')
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
|
|
||||||
export default class MCPExtensionWeb extends MCPExtension {
|
export default class MCPExtensionWeb extends MCPExtension {
|
||||||
private mcpEndpoint = '/mcp'
|
private mcpEndpoint = '/mcp'
|
||||||
@ -77,7 +77,7 @@ export default class MCPExtensionWeb extends MCPExtension {
|
|||||||
|
|
||||||
// Create transport with OAuth provider (handles token refresh automatically)
|
// Create transport with OAuth provider (handles token refresh automatically)
|
||||||
const transport = new StreamableHTTPClientTransport(
|
const transport = new StreamableHTTPClientTransport(
|
||||||
new URL(`${JAN_API_BASE}${this.mcpEndpoint}`),
|
new URL(`${MENLO_PLATFORM_BASE_URL}${this.mcpEndpoint}`),
|
||||||
{
|
{
|
||||||
authProvider: this.oauthProvider
|
authProvider: this.oauthProvider
|
||||||
// No sessionId needed - server will generate one automatically
|
// No sessionId needed - server will generate one automatically
|
||||||
|
|||||||
@ -6,13 +6,13 @@
|
|||||||
import { AuthTokens } from './types'
|
import { AuthTokens } from './types'
|
||||||
import { AUTH_ENDPOINTS } from './const'
|
import { AUTH_ENDPOINTS } from './const'
|
||||||
|
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout user on server
|
* Logout user on server
|
||||||
*/
|
*/
|
||||||
export async function logoutUser(): Promise<void> {
|
export async function logoutUser(): Promise<void> {
|
||||||
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.LOGOUT}`, {
|
const response = await fetch(`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.LOGOUT}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
@ -29,7 +29,7 @@ export async function logoutUser(): Promise<void> {
|
|||||||
* Guest login
|
* Guest login
|
||||||
*/
|
*/
|
||||||
export async function guestLogin(): Promise<AuthTokens> {
|
export async function guestLogin(): Promise<AuthTokens> {
|
||||||
const response = await fetch(`${JAN_API_BASE}${AUTH_ENDPOINTS.GUEST_LOGIN}`, {
|
const response = await fetch(`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.GUEST_LOGIN}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
@ -51,7 +51,7 @@ export async function guestLogin(): Promise<AuthTokens> {
|
|||||||
*/
|
*/
|
||||||
export async function refreshToken(): Promise<AuthTokens> {
|
export async function refreshToken(): Promise<AuthTokens> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${JAN_API_BASE}${AUTH_ENDPOINTS.REFRESH_TOKEN}`,
|
`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.REFRESH_TOKEN}`,
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
import { AuthTokens, LoginUrlResponse } from './types'
|
import { AuthTokens, LoginUrlResponse } from './types'
|
||||||
|
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
|
|
||||||
export async function getLoginUrl(endpoint: string): Promise<LoginUrlResponse> {
|
export async function getLoginUrl(endpoint: string): Promise<LoginUrlResponse> {
|
||||||
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
|
const response: Response = await fetch(`${MENLO_PLATFORM_BASE_URL}${endpoint}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
@ -30,7 +30,7 @@ export async function handleOAuthCallback(
|
|||||||
code: string,
|
code: string,
|
||||||
state?: string
|
state?: string
|
||||||
): Promise<AuthTokens> {
|
): Promise<AuthTokens> {
|
||||||
const response: Response = await fetch(`${JAN_API_BASE}${endpoint}`, {
|
const response: Response = await fetch(`${MENLO_PLATFORM_BASE_URL}${endpoint}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
* Handles authentication flows for any OAuth provider
|
* Handles authentication flows for any OAuth provider
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
|
|
||||||
import { User, AuthState, AuthBroadcastMessage } from './types'
|
import { User, AuthState, AuthBroadcastMessage, AuthTokens } from './types'
|
||||||
import {
|
import {
|
||||||
AUTH_STORAGE_KEYS,
|
AUTH_STORAGE_KEYS,
|
||||||
AUTH_ENDPOINTS,
|
AUTH_ENDPOINTS,
|
||||||
@ -115,7 +115,7 @@ export class JanAuthService {
|
|||||||
|
|
||||||
// Store tokens and set authenticated state
|
// Store tokens and set authenticated state
|
||||||
this.accessToken = tokens.access_token
|
this.accessToken = tokens.access_token
|
||||||
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
|
this.tokenExpiryTime = this.computeTokenExpiry(tokens)
|
||||||
this.setAuthProvider(providerId)
|
this.setAuthProvider(providerId)
|
||||||
|
|
||||||
this.authBroadcast.broadcastLogin()
|
this.authBroadcast.broadcastLogin()
|
||||||
@ -158,7 +158,7 @@ export class JanAuthService {
|
|||||||
const tokens = await refreshToken()
|
const tokens = await refreshToken()
|
||||||
|
|
||||||
this.accessToken = tokens.access_token
|
this.accessToken = tokens.access_token
|
||||||
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
|
this.tokenExpiryTime = this.computeTokenExpiry(tokens)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to refresh access token:', error)
|
console.error('Failed to refresh access token:', error)
|
||||||
if (error instanceof ApiError && error.isStatus(401)) {
|
if (error instanceof ApiError && error.isStatus(401)) {
|
||||||
@ -343,6 +343,23 @@ export class JanAuthService {
|
|||||||
localStorage.removeItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER)
|
localStorage.removeItem(AUTH_STORAGE_KEYS.AUTH_PROVIDER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private computeTokenExpiry(tokens: AuthTokens): number {
|
||||||
|
if (tokens.expires_at) {
|
||||||
|
const expiresAt = new Date(tokens.expires_at).getTime()
|
||||||
|
if (!Number.isNaN(expiresAt)) {
|
||||||
|
return expiresAt
|
||||||
|
}
|
||||||
|
console.warn('Invalid expires_at format in auth tokens:', tokens.expires_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof tokens.expires_in === 'number') {
|
||||||
|
return Date.now() + tokens.expires_in * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('Auth tokens missing expiry information; defaulting to immediate expiry')
|
||||||
|
return Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure guest access is available
|
* Ensure guest access is available
|
||||||
*/
|
*/
|
||||||
@ -352,7 +369,7 @@ export class JanAuthService {
|
|||||||
if (!this.accessToken || Date.now() > this.tokenExpiryTime) {
|
if (!this.accessToken || Date.now() > this.tokenExpiryTime) {
|
||||||
const tokens = await guestLogin()
|
const tokens = await guestLogin()
|
||||||
this.accessToken = tokens.access_token
|
this.accessToken = tokens.access_token
|
||||||
this.tokenExpiryTime = Date.now() + tokens.expires_in * 1000
|
this.tokenExpiryTime = this.computeTokenExpiry(tokens)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to ensure guest access:', error)
|
console.error('Failed to ensure guest access:', error)
|
||||||
@ -387,7 +404,6 @@ export class JanAuthService {
|
|||||||
case AUTH_EVENTS.LOGOUT:
|
case AUTH_EVENTS.LOGOUT:
|
||||||
// Another tab logged out, clear our state
|
// Another tab logged out, clear our state
|
||||||
this.clearAuthState()
|
this.clearAuthState()
|
||||||
this.ensureGuestAccess().catch(console.error)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -413,7 +429,7 @@ export class JanAuthService {
|
|||||||
private async fetchUserProfile(): Promise<User | null> {
|
private async fetchUserProfile(): Promise<User | null> {
|
||||||
try {
|
try {
|
||||||
return await this.makeAuthenticatedRequest<User>(
|
return await this.makeAuthenticatedRequest<User>(
|
||||||
`${JAN_API_BASE}${AUTH_ENDPOINTS.ME}`
|
`${MENLO_PLATFORM_BASE_URL}${AUTH_ENDPOINTS.ME}`
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch user profile:', error)
|
console.error('Failed to fetch user profile:', error)
|
||||||
|
|||||||
@ -16,7 +16,8 @@ export type AuthType = ProviderType | 'guest'
|
|||||||
|
|
||||||
export interface AuthTokens {
|
export interface AuthTokens {
|
||||||
access_token: string
|
access_token: string
|
||||||
expires_in: number
|
expires_in?: number
|
||||||
|
expires_at?: string
|
||||||
object: string
|
object: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
extensions-web/src/types/global.d.ts
vendored
2
extensions-web/src/types/global.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
export {}
|
export {}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
declare const JAN_API_BASE: string
|
declare const MENLO_PLATFORM_BASE_URL: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,6 @@ export default defineConfig({
|
|||||||
emptyOutDir: false // Don't clean the output directory
|
emptyOutDir: false // Don't clean the output directory
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
JAN_API_BASE: JSON.stringify(process.env.JAN_API_BASE || 'https://api-dev.jan.ai/v1'),
|
MENLO_PLATFORM_BASE_URL: JSON.stringify(process.env.MENLO_PLATFORM_BASE_URL || 'https://api-dev.menlo.ai/v1'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -70,6 +70,6 @@ There are a few things to keep in mind when writing your extension code:
|
|||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Extension Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/menloresearch/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your extension!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -56,7 +56,7 @@ async function fetchRemoteSupportedBackends(
|
|||||||
supportedBackends: string[]
|
supportedBackends: string[]
|
||||||
): Promise<{ version: string; backend: string }[]> {
|
): Promise<{ version: string; backend: string }[]> {
|
||||||
// Pull the latest releases from the repo
|
// Pull the latest releases from the repo
|
||||||
const { releases } = await _fetchGithubReleases('menloresearch', 'llama.cpp')
|
const { releases } = await _fetchGithubReleases('janhq', 'llama.cpp')
|
||||||
releases.sort((a, b) => b.tag_name.localeCompare(a.tag_name))
|
releases.sort((a, b) => b.tag_name.localeCompare(a.tag_name))
|
||||||
releases.splice(10) // keep only the latest 10 releases
|
releases.splice(10) // keep only the latest 10 releases
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export async function listSupportedBackends(): Promise<
|
|||||||
const sysType = `${os_type}-${arch}`
|
const sysType = `${os_type}-${arch}`
|
||||||
let supportedBackends = []
|
let supportedBackends = []
|
||||||
|
|
||||||
// NOTE: menloresearch's tags for llama.cpp builds are a bit different
|
// NOTE: janhq's tags for llama.cpp builds are a bit different
|
||||||
// TODO: fetch versions from the server?
|
// TODO: fetch versions from the server?
|
||||||
// TODO: select CUDA version based on driver version
|
// TODO: select CUDA version based on driver version
|
||||||
if (sysType == 'windows-x86_64') {
|
if (sysType == 'windows-x86_64') {
|
||||||
@ -156,8 +156,13 @@ export async function listSupportedBackends(): Promise<
|
|||||||
supportedBackends.push('macos-arm64')
|
supportedBackends.push('macos-arm64')
|
||||||
}
|
}
|
||||||
// get latest backends from Github
|
// get latest backends from Github
|
||||||
const remoteBackendVersions =
|
let remoteBackendVersions = []
|
||||||
|
try {
|
||||||
|
remoteBackendVersions =
|
||||||
await fetchRemoteSupportedBackends(supportedBackends)
|
await fetchRemoteSupportedBackends(supportedBackends)
|
||||||
|
} catch (e) {
|
||||||
|
console.debug(`Not able to get remote backends, Jan might be offline or network problem: ${String(e)}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Get locally installed versions
|
// Get locally installed versions
|
||||||
const localBackendVersions = await getLocalInstalledBackends()
|
const localBackendVersions = await getLocalInstalledBackends()
|
||||||
@ -242,7 +247,7 @@ export async function downloadBackend(
|
|||||||
// Build URLs per source
|
// Build URLs per source
|
||||||
const backendUrl =
|
const backendUrl =
|
||||||
source === 'github'
|
source === 'github'
|
||||||
? `https://github.com/menloresearch/llama.cpp/releases/download/${version}/llama-${version}-bin-${backend}.tar.gz`
|
? `https://github.com/janhq/llama.cpp/releases/download/${version}/llama-${version}-bin-${backend}.tar.gz`
|
||||||
: `https://catalog.jan.ai/llama.cpp/releases/${version}/llama-${version}-bin-${backend}.tar.gz`
|
: `https://catalog.jan.ai/llama.cpp/releases/${version}/llama-${version}-bin-${backend}.tar.gz`
|
||||||
|
|
||||||
const downloadItems = [
|
const downloadItems = [
|
||||||
@ -258,7 +263,7 @@ export async function downloadBackend(
|
|||||||
downloadItems.push({
|
downloadItems.push({
|
||||||
url:
|
url:
|
||||||
source === 'github'
|
source === 'github'
|
||||||
? `https://github.com/menloresearch/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`
|
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`
|
||||||
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`,
|
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu11.7-x64.tar.gz`,
|
||||||
save_path: await joinPath([libDir, 'cuda11.tar.gz']),
|
save_path: await joinPath([libDir, 'cuda11.tar.gz']),
|
||||||
proxy: proxyConfig,
|
proxy: proxyConfig,
|
||||||
@ -267,7 +272,7 @@ export async function downloadBackend(
|
|||||||
downloadItems.push({
|
downloadItems.push({
|
||||||
url:
|
url:
|
||||||
source === 'github'
|
source === 'github'
|
||||||
? `https://github.com/menloresearch/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`
|
? `https://github.com/janhq/llama.cpp/releases/download/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`
|
||||||
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`,
|
: `https://catalog.jan.ai/llama.cpp/releases/${version}/cudart-llama-bin-${platformName}-cu12.0-x64.tar.gz`,
|
||||||
save_path: await joinPath([libDir, 'cuda12.tar.gz']),
|
save_path: await joinPath([libDir, 'cuda12.tar.gz']),
|
||||||
proxy: proxyConfig,
|
proxy: proxyConfig,
|
||||||
|
|||||||
@ -39,7 +39,6 @@ import { getProxyConfig } from './util'
|
|||||||
import { basename } from '@tauri-apps/api/path'
|
import { basename } from '@tauri-apps/api/path'
|
||||||
import {
|
import {
|
||||||
readGgufMetadata,
|
readGgufMetadata,
|
||||||
estimateKVCacheSize,
|
|
||||||
getModelSize,
|
getModelSize,
|
||||||
isModelSupported,
|
isModelSupported,
|
||||||
planModelLoadInternal,
|
planModelLoadInternal,
|
||||||
@ -58,6 +57,8 @@ type LlamacppConfig = {
|
|||||||
chat_template: string
|
chat_template: string
|
||||||
n_gpu_layers: number
|
n_gpu_layers: number
|
||||||
offload_mmproj: boolean
|
offload_mmproj: boolean
|
||||||
|
cpu_moe: boolean
|
||||||
|
n_cpu_moe: number
|
||||||
override_tensor_buffer_t: string
|
override_tensor_buffer_t: string
|
||||||
ctx_size: number
|
ctx_size: number
|
||||||
threads: number
|
threads: number
|
||||||
@ -1527,6 +1528,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
this.autoUnload &&
|
this.autoUnload &&
|
||||||
|
!isEmbedding &&
|
||||||
(loadedModels.length > 0 || otherLoadingPromises.length > 0)
|
(loadedModels.length > 0 || otherLoadingPromises.length > 0)
|
||||||
) {
|
) {
|
||||||
// Wait for OTHER loading models to finish, then unload everything
|
// Wait for OTHER loading models to finish, then unload everything
|
||||||
@ -1534,10 +1536,33 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
await Promise.all(otherLoadingPromises)
|
await Promise.all(otherLoadingPromises)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now unload all loaded models
|
// Now unload all loaded Text models excluding embedding models
|
||||||
const allLoadedModels = await this.getLoadedModels()
|
const allLoadedModels = await this.getLoadedModels()
|
||||||
if (allLoadedModels.length > 0) {
|
if (allLoadedModels.length > 0) {
|
||||||
await Promise.all(allLoadedModels.map((model) => this.unload(model)))
|
const sessionInfos: (SessionInfo | null)[] = await Promise.all(
|
||||||
|
allLoadedModels.map(async (modelId) => {
|
||||||
|
try {
|
||||||
|
return await this.findSessionByModel(modelId)
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Unable to find session for model "${modelId}": ${e}`)
|
||||||
|
return null // treat as “not‑eligible for unload”
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(JSON.stringify(sessionInfos))
|
||||||
|
|
||||||
|
const nonEmbeddingModels: string[] = sessionInfos
|
||||||
|
.filter(
|
||||||
|
(s): s is SessionInfo => s !== null && s.is_embedding === false
|
||||||
|
)
|
||||||
|
.map((s) => s.model_id)
|
||||||
|
|
||||||
|
if (nonEmbeddingModels.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
nonEmbeddingModels.map((modelId) => this.unload(modelId))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const args: string[] = []
|
const args: string[] = []
|
||||||
@ -1581,6 +1606,10 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
])
|
])
|
||||||
args.push('--jinja')
|
args.push('--jinja')
|
||||||
args.push('-m', modelPath)
|
args.push('-m', modelPath)
|
||||||
|
if (cfg.cpu_moe) args.push('--cpu-moe')
|
||||||
|
if (cfg.n_cpu_moe && cfg.n_cpu_moe > 0) {
|
||||||
|
args.push('--n-cpu-moe', String(cfg.n_cpu_moe))
|
||||||
|
}
|
||||||
// For overriding tensor buffer type, useful where
|
// For overriding tensor buffer type, useful where
|
||||||
// massive MOE models can be made faster by keeping attention on the GPU
|
// massive MOE models can be made faster by keeping attention on the GPU
|
||||||
// and offloading the expert FFNs to the CPU.
|
// and offloading the expert FFNs to the CPU.
|
||||||
@ -1631,7 +1660,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
if (cfg.no_kv_offload) args.push('--no-kv-offload')
|
if (cfg.no_kv_offload) args.push('--no-kv-offload')
|
||||||
if (isEmbedding) {
|
if (isEmbedding) {
|
||||||
args.push('--embedding')
|
args.push('--embedding')
|
||||||
args.push('--pooling mean')
|
args.push('--pooling', 'mean')
|
||||||
} else {
|
} else {
|
||||||
if (cfg.ctx_size > 0) args.push('--ctx-size', String(cfg.ctx_size))
|
if (cfg.ctx_size > 0) args.push('--ctx-size', String(cfg.ctx_size))
|
||||||
if (cfg.n_predict > 0) args.push('--n-predict', String(cfg.n_predict))
|
if (cfg.n_predict > 0) args.push('--n-predict', String(cfg.n_predict))
|
||||||
@ -1670,6 +1699,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
libraryPath,
|
libraryPath,
|
||||||
args,
|
args,
|
||||||
envs,
|
envs,
|
||||||
|
isEmbedding,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return sInfo
|
return sInfo
|
||||||
@ -2005,6 +2035,69 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
libraryPath,
|
libraryPath,
|
||||||
envs,
|
envs,
|
||||||
})
|
})
|
||||||
|
// On Linux with AMD GPUs, llama.cpp via Vulkan may report UMA (shared) memory as device-local.
|
||||||
|
// For clearer UX, override with dedicated VRAM from the hardware plugin when available.
|
||||||
|
try {
|
||||||
|
const sysInfo = await getSystemInfo()
|
||||||
|
if (sysInfo?.os_type === 'linux' && Array.isArray(sysInfo.gpus)) {
|
||||||
|
const usage = await getSystemUsage()
|
||||||
|
if (usage && Array.isArray(usage.gpus)) {
|
||||||
|
const uuidToUsage: Record<string, { total_memory: number; used_memory: number }> = {}
|
||||||
|
for (const u of usage.gpus as any[]) {
|
||||||
|
if (u && typeof u.uuid === 'string') {
|
||||||
|
uuidToUsage[u.uuid] = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexToAmdUuid = new Map<number, string>()
|
||||||
|
for (const gpu of sysInfo.gpus as any[]) {
|
||||||
|
const vendorStr =
|
||||||
|
typeof gpu?.vendor === 'string'
|
||||||
|
? gpu.vendor
|
||||||
|
: typeof gpu?.vendor === 'object' && gpu.vendor !== null
|
||||||
|
? String(gpu.vendor)
|
||||||
|
: ''
|
||||||
|
if (
|
||||||
|
vendorStr.toUpperCase().includes('AMD') &&
|
||||||
|
gpu?.vulkan_info &&
|
||||||
|
typeof gpu.vulkan_info.index === 'number' &&
|
||||||
|
typeof gpu.uuid === 'string'
|
||||||
|
) {
|
||||||
|
indexToAmdUuid.set(gpu.vulkan_info.index, gpu.uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indexToAmdUuid.size > 0) {
|
||||||
|
const adjusted = dList.map((dev) => {
|
||||||
|
if (dev.id?.startsWith('Vulkan')) {
|
||||||
|
const match = /^Vulkan(\d+)/.exec(dev.id)
|
||||||
|
if (match) {
|
||||||
|
const vIdx = Number(match[1])
|
||||||
|
const uuid = indexToAmdUuid.get(vIdx)
|
||||||
|
if (uuid) {
|
||||||
|
const u = uuidToUsage[uuid]
|
||||||
|
if (
|
||||||
|
u &&
|
||||||
|
typeof u.total_memory === 'number' &&
|
||||||
|
typeof u.used_memory === 'number'
|
||||||
|
) {
|
||||||
|
const total = Math.max(0, Math.floor(u.total_memory))
|
||||||
|
const free = Math.max(0, Math.floor(u.total_memory - u.used_memory))
|
||||||
|
return { ...dev, mem: total, free }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dev
|
||||||
|
})
|
||||||
|
return adjusted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Device memory override (AMD/Linux) failed:', e)
|
||||||
|
}
|
||||||
|
|
||||||
return dList
|
return dList
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to query devices:\n', error)
|
logger.error('Failed to query devices:\n', error)
|
||||||
@ -2013,6 +2106,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async embed(text: string[]): Promise<EmbeddingResponse> {
|
async embed(text: string[]): Promise<EmbeddingResponse> {
|
||||||
|
// Ensure the sentence-transformer model is present
|
||||||
let sInfo = await this.findSessionByModel('sentence-transformer-mini')
|
let sInfo = await this.findSessionByModel('sentence-transformer-mini')
|
||||||
if (!sInfo) {
|
if (!sInfo) {
|
||||||
const downloadedModelList = await this.list()
|
const downloadedModelList = await this.list()
|
||||||
@ -2026,30 +2120,45 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
'https://huggingface.co/second-state/All-MiniLM-L6-v2-Embedding-GGUF/resolve/main/all-MiniLM-L6-v2-ggml-model-f16.gguf?download=true',
|
'https://huggingface.co/second-state/All-MiniLM-L6-v2-Embedding-GGUF/resolve/main/all-MiniLM-L6-v2-ggml-model-f16.gguf?download=true',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sInfo = await this.load('sentence-transformer-mini')
|
// Load specifically in embedding mode
|
||||||
|
sInfo = await this.load('sentence-transformer-mini', undefined, true)
|
||||||
}
|
}
|
||||||
const baseUrl = `http://localhost:${sInfo.port}/v1/embeddings`
|
|
||||||
const headers = {
|
const attemptRequest = async (session: SessionInfo) => {
|
||||||
'Content-Type': 'application/json',
|
const baseUrl = `http://localhost:${session.port}/v1/embeddings`
|
||||||
'Authorization': `Bearer ${sInfo.api_key}`,
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${session.api_key}`,
|
||||||
|
}
|
||||||
|
const body = JSON.stringify({
|
||||||
|
input: text,
|
||||||
|
model: session.model_id,
|
||||||
|
encoding_format: 'float',
|
||||||
|
})
|
||||||
|
const response = await fetch(baseUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try with the existing session (may have been started without --embedding previously)
|
||||||
|
let response = await attemptRequest(sInfo)
|
||||||
|
|
||||||
|
// If embeddings endpoint is not available (501), reload with embedding mode and retry once
|
||||||
|
if (response.status === 501) {
|
||||||
|
try {
|
||||||
|
await this.unload('sentence-transformer-mini')
|
||||||
|
} catch {}
|
||||||
|
sInfo = await this.load('sentence-transformer-mini', undefined, true)
|
||||||
|
response = await attemptRequest(sInfo)
|
||||||
}
|
}
|
||||||
const body = JSON.stringify({
|
|
||||||
input: text,
|
|
||||||
model: sInfo.model_id,
|
|
||||||
encoding_format: 'float',
|
|
||||||
})
|
|
||||||
const response = await fetch(baseUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => null)
|
const errorData = await response.json().catch(() => null)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`API request failed with status ${response.status}: ${JSON.stringify(
|
`API request failed with status ${response.status}: ${JSON.stringify(errorData)}`
|
||||||
errorData
|
|
||||||
)}`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const responseData = await response.json()
|
const responseData = await response.json()
|
||||||
@ -2151,7 +2260,12 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
if (mmprojPath && !this.isAbsolutePath(mmprojPath))
|
if (mmprojPath && !this.isAbsolutePath(mmprojPath))
|
||||||
mmprojPath = await joinPath([await getJanDataFolderPath(), path])
|
mmprojPath = await joinPath([await getJanDataFolderPath(), path])
|
||||||
try {
|
try {
|
||||||
const result = await planModelLoadInternal(path, this.memoryMode, mmprojPath, requestedCtx)
|
const result = await planModelLoadInternal(
|
||||||
|
path,
|
||||||
|
this.memoryMode,
|
||||||
|
mmprojPath,
|
||||||
|
requestedCtx
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(String(e))
|
throw new Error(String(e))
|
||||||
@ -2279,12 +2393,18 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate text tokens
|
// Calculate text tokens
|
||||||
const messages = JSON.stringify({ messages: opts.messages })
|
// Use chat_template_kwargs from opts if provided, otherwise default to disable enable_thinking
|
||||||
|
const tokenizeRequest = {
|
||||||
|
messages: opts.messages,
|
||||||
|
chat_template_kwargs: opts.chat_template_kwargs || {
|
||||||
|
enable_thinking: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let parseResponse = await fetch(`${baseUrl}/apply-template`, {
|
let parseResponse = await fetch(`${baseUrl}/apply-template`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: messages,
|
body: JSON.stringify(tokenizeRequest),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!parseResponse.ok) {
|
if (!parseResponse.ok) {
|
||||||
|
|||||||
33
extensions/rag-extension/package.json
Normal file
33
extensions/rag-extension/package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@janhq/rag-extension",
|
||||||
|
"productName": "RAG Tools",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Registers RAG tools and orchestrates retrieval across parser, embeddings, and vector DB",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/module.js",
|
||||||
|
"author": "Jan <service@jan.ai>",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rolldown -c rolldown.config.mjs",
|
||||||
|
"build:publish": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cpx": "1.5.0",
|
||||||
|
"rimraf": "6.0.1",
|
||||||
|
"rolldown": "1.0.0-beta.1",
|
||||||
|
"typescript": "5.9.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@janhq/core": "../../core/package.tgz",
|
||||||
|
"@janhq/tauri-plugin-rag-api": "link:../../src-tauri/plugins/tauri-plugin-rag",
|
||||||
|
"@janhq/tauri-plugin-vector-db-api": "link:../../src-tauri/plugins/tauri-plugin-vector-db"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/*",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"installConfig": {
|
||||||
|
"hoistingLimits": "workspaces"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.5.3"
|
||||||
|
}
|
||||||
14
extensions/rag-extension/rolldown.config.mjs
Normal file
14
extensions/rag-extension/rolldown.config.mjs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'rolldown'
|
||||||
|
import settingJson from './settings.json' with { type: 'json' }
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
input: 'src/index.ts',
|
||||||
|
output: {
|
||||||
|
format: 'esm',
|
||||||
|
file: 'dist/index.js',
|
||||||
|
},
|
||||||
|
platform: 'browser',
|
||||||
|
define: {
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
|
},
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user