Merge branch 'dev' into patch-1

This commit is contained in:
Ashu 2024-11-13 23:03:32 +05:30 committed by GitHub
commit 44b7198bdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 258 additions and 191 deletions

View File

@ -114,15 +114,14 @@ jobs:
- name: Upload latest-mac.yml - name: Upload latest-mac.yml
if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }} if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }}
run: | run: |
aws s3 cp ./latest-mac.yml "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-latest/latest-mac.yml" aws s3 cp ./latest-mac.yml "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/latest-mac.yml"
aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-latest/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/latest/ aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/nightly/
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 }}
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: [build-macos-x64, build-macos-arm64, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, combine-latest-mac-yml] needs: [build-macos-x64, build-macos-arm64, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, combine-latest-mac-yml]
secrets: inherit secrets: inherit

View File

@ -60,16 +60,16 @@ jobs:
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/latest", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-latest", "channel": "latest"}]' electron/package.json > /tmp/package.json jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
cat electron/package.json cat electron/package.json
# chmod +x .github/scripts/rename-app.sh chmod +x .github/scripts/rename-app.sh
# .github/scripts/rename-app.sh ./electron/package.json nightly .github/scripts/rename-app.sh ./electron/package.json nightly
# chmod +x .github/scripts/rename-workspace.sh chmod +x .github/scripts/rename-workspace.sh
# .github/scripts/rename-workspace.sh ./package.json nightly .github/scripts/rename-workspace.sh ./package.json nightly
# echo "------------------------" echo "------------------------"
# cat ./electron/package.json cat ./electron/package.json
# echo "------------------------" echo "------------------------"
- name: Change App Name for beta version - name: Change App Name for beta version
if: inputs.beta == true if: inputs.beta == true

View File

@ -72,20 +72,20 @@ jobs:
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/latest", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-latest", "channel": "latest"}]' electron/package.json > /tmp/package.json jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
# cat electron/package.json # cat electron/package.json
# chmod +x .github/scripts/rename-app.sh chmod +x .github/scripts/rename-app.sh
# .github/scripts/rename-app.sh ./electron/package.json nightly .github/scripts/rename-app.sh ./electron/package.json nightly
# chmod +x .github/scripts/rename-workspace.sh chmod +x .github/scripts/rename-workspace.sh
# .github/scripts/rename-workspace.sh ./package.json nightly .github/scripts/rename-workspace.sh ./package.json nightly
# echo "------------------------" echo "------------------------"
# cat ./electron/package.json cat ./electron/package.json
# echo "------------------------" echo "------------------------"
- name: Change App Name for beta version - name: Change App Name for beta version
if: inputs.beta == true if: inputs.beta == true

View File

@ -72,20 +72,20 @@ jobs:
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/latest", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-latest", "channel": "latest"}]' electron/package.json > /tmp/package.json jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
# cat electron/package.json cat electron/package.json
# chmod +x .github/scripts/rename-app.sh chmod +x .github/scripts/rename-app.sh
# .github/scripts/rename-app.sh ./electron/package.json nightly .github/scripts/rename-app.sh ./electron/package.json nightly
# chmod +x .github/scripts/rename-workspace.sh chmod +x .github/scripts/rename-workspace.sh
# .github/scripts/rename-workspace.sh ./package.json nightly .github/scripts/rename-workspace.sh ./package.json nightly
# echo "------------------------" echo "------------------------"
# cat ./electron/package.json cat ./electron/package.json
# echo "------------------------" echo "------------------------"
- name: Change App Name for beta version - name: Change App Name for beta version
if: inputs.beta == true if: inputs.beta == true

View File

@ -73,24 +73,24 @@ jobs:
jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
mv /tmp/package.json web/package.json mv /tmp/package.json web/package.json
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/latest", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-latest", "channel": "latest"}]' electron/package.json > /tmp/package.json jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
mv /tmp/package.json electron/package.json mv /tmp/package.json electron/package.json
cat electron/package.json cat electron/package.json
# chmod +x .github/scripts/rename-app.sh chmod +x .github/scripts/rename-app.sh
# .github/scripts/rename-app.sh ./electron/package.json nightly .github/scripts/rename-app.sh ./electron/package.json nightly
# chmod +x .github/scripts/rename-workspace.sh chmod +x .github/scripts/rename-workspace.sh
# .github/scripts/rename-workspace.sh ./package.json nightly .github/scripts/rename-workspace.sh ./package.json nightly
# chmod +x .github/scripts/rename-uninstaller.sh chmod +x .github/scripts/rename-uninstaller.sh
# .github/scripts/rename-uninstaller.sh nightly .github/scripts/rename-uninstaller.sh nightly
# echo "------------------------" echo "------------------------"
# cat ./electron/package.json cat ./electron/package.json
# echo "------------------------" echo "------------------------"
# cat ./package.json cat ./package.json
# echo "------------------------" echo "------------------------"
- name: Change App Name for beta version - name: Change App Name for beta version
if: inputs.beta == true if: inputs.beta == true

View File

@ -47,11 +47,11 @@ jobs:
with: with:
args: | args: |
Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}: Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}:
- Windows: https://delta.jan.ai/latest/jan-win-x64-{{ VERSION }}.exe - Windows: https://delta.jan.ai/nightly/jan-win-x64-{{ VERSION }}.exe
- macOS Intel: https://delta.jan.ai/latest/jan-mac-x64-{{ VERSION }}.dmg - macOS Intel: https://delta.jan.ai/nightly/jan-mac-x64-{{ VERSION }}.dmg
- macOS Apple Silicon: https://delta.jan.ai/latest/jan-mac-arm64-{{ VERSION }}.dmg - macOS Apple Silicon: https://delta.jan.ai/nightly/jan-mac-arm64-{{ VERSION }}.dmg
- Linux Deb: https://delta.jan.ai/latest/jan-linux-amd64-{{ VERSION }}.deb - Linux Deb: https://delta.jan.ai/nightly/jan-linux-amd64-{{ VERSION }}.deb
- Linux AppImage: https://delta.jan.ai/latest/jan-linux-x86_64-{{ VERSION }}.AppImage - Linux AppImage: https://delta.jan.ai/nightly/jan-linux-x86_64-{{ VERSION }}.AppImage
- Github action run: https://github.com/janhq/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 }}

View File

@ -1,6 +1,6 @@
import { resolve, sep } from 'path' import { resolve, sep } from 'path'
import { DownloadEvent } from '../../../types/api' import { DownloadEvent } from '../../../types/api'
import { normalizeFilePath, validatePath } from '../../helper/path' import { normalizeFilePath } from '../../helper/path'
import { getJanDataFolderPath } from '../../helper' import { getJanDataFolderPath } from '../../helper'
import { DownloadManager } from '../../helper/download' import { DownloadManager } from '../../helper/download'
import { createWriteStream, renameSync } from 'fs' import { createWriteStream, renameSync } from 'fs'
@ -37,7 +37,6 @@ export class Downloader implements Processor {
const modelId = downloadRequest.modelId ?? array.pop() ?? '' const modelId = downloadRequest.modelId ?? array.pop() ?? ''
const destination = resolve(getJanDataFolderPath(), normalizedPath) const destination = resolve(getJanDataFolderPath(), normalizedPath)
validatePath(destination)
const rq = request({ url, strictSSL, proxy }) const rq = request({ url, strictSSL, proxy })
// Put request to download manager instance // Put request to download manager instance

View File

@ -1,5 +1,5 @@
import { join, resolve } from 'path' import { join, resolve } from 'path'
import { normalizeFilePath, validatePath } from '../../helper/path' import { normalizeFilePath } from '../../helper/path'
import { getJanDataFolderPath } from '../../helper' import { getJanDataFolderPath } from '../../helper'
import { Processor } from './Processor' import { Processor } from './Processor'
import fs from 'fs' import fs from 'fs'
@ -36,7 +36,6 @@ export class FileSystem implements Processor {
return path return path
} }
const absolutePath = resolve(path) const absolutePath = resolve(path)
validatePath(absolutePath)
return absolutePath return absolutePath
}) })
) )
@ -55,7 +54,6 @@ export class FileSystem implements Processor {
} }
const absolutePath = resolve(path) const absolutePath = resolve(path)
validatePath(absolutePath)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.rm(absolutePath, { recursive: true, force: true }, (err) => { fs.rm(absolutePath, { recursive: true, force: true }, (err) => {
@ -79,7 +77,6 @@ export class FileSystem implements Processor {
} }
const absolutePath = resolve(path) const absolutePath = resolve(path)
validatePath(absolutePath)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.mkdir(absolutePath, { recursive: true }, (err) => { fs.mkdir(absolutePath, { recursive: true }, (err) => {

View File

@ -1,6 +1,6 @@
import { basename, join } from 'path' import { basename, join } from 'path'
import fs, { readdirSync } from 'fs' import fs, { readdirSync } from 'fs'
import { appResourcePath, normalizeFilePath, validatePath } from '../../helper/path' import { appResourcePath, normalizeFilePath } from '../../helper/path'
import { defaultAppConfig, getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper' import { defaultAppConfig, getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper'
import { Processor } from './Processor' import { Processor } from './Processor'
import { FileStat } from '../../../types' import { FileStat } from '../../../types'
@ -61,7 +61,6 @@ export class FSExt implements Processor {
const dataBuffer = Buffer.from(data, 'base64') const dataBuffer = Buffer.from(data, 'base64')
const writePath = join(getJanDataFolderPath(), normalizedPath) const writePath = join(getJanDataFolderPath(), normalizedPath)
validatePath(writePath)
fs.writeFileSync(writePath, dataBuffer) fs.writeFileSync(writePath, dataBuffer)
} catch (err) { } catch (err) {
console.error(`writeFile ${path} result: ${err}`) console.error(`writeFile ${path} result: ${err}`)
@ -69,7 +68,6 @@ export class FSExt implements Processor {
} }
copyFile(src: string, dest: string): Promise<void> { copyFile(src: string, dest: string): Promise<void> {
validatePath(dest)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fs.copyFile(src, dest, (err) => { fs.copyFile(src, dest, (err) => {
if (err) { if (err) {

View File

@ -34,18 +34,4 @@ export function appResourcePath() {
// server // server
return join(global.core.appPath(), '../../..') return join(global.core.appPath(), '../../..')
} }
export function validatePath(path: string) {
const appDataFolderPath = getJanDataFolderPath()
const resourcePath = appResourcePath()
const applicationSupportPath = global.core?.appPath() ?? resourcePath
const absolutePath = resolve(__dirname, path)
if (
![appDataFolderPath, resourcePath, applicationSupportPath].some((whiteListedPath) =>
absolutePath.startsWith(whiteListedPath)
)
) {
throw new Error(`Invalid path: ${absolutePath}`)
}
}

View File

@ -61,6 +61,9 @@ async function checkAndMigrateTheme(
) )
if (existingTheme) { if (existingTheme) {
const desTheme = join(janDataThemesFolder, existingTheme) const desTheme = join(janDataThemesFolder, existingTheme)
if (!lstatSync(desTheme).isDirectory()) {
return
}
console.debug('Updating theme', existingTheme) console.debug('Updating theme', existingTheme)
rmdirSync(desTheme, { recursive: true }) rmdirSync(desTheme, { recursive: true })
cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), { cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), {

View File

@ -1,7 +1,7 @@
{ {
"name": "@janhq/inference-cortex-extension", "name": "@janhq/inference-cortex-extension",
"productName": "Cortex Inference Engine", "productName": "Cortex Inference Engine",
"version": "1.0.20", "version": "1.0.21",
"description": "This extension embeds cortex.cpp, a lightweight inference engine written in C++. See https://jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.", "description": "This extension embeds cortex.cpp, a lightweight inference engine written in C++. See https://jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.",
"main": "dist/index.js", "main": "dist/index.js",
"node": "dist/node/index.cjs.js", "node": "dist/node/index.cjs.js",

View File

@ -0,0 +1,36 @@
{
"sources": [
{
"filename": "Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf",
"url": "https://huggingface.co/bartowski/Qwen2.5-Coder-14B-Instruct-GGUF/resolve/main/Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf"
}
],
"id": "qwen2.5-coder-14b-instruct",
"object": "model",
"name": "Qwen2.5 Coder 14B Instruct Q4",
"version": "1.0",
"description": "Qwen2.5-Coder is the latest series of Code-Specific Qwen large language models. Significantly improvements in code generation, code reasoning and code fixing.",
"format": "gguf",
"settings": {
"ctx_len": 32768,
"prompt_template": "<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant",
"llama_model_path": "Qwen2.5-Coder-14B-Instruct-Q4_K_M.gguf",
"ngl": 29
},
"parameters": {
"temperature": 0.7,
"top_p": 0.95,
"stream": true,
"max_tokens": 32768,
"stop": ["<|endoftext|>", "<|im_end|>"],
"frequency_penalty": 0,
"presence_penalty": 0
},
"metadata": {
"author": "QwenLM",
"tags": ["14B", "Featured"],
"size": 8990000000
},
"engine": "llama-cpp"
}

View File

@ -0,0 +1,36 @@
{
"sources": [
{
"filename": "Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf",
"url": "https://huggingface.co/bartowski/Qwen2.5-Coder-32B-Instruct-GGUF/resolve/main/Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf"
}
],
"id": "qwen2.5-coder-32b-instruct",
"object": "model",
"name": "Qwen2.5 Coder 32B Instruct Q4",
"version": "1.0",
"description": "Qwen2.5-Coder is the latest series of Code-Specific Qwen large language models. Significantly improvements in code generation, code reasoning and code fixing.",
"format": "gguf",
"settings": {
"ctx_len": 32768,
"prompt_template": "<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant",
"llama_model_path": "Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf",
"ngl": 29
},
"parameters": {
"temperature": 0.7,
"top_p": 0.95,
"stream": true,
"max_tokens": 32768,
"stop": ["<|endoftext|>", "<|im_end|>"],
"frequency_penalty": 0,
"presence_penalty": 0
},
"metadata": {
"author": "QwenLM",
"tags": ["32B", "Featured"],
"size": 19900000000
},
"engine": "llama-cpp"
}

View File

@ -49,6 +49,8 @@ const llama321bJson = require('./resources/models/llama3.2-1b-instruct/model.jso
const llama323bJson = require('./resources/models/llama3.2-3b-instruct/model.json') const llama323bJson = require('./resources/models/llama3.2-3b-instruct/model.json')
const qwen257bJson = require('./resources/models/qwen2.5-7b-instruct/model.json') const qwen257bJson = require('./resources/models/qwen2.5-7b-instruct/model.json')
const qwen25coder7bJson = require('./resources/models/qwen2.5-coder-7b-instruct/model.json') const qwen25coder7bJson = require('./resources/models/qwen2.5-coder-7b-instruct/model.json')
const qwen25coder14bJson = require('./resources/models/qwen2.5-coder-14b-instruct/model.json')
const qwen25coder32bJson = require('./resources/models/qwen2.5-coder-32b-instruct/model.json')
const qwen2514bJson = require('./resources/models/qwen2.5-14b-instruct/model.json') const qwen2514bJson = require('./resources/models/qwen2.5-14b-instruct/model.json')
const qwen2532bJson = require('./resources/models/qwen2.5-32b-instruct/model.json') const qwen2532bJson = require('./resources/models/qwen2.5-32b-instruct/model.json')
const qwen2572bJson = require('./resources/models/qwen2.5-72b-instruct/model.json') const qwen2572bJson = require('./resources/models/qwen2.5-72b-instruct/model.json')
@ -108,6 +110,8 @@ export default [
llama323bJson, llama323bJson,
qwen257bJson, qwen257bJson,
qwen25coder7bJson, qwen25coder7bJson,
qwen25coder14bJson,
qwen25coder32bJson,
qwen2514bJson, qwen2514bJson,
qwen2532bJson, qwen2532bJson,
qwen2572bJson, qwen2572bJson,
@ -115,6 +119,7 @@ export default [
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`), NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
DEFAULT_SETTINGS: JSON.stringify(defaultSettingJson), DEFAULT_SETTINGS: JSON.stringify(defaultSettingJson),
CORTEX_API_URL: JSON.stringify('http://127.0.0.1:39291'), CORTEX_API_URL: JSON.stringify('http://127.0.0.1:39291'),
CORTEX_SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'),
}), }),
// Allow json resolution // Allow json resolution
json(), json(),

View File

@ -1,5 +1,6 @@
declare const NODE: string declare const NODE: string
declare const CORTEX_API_URL: string declare const CORTEX_API_URL: string
declare const CORTEX_SOCKET_URL: string
declare const DEFAULT_SETTINGS: Array<any> declare const DEFAULT_SETTINGS: Array<any>
declare const MODELS: Array<any> declare const MODELS: Array<any>

View File

@ -16,17 +16,29 @@ import {
getJanDataFolderPath, getJanDataFolderPath,
extractModelLoadParams, extractModelLoadParams,
fs, fs,
events,
ModelEvent
} from '@janhq/core' } from '@janhq/core'
import PQueue from 'p-queue' import PQueue from 'p-queue'
import ky from 'ky' import ky from 'ky'
/**
* Event subscription types of Downloader
*/
enum DownloadTypes {
DownloadUpdated = 'onFileDownloadUpdate',
DownloadError = 'onFileDownloadError',
DownloadSuccess = 'onFileDownloadSuccess',
DownloadStopped = 'onFileDownloadStopped',
DownloadStarted = 'onFileDownloadStarted',
}
/** /**
* A class that implements the InferenceExtension interface from the @janhq/core package. * A class that implements the InferenceExtension interface from the @janhq/core package.
* The class provides methods for initializing and stopping a model, and for making inference requests. * The class provides methods for initializing and stopping a model, and for making inference requests.
* It also subscribes to events emitted by the @janhq/core package and handles new message requests. * It also subscribes to events emitted by the @janhq/core package and handles new message requests.
*/ */
export default class JanInferenceCortexExtension extends LocalOAIEngine { export default class JanInferenceCortexExtension extends LocalOAIEngine {
// DEPRECATED
nodeModule: string = 'node' nodeModule: string = 'node'
queue = new PQueue({ concurrency: 1 }) queue = new PQueue({ concurrency: 1 })
@ -38,6 +50,11 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
*/ */
inferenceUrl = `${CORTEX_API_URL}/v1/chat/completions` inferenceUrl = `${CORTEX_API_URL}/v1/chat/completions`
/**
* Socket instance of events subscription
*/
socket?: WebSocket = undefined
/** /**
* Subscribes to events emitted by the @janhq/core package. * Subscribes to events emitted by the @janhq/core package.
*/ */
@ -55,6 +72,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
this.queue.add(() => this.healthz()) this.queue.add(() => this.healthz())
this.subscribeToEvents()
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
this.clean() this.clean()
}) })
@ -138,7 +157,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
methods: ['get'], methods: ['get'],
}, },
}) })
.then(() => {}) .then(() => { })
} }
/** /**
@ -154,6 +173,50 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
// Do nothing // Do nothing
}) })
} }
/**
* Subscribe to cortex.cpp websocket events
*/
subscribeToEvents() {
this.queue.add(
() =>
new Promise<void>((resolve) => {
this.socket = new WebSocket(`${CORTEX_SOCKET_URL}/events`)
this.socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
const transferred = data.task.items.reduce(
(acc: number, cur: any) => acc + cur.downloadedBytes,
0
)
const total = data.task.items.reduce(
(acc: number, cur: any) => acc + cur.bytes,
0
)
const percent = total > 0 ? transferred / total : 0
events.emit(DownloadTypes[data.type as keyof typeof DownloadTypes], {
modelId: data.task.id,
percent: percent,
size: {
transferred: transferred,
total: total,
},
})
// Update models list from Hub
if (data.type === DownloadTypes.DownloadSuccess) {
// Delay for the state update from cortex.cpp
// Just to be sure
setTimeout(() => {
events.emit(ModelEvent.OnModelsUpdate, {})
}, 500)
}
})
resolve()
})
)
}
} }
/// Legacy /// Legacy

View File

@ -70,16 +70,17 @@ export default class JanInferenceOpenAIExtension extends RemoteOAIEngine {
* Tranform the payload before sending it to the inference endpoint. * Tranform the payload before sending it to the inference endpoint.
* The new preview models such as o1-mini and o1-preview replaced max_tokens by max_completion_tokens parameter. * The new preview models such as o1-mini and o1-preview replaced max_tokens by max_completion_tokens parameter.
* Others do not. * Others do not.
* @param payload * @param payload
* @returns * @returns
*/ */
transformPayload = (payload: OpenAIPayloadType): OpenAIPayloadType => { transformPayload = (payload: OpenAIPayloadType): OpenAIPayloadType => {
// Transform the payload for preview models // Transform the payload for preview models
if (this.previewModels.includes(payload.model)) { if (this.previewModels.includes(payload.model)) {
const { max_tokens, ...params } = payload const { max_tokens, temperature, top_p, stop, ...params } = payload
return { return {
...params, ...params,
max_completion_tokens: max_tokens, max_completion_tokens: max_tokens,
stream: false // o1 only support stream = false
} }
} }
// Pass through for non-preview models // Pass through for non-preview models

View File

@ -1,6 +1,6 @@
import PQueue from 'p-queue' import PQueue from 'p-queue'
import ky from 'ky' import ky from 'ky'
import { events, extractModelLoadParams, Model, ModelEvent } from '@janhq/core' import { extractModelLoadParams, Model } from '@janhq/core'
import { extractInferenceParams } from '@janhq/core' import { extractInferenceParams } from '@janhq/core'
/** /**
* cortex.cpp Model APIs interface * cortex.cpp Model APIs interface
@ -24,21 +24,11 @@ type ModelList = {
data: any[] data: any[]
} }
enum DownloadTypes {
DownloadUpdated = 'onFileDownloadUpdate',
DownloadError = 'onFileDownloadError',
DownloadSuccess = 'onFileDownloadSuccess',
DownloadStopped = 'onFileDownloadStopped',
DownloadStarted = 'onFileDownloadStarted',
}
export class CortexAPI implements ICortexAPI { export class CortexAPI implements ICortexAPI {
queue = new PQueue({ concurrency: 1 }) queue = new PQueue({ concurrency: 1 })
socket?: WebSocket = undefined
constructor() { constructor() {
this.queue.add(() => this.healthz()) this.queue.add(() => this.healthz())
this.subscribeToEvents()
} }
/** /**
@ -172,49 +162,6 @@ export class CortexAPI implements ICortexAPI {
.then(() => {}) .then(() => {})
} }
/**
* Subscribe to cortex.cpp websocket events
*/
subscribeToEvents() {
this.queue.add(
() =>
new Promise<void>((resolve) => {
this.socket = new WebSocket(`${SOCKET_URL}/events`)
this.socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
const transferred = data.task.items.reduce(
(acc, cur) => acc + cur.downloadedBytes,
0
)
const total = data.task.items.reduce(
(acc, cur) => acc + cur.bytes,
0
)
const percent = total > 0 ? transferred / total : 0
events.emit(DownloadTypes[data.type], {
modelId: data.task.id,
percent: percent,
size: {
transferred: transferred,
total: total,
},
})
// Update models list from Hub
if (data.type === DownloadTypes.DownloadSuccess) {
// Delay for the state update from cortex.cpp
// Just to be sure
setTimeout(() => {
events.emit(ModelEvent.OnModelsUpdate, {})
}, 500)
}
})
resolve()
})
)
}
/** /**
* TRansform model to the expected format (e.g. parameters, settings, metadata) * TRansform model to the expected format (e.g. parameters, settings, metadata)
* @param model * @param model

View File

@ -179,8 +179,8 @@ export default class JanModelExtension extends ModelExtension {
if (toImportModels.length > 0) { if (toImportModels.length > 0) {
// Import models // Import models
await Promise.all( await Promise.all(
toImportModels.map(async (model: Model & { file_path: string }) => toImportModels.map(async (model: Model & { file_path: string }) => {
this.importModel( return this.importModel(
model.id, model.id,
model.sources[0].url.startsWith('http') || model.sources[0].url.startsWith('http') ||
!(await fs.existsSync(model.sources[0].url)) !(await fs.existsSync(model.sources[0].url))
@ -200,7 +200,7 @@ export default class JanModelExtension extends ModelExtension {
...model.parameters, ...model.parameters,
} as Partial<Model>) } as Partial<Model>)
}) })
) })
) )
return currentModels return currentModels

View File

@ -79,7 +79,7 @@ const SystemMonitor = () => {
{showSystemMonitorPanel && ( {showSystemMonitorPanel && (
<div <div
className={twMerge( className={twMerge(
'fixed bottom-9 left-[49px] z-50 flex h-[200px] w-[calc(100%-48px)] flex-shrink-0 flex-col border-t border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))]', 'system-monitor-panel fixed bottom-9 left-[49px] z-50 flex w-[calc(100%-48px)] flex-shrink-0 flex-col border-t border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))]',
showFullScreen && 'h-[calc(100%-63px)]', showFullScreen && 'h-[calc(100%-63px)]',
reduceTransparent && 'w-[calc(100%-48px)] rounded-none' reduceTransparent && 'w-[calc(100%-48px)] rounded-none'
)} )}
@ -147,7 +147,6 @@ const SystemMonitor = () => {
<span className="flex-shrink-0 ">{ramUtilitized}%</span> <span className="flex-shrink-0 ">{ramUtilitized}%</span>
</div> </div>
</div> </div>
{gpus.length > 0 && ( {gpus.length > 0 && (
<div className="mb-4 border-b border-[hsla(var(--app-border))] pb-4 last:border-none"> <div className="mb-4 border-b border-[hsla(var(--app-border))] pb-4 last:border-none">
{gpus.map((gpu, index) => { {gpus.map((gpu, index) => {

View File

@ -108,6 +108,11 @@ const ModelDropdown = ({
const filteredDownloadedModels = useMemo( const filteredDownloadedModels = useMemo(
() => () =>
configuredModels configuredModels
.concat(
downloadedModels.filter(
(e) => !configuredModels.some((x) => x.id === e.id)
)
)
.filter((e) => .filter((e) =>
e.name.toLowerCase().includes(searchText.toLowerCase().trim()) e.name.toLowerCase().includes(searchText.toLowerCase().trim())
) )

View File

@ -29,7 +29,7 @@ export default function useDropModelBinaries() {
const importingModels: ImportingModel[] = supportedFiles.map((file) => ({ const importingModels: ImportingModel[] = supportedFiles.map((file) => ({
importId: uuidv4(), importId: uuidv4(),
modelId: undefined, modelId: undefined,
name: file.name.replace('.gguf', ''), name: file.name.replace(/ /g, '').replace('.gguf', ''),
description: '', description: '',
path: file.path, path: file.path,
tags: [], tags: [],

View File

@ -5,14 +5,14 @@ import {
ImportingModel, ImportingModel,
LocalImportModelEvent, LocalImportModelEvent,
Model, Model,
ModelEvent,
ModelExtension, ModelExtension,
OptionType, OptionType,
events, events,
fs, fs,
baseName,
} from '@janhq/core' } from '@janhq/core'
import { atom, useSetAtom } from 'jotai' import { atom, useAtomValue, useSetAtom } from 'jotai'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -23,6 +23,7 @@ import { FilePathWithSize } from '@/utils/file'
import { extensionManager } from '@/extension' import { extensionManager } from '@/extension'
import { import {
addDownloadingModelAtom, addDownloadingModelAtom,
downloadedModelsAtom,
importingModelsAtom, importingModelsAtom,
removeDownloadingModelAtom, removeDownloadingModelAtom,
} from '@/helpers/atoms/Model.atom' } from '@/helpers/atoms/Model.atom'
@ -58,11 +59,24 @@ const useImportModel = () => {
const setImportingModels = useSetAtom(importingModelsAtom) const setImportingModels = useSetAtom(importingModelsAtom)
const addDownloadingModel = useSetAtom(addDownloadingModelAtom) const addDownloadingModel = useSetAtom(addDownloadingModelAtom)
const removeDownloadingModel = useSetAtom(removeDownloadingModelAtom) const removeDownloadingModel = useSetAtom(removeDownloadingModelAtom)
const downloadedModels = useAtomValue(downloadedModelsAtom)
const incrementalModelName = useCallback(
(name: string, startIndex: number = 0): string => {
const newModelName = startIndex ? `${name}-${startIndex}` : name
if (downloadedModels.some((model) => model.id === newModelName)) {
return incrementalModelName(name, startIndex + 1)
} else {
return newModelName
}
},
[downloadedModels]
)
const importModels = useCallback( const importModels = useCallback(
(models: ImportingModel[], optionType: OptionType) => { (models: ImportingModel[], optionType: OptionType) => {
models.map((model) => { models.map(async (model) => {
const modelId = model.modelId ?? model.path.split('/').pop() const modelId = model.modelId ?? incrementalModelName(model.name)
if (modelId) { if (modelId) {
addDownloadingModel(modelId) addDownloadingModel(modelId)
extensionManager extensionManager
@ -78,7 +92,7 @@ const useImportModel = () => {
} }
}) })
}, },
[addDownloadingModel, removeDownloadingModel] [addDownloadingModel, incrementalModelName, removeDownloadingModel]
) )
const updateModelInfo = useCallback( const updateModelInfo = useCallback(
@ -100,7 +114,7 @@ const useImportModel = () => {
({ path, name, size }: FilePathWithSize) => ({ ({ path, name, size }: FilePathWithSize) => ({
importId: uuidv4(), importId: uuidv4(),
modelId: undefined, modelId: undefined,
name: name.replace('.gguf', ''), name: name.replace(/ /g, '').replace('.gguf', ''),
description: '', description: '',
path: path, path: path,
tags: [], tags: [],

View File

@ -38,7 +38,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-circular-progressbar": "^2.1.0", "react-circular-progressbar": "^2.1.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "14.2.3",
"react-hook-form": "^7.47.0", "react-hook-form": "^7.47.0",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",

View File

@ -189,7 +189,7 @@ const Advanced = () => {
* @param gpuId * @param gpuId
* @returns * @returns
*/ */
const handleGPUChange = (gpuId: string) => { const handleGPUChange = async (gpuId: string) => {
let updatedGpusInUse = [...gpusInUse] let updatedGpusInUse = [...gpusInUse]
if (updatedGpusInUse.includes(gpuId)) { if (updatedGpusInUse.includes(gpuId)) {
updatedGpusInUse = updatedGpusInUse.filter((id) => id !== gpuId) updatedGpusInUse = updatedGpusInUse.filter((id) => id !== gpuId)
@ -208,7 +208,7 @@ const Advanced = () => {
updatedGpusInUse.push(gpuId) updatedGpusInUse.push(gpuId)
} }
setGpusInUse(updatedGpusInUse) setGpusInUse(updatedGpusInUse)
saveSettings({ gpusInUse: updatedGpusInUse }) await saveSettings({ gpusInUse: updatedGpusInUse })
window.core?.api?.relaunch() window.core?.api?.relaunch()
} }
@ -306,7 +306,13 @@ const Advanced = () => {
}) })
} }
// Stop any running model to apply the changes // Stop any running model to apply the changes
if (e.target.checked !== gpuEnabled) stopModel() if (e.target.checked !== gpuEnabled) {
stopModel().finally(() => {
setTimeout(() => {
window.location.reload()
}, 300)
})
}
}} }}
/> />
} }

View File

@ -21,7 +21,7 @@ const SelectingModelModal = () => {
const onSelectFileClick = useCallback(async () => { const onSelectFileClick = useCallback(async () => {
const platform = (await systemInformation()).osInfo?.platform const platform = (await systemInformation()).osInfo?.platform
if (platform === 'win32') { if (platform !== 'darwin') {
setImportModelStage('CHOOSE_WHAT_TO_IMPORT') setImportModelStage('CHOOSE_WHAT_TO_IMPORT')
return return
} }

View File

@ -64,49 +64,6 @@ const LoadModelError = () => {
to continue using it. to continue using it.
</p> </p>
) )
} else if (
settings &&
settings.run_mode === 'gpu' &&
!settings.vulkan &&
(!settings.nvidia_driver?.exist || !settings.cuda?.exist)
) {
return (
<>
{!settings?.cuda.exist ? (
<p>
The CUDA toolkit may be unavailable. Please use the{' '}
<span
className="cursor-pointer font-medium text-[hsla(var(--app-link))]"
onClick={() => {
setMainState(MainViewState.Settings)
if (activeThread?.assistants[0]?.model.engine) {
const engine = EngineManager.instance().get(
activeThread.assistants[0].model.engine
)
engine?.name && setSelectedSettingScreen(engine.name)
}
}}
>
Install Additional Dependencies
</span>{' '}
setting to proceed with the download / installation process.
</p>
) : (
<div>
Problem with Nvidia drivers. Please follow the{' '}
<a
className="font-medium text-[hsla(var(--app-link))]"
href="https://www.nvidia.com/Download/index.aspx"
target="_blank"
>
Nvidia Drivers guideline
</a>{' '}
to access installation instructions and ensure proper functioning
of the application.
</div>
)}
</>
)
} else { } else {
return ( return (
<div> <div>

View File

@ -147,6 +147,20 @@ const ThreadCenterPanel = () => {
const showSystemMonitorPanel = useAtomValue(showSystemMonitorPanelAtom) const showSystemMonitorPanel = useAtomValue(showSystemMonitorPanelAtom)
const [height, setHeight] = useState<number>(0)
useEffect(() => {
if (showSystemMonitorPanel) {
const element = document.querySelector('.system-monitor-panel')
if (element) {
setHeight(element.clientHeight) // You can also use offsetHeight if needed
}
} else {
setHeight(0)
}
}, [showSystemMonitorPanel])
return ( return (
<CenterPanelContainer> <CenterPanelContainer>
<div <div
@ -193,9 +207,10 @@ const ThreadCenterPanel = () => {
)} )}
<div <div
className={twMerge( className={twMerge(
'flex h-full w-full flex-col justify-between', 'flex h-full w-full flex-col justify-between'
showSystemMonitorPanel && 'h-[calc(100%-200px)]' // showSystemMonitorPanel && `h-[calc(100%-${height}px)]`
)} )}
style={{ height: `calc(100% - ${height}px)` }}
> >
{activeThread ? ( {activeThread ? (
<div className="flex h-full w-full overflow-x-hidden"> <div className="flex h-full w-full overflow-x-hidden">