commit
60cf8de832
@ -67,16 +67,17 @@ jobs:
|
|||||||
|
|
||||||
test-on-windows:
|
test-on-windows:
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
||||||
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
||||||
steps:
|
steps:
|
||||||
- name: Clean workspace
|
- name: Clean workspace
|
||||||
run: |
|
run: |
|
||||||
Remove-Item -Path .\* -Force -Recurse
|
Remove-Item -Path "\\?\$(Get-Location)\*" -Force -Recurse
|
||||||
$path = "$Env:APPDATA\jan"
|
$path = "$Env:APPDATA\jan"
|
||||||
if (Test-Path $path) {
|
if (Test-Path $path) {
|
||||||
Remove-Item $path -Recurse -Force
|
Remove-Item "\\?\$path" -Recurse -Force
|
||||||
} else {
|
} else {
|
||||||
Write-Output "Folder does not exist."
|
Write-Output "Folder does not exist."
|
||||||
}
|
}
|
||||||
|
|||||||
21
.gitignore
vendored
21
.gitignore
vendored
@ -14,6 +14,7 @@ electron/renderer
|
|||||||
electron/models
|
electron/models
|
||||||
electron/docs
|
electron/docs
|
||||||
electron/engines
|
electron/engines
|
||||||
|
electron/playwright-report
|
||||||
server/pre-install
|
server/pre-install
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
@ -21,16 +22,16 @@ package-lock.json
|
|||||||
core/lib/**
|
core/lib/**
|
||||||
|
|
||||||
# Nitro binary files
|
# Nitro binary files
|
||||||
extensions/inference-nitro-extension/bin/*/nitro
|
extensions/*-extension/bin/*/nitro
|
||||||
extensions/inference-nitro-extension/bin/*/*.metal
|
extensions/*-extension/bin/*/*.metal
|
||||||
extensions/inference-nitro-extension/bin/*/*.exe
|
extensions/*-extension/bin/*/*.exe
|
||||||
extensions/inference-nitro-extension/bin/*/*.dll
|
extensions/*-extension/bin/*/*.dll
|
||||||
extensions/inference-nitro-extension/bin/*/*.exp
|
extensions/*-extension/bin/*/*.exp
|
||||||
extensions/inference-nitro-extension/bin/*/*.lib
|
extensions/*-extension/bin/*/*.lib
|
||||||
extensions/inference-nitro-extension/bin/saved-*
|
extensions/*-extension/bin/saved-*
|
||||||
extensions/inference-nitro-extension/bin/*.tar.gz
|
extensions/*-extension/bin/*.tar.gz
|
||||||
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
|
extensions/*-extension/bin/vulkaninfoSDK.exe
|
||||||
extensions/inference-nitro-extension/bin/vulkaninfo
|
extensions/*-extension/bin/vulkaninfo
|
||||||
|
|
||||||
|
|
||||||
# Turborepo
|
# Turborepo
|
||||||
|
|||||||
20
README.md
20
README.md
@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-win-x64-0.4.7.exe'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-win-x64-0.4.8.exe'>
|
||||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-x64-0.4.7.dmg'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-x64-0.4.8.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-arm64-0.4.7.dmg'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-arm64-0.4.8.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-amd64-0.4.7.deb'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-amd64-0.4.8.deb'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-x86_64-0.4.7.AppImage'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-x86_64-0.4.8.AppImage'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.7-302.exe'>
|
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.8-324.exe'>
|
||||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.7-302.dmg'>
|
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.8-324.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.7-302.dmg'>
|
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.8-324.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.7-302.deb'>
|
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.8-324.deb'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.7-302.AppImage'>
|
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.8-324.AppImage'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -45,19 +45,24 @@
|
|||||||
"start": "rollup -c rollup.config.ts -w"
|
"start": "rollup -c rollup.config.ts -w"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^25.4.0",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/jest": "^29.5.11",
|
|
||||||
"@types/node": "^12.0.2",
|
"@types/node": "^12.0.2",
|
||||||
"eslint-plugin-jest": "^23.8.2",
|
"eslint": "8.57.0",
|
||||||
|
"eslint-plugin-jest": "^27.9.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.38.5",
|
"rollup": "^2.38.5",
|
||||||
"rollup-plugin-commonjs": "^9.1.8",
|
"rollup-plugin-commonjs": "^9.1.8",
|
||||||
"rollup-plugin-json": "^3.1.0",
|
"rollup-plugin-json": "^3.1.0",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||||
"rollup-plugin-typescript2": "^0.36.0",
|
"rollup-plugin-typescript2": "^0.36.0",
|
||||||
"ts-jest": "^26.1.1",
|
"ts-jest": "^29.1.2",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.3.3"
|
||||||
"rimraf": "^3.0.2"
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"ulid": "^2.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export default [
|
|||||||
// Allow json resolution
|
// Allow json resolution
|
||||||
json(),
|
json(),
|
||||||
// Compile TypeScript files
|
// Compile TypeScript files
|
||||||
typescript({ useTsconfigDeclarationDir: true }),
|
typescript({ useTsconfigDeclarationDir: true, exclude: ['src/*.ts', 'src/extensions/**'] }),
|
||||||
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
|
// Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs)
|
||||||
commonjs(),
|
commonjs(),
|
||||||
// Allow node_modules resolution, so you can use 'external' to control
|
// Allow node_modules resolution, so you can use 'external' to control
|
||||||
|
|||||||
@ -9,6 +9,14 @@ export enum NativeRoute {
|
|||||||
selectDirectory = 'selectDirectory',
|
selectDirectory = 'selectDirectory',
|
||||||
selectModelFiles = 'selectModelFiles',
|
selectModelFiles = 'selectModelFiles',
|
||||||
relaunch = 'relaunch',
|
relaunch = 'relaunch',
|
||||||
|
|
||||||
|
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||||
|
sendQuickAskInput = 'sendQuickAskInput',
|
||||||
|
|
||||||
|
hideMainWindow = 'hideMainWindow',
|
||||||
|
showMainWindow = 'showMainWindow',
|
||||||
|
|
||||||
|
quickAskSizeUpdated = 'quickAskSizeUpdated',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,12 +33,17 @@ export enum AppRoute {
|
|||||||
stopServer = 'stopServer',
|
stopServer = 'stopServer',
|
||||||
log = 'log',
|
log = 'log',
|
||||||
logServer = 'logServer',
|
logServer = 'logServer',
|
||||||
|
systemInformations = 'systemInformations',
|
||||||
|
showToast = 'showToast',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppEvent {
|
export enum AppEvent {
|
||||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||||
|
|
||||||
|
onUserSubmitQuickAsk = 'onUserSubmitQuickAsk',
|
||||||
|
onSelectedText = 'onSelectedText',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DownloadRoute {
|
export enum DownloadRoute {
|
||||||
@ -45,6 +58,7 @@ export enum DownloadEvent {
|
|||||||
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
||||||
onFileDownloadError = 'onFileDownloadError',
|
onFileDownloadError = 'onFileDownloadError',
|
||||||
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
||||||
|
onFileUnzipSuccess = 'onFileUnzipSuccess',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LocalImportModelEvent {
|
export enum LocalImportModelEvent {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { FileStat } from './types'
|
import { DownloadRequest, FileStat, NetworkConfig } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a extension module function in main process
|
* Execute a extension module function in main process
|
||||||
@ -17,18 +17,16 @@ const executeOnMain: (extension: string, method: string, ...args: any[]) => Prom
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads a file from a URL and saves it to the local file system.
|
* Downloads a file from a URL and saves it to the local file system.
|
||||||
* @param {string} url - The URL of the file to download.
|
*
|
||||||
* @param {string} fileName - The name to use for the downloaded file.
|
* @param {DownloadRequest} downloadRequest - The request to download the file.
|
||||||
* @param {object} network - Optional object to specify proxy/whether to ignore SSL certificates.
|
* @param {NetworkConfig} network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||||
|
*
|
||||||
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
||||||
*/
|
*/
|
||||||
const downloadFile: (
|
const downloadFile: (downloadRequest: DownloadRequest, network?: NetworkConfig) => Promise<any> = (
|
||||||
url: string,
|
downloadRequest,
|
||||||
fileName: string,
|
network
|
||||||
network?: { proxy?: string; ignoreSSL?: boolean }
|
) => global.core?.api?.downloadFile(downloadRequest, network)
|
||||||
) => Promise<any> = (url, fileName, network) => {
|
|
||||||
return global.core?.api?.downloadFile(url, fileName, network)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aborts the download of a specific file.
|
* Aborts the download of a specific file.
|
||||||
@ -108,6 +106,20 @@ const log: (message: string, fileName?: string) => void = (message, fileName) =>
|
|||||||
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (from: string, to: string) =>
|
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (from: string, to: string) =>
|
||||||
global.core.api?.isSubdirectory(from, to)
|
global.core.api?.isSubdirectory(from, to)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get system information
|
||||||
|
* @returns {Promise<any>} - A promise that resolves with the system information.
|
||||||
|
*/
|
||||||
|
const systemInformations: () => Promise<any> = () => global.core.api?.systemInformations()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast message from browser processes.
|
||||||
|
* @param title
|
||||||
|
* @param message
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const showToast: (title: string, message: string) => void = (title, message) =>
|
||||||
|
global.core.api?.showToast(title, message)
|
||||||
/**
|
/**
|
||||||
* Register extension point function type definition
|
* Register extension point function type definition
|
||||||
*/
|
*/
|
||||||
@ -134,5 +146,7 @@ export {
|
|||||||
log,
|
log,
|
||||||
isSubdirectory,
|
isSubdirectory,
|
||||||
getUserHomePath,
|
getUserHomePath,
|
||||||
|
systemInformations,
|
||||||
|
showToast,
|
||||||
FileStat,
|
FileStat,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,22 @@ export enum ExtensionTypeEnum {
|
|||||||
export interface ExtensionType {
|
export interface ExtensionType {
|
||||||
type(): ExtensionTypeEnum | undefined
|
type(): ExtensionTypeEnum | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Compatibility {
|
||||||
|
platform: string[]
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALL_INSTALLATION_STATE = [
|
||||||
|
'NotRequired', // not required.
|
||||||
|
'Installed', // require and installed. Good to go.
|
||||||
|
'NotInstalled', // require to be installed.
|
||||||
|
'Corrupted', // require but corrupted. Need to redownload.
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type InstallationStateTuple = typeof ALL_INSTALLATION_STATE
|
||||||
|
export type InstallationState = InstallationStateTuple[number]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a base extension.
|
* Represents a base extension.
|
||||||
* This class should be extended by any class that represents an extension.
|
* This class should be extended by any class that represents an extension.
|
||||||
@ -33,4 +49,32 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
* Any cleanup logic for the extension should be put here.
|
* Any cleanup logic for the extension should be put here.
|
||||||
*/
|
*/
|
||||||
abstract onUnload(): void
|
abstract onUnload(): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The compatibility of the extension.
|
||||||
|
* This is used to check if the extension is compatible with the current environment.
|
||||||
|
* @property {Array} platform
|
||||||
|
*/
|
||||||
|
compatibility(): Compatibility | undefined {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the prerequisites for the extension are installed.
|
||||||
|
*
|
||||||
|
* @returns {boolean} true if the prerequisites are installed, false otherwise.
|
||||||
|
*/
|
||||||
|
async installationState(): Promise<InstallationState> {
|
||||||
|
return 'NotRequired'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the prerequisites for the extension.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
async install(...args): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
core/src/extensions/ai-engines/AIEngine.ts
Normal file
60
core/src/extensions/ai-engines/AIEngine.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { getJanDataFolderPath, joinPath } from '../../core'
|
||||||
|
import { events } from '../../events'
|
||||||
|
import { BaseExtension } from '../../extension'
|
||||||
|
import { fs } from '../../fs'
|
||||||
|
import { Model, ModelEvent } from '../../types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base AIEngine
|
||||||
|
* Applicable to all AI Engines
|
||||||
|
*/
|
||||||
|
export abstract class AIEngine extends BaseExtension {
|
||||||
|
// The inference engine
|
||||||
|
abstract provider: string
|
||||||
|
// The model folder
|
||||||
|
modelFolder: string = 'models'
|
||||||
|
|
||||||
|
abstract models(): Promise<Model[]>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On extension load, subscribe to events.
|
||||||
|
*/
|
||||||
|
onLoad() {
|
||||||
|
this.prePopulateModels()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-populate models to App Data Folder
|
||||||
|
*/
|
||||||
|
prePopulateModels(): Promise<void> {
|
||||||
|
return this.models().then((models) => {
|
||||||
|
const prePoluateOperations = models.map((model) =>
|
||||||
|
getJanDataFolderPath()
|
||||||
|
.then((janDataFolder) =>
|
||||||
|
// Attempt to create the model folder
|
||||||
|
joinPath([janDataFolder, this.modelFolder, model.id]).then((path) =>
|
||||||
|
fs
|
||||||
|
.mkdirSync(path)
|
||||||
|
.catch()
|
||||||
|
.then(() => path)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then((path) => joinPath([path, 'model.json']))
|
||||||
|
.then((path) => {
|
||||||
|
// Do not overwite existing model.json
|
||||||
|
return fs.existsSync(path).then((exist: any) => {
|
||||||
|
if (!exist) return fs.writeFileSync(path, JSON.stringify(model, null, 2))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((e: Error) => {
|
||||||
|
console.error('Error', e)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
Promise.all(prePoluateOperations).then(() =>
|
||||||
|
// Emit event to update models
|
||||||
|
// So the UI can update the models list
|
||||||
|
events.emit(ModelEvent.OnModelsUpdate, {})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
63
core/src/extensions/ai-engines/LocalOAIEngine.ts
Normal file
63
core/src/extensions/ai-engines/LocalOAIEngine.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { executeOnMain, getJanDataFolderPath, joinPath } from '../../core'
|
||||||
|
import { events } from '../../events'
|
||||||
|
import { Model, ModelEvent } from '../../types'
|
||||||
|
import { OAIEngine } from './OAIEngine'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base OAI Local Inference Provider
|
||||||
|
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||||
|
*/
|
||||||
|
export abstract class LocalOAIEngine extends OAIEngine {
|
||||||
|
// The inference engine
|
||||||
|
loadModelFunctionName: string = 'loadModel'
|
||||||
|
unloadModelFunctionName: string = 'unloadModel'
|
||||||
|
isRunning: boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On extension load, subscribe to events.
|
||||||
|
*/
|
||||||
|
onLoad() {
|
||||||
|
super.onLoad()
|
||||||
|
// These events are applicable to local inference providers
|
||||||
|
events.on(ModelEvent.OnModelInit, (model: Model) => this.onModelInit(model))
|
||||||
|
events.on(ModelEvent.OnModelStop, (model: Model) => this.onModelStop(model))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the model.
|
||||||
|
*/
|
||||||
|
async onModelInit(model: Model) {
|
||||||
|
if (model.engine.toString() !== this.provider) return
|
||||||
|
|
||||||
|
const modelFolder = await joinPath([await getJanDataFolderPath(), this.modelFolder, model.id])
|
||||||
|
|
||||||
|
const res = await executeOnMain(this.nodeModule, this.loadModelFunctionName, {
|
||||||
|
modelFolder,
|
||||||
|
model,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res?.error) {
|
||||||
|
events.emit(ModelEvent.OnModelFail, {
|
||||||
|
...model,
|
||||||
|
error: res.error,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
this.loadedModel = model
|
||||||
|
events.emit(ModelEvent.OnModelReady, model)
|
||||||
|
this.isRunning = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Stops the model.
|
||||||
|
*/
|
||||||
|
onModelStop(model: Model) {
|
||||||
|
if (model.engine?.toString() !== this.provider) return
|
||||||
|
|
||||||
|
this.isRunning = false
|
||||||
|
|
||||||
|
executeOnMain(this.nodeModule, this.unloadModelFunctionName).then(() => {
|
||||||
|
events.emit(ModelEvent.OnModelStopped, {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
116
core/src/extensions/ai-engines/OAIEngine.ts
Normal file
116
core/src/extensions/ai-engines/OAIEngine.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import { requestInference } from './helpers/sse'
|
||||||
|
import { ulid } from 'ulid'
|
||||||
|
import { AIEngine } from './AIEngine'
|
||||||
|
import {
|
||||||
|
ChatCompletionRole,
|
||||||
|
ContentType,
|
||||||
|
InferenceEvent,
|
||||||
|
MessageEvent,
|
||||||
|
MessageRequest,
|
||||||
|
MessageRequestType,
|
||||||
|
MessageStatus,
|
||||||
|
Model,
|
||||||
|
ModelInfo,
|
||||||
|
ThreadContent,
|
||||||
|
ThreadMessage,
|
||||||
|
} from '../../types'
|
||||||
|
import { events } from '../../events'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base OAI Inference Provider
|
||||||
|
* Applicable to all OAI compatible inference providers
|
||||||
|
*/
|
||||||
|
export abstract class OAIEngine extends AIEngine {
|
||||||
|
// The inference engine
|
||||||
|
abstract inferenceUrl: string
|
||||||
|
abstract nodeModule: string
|
||||||
|
|
||||||
|
// Controller to handle stop requests
|
||||||
|
controller = new AbortController()
|
||||||
|
isCancelled = false
|
||||||
|
|
||||||
|
// The loaded model instance
|
||||||
|
loadedModel: Model | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On extension load, subscribe to events.
|
||||||
|
*/
|
||||||
|
onLoad() {
|
||||||
|
super.onLoad()
|
||||||
|
events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => this.inference(data))
|
||||||
|
events.on(InferenceEvent.OnInferenceStopped, () => this.onInferenceStopped())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On extension unload
|
||||||
|
*/
|
||||||
|
onUnload(): void {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inference request
|
||||||
|
*/
|
||||||
|
inference(data: MessageRequest) {
|
||||||
|
if (data.model?.engine?.toString() !== this.provider) return
|
||||||
|
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const message: ThreadMessage = {
|
||||||
|
id: ulid(),
|
||||||
|
thread_id: data.threadId,
|
||||||
|
type: data.type,
|
||||||
|
assistant_id: data.assistantId,
|
||||||
|
role: ChatCompletionRole.Assistant,
|
||||||
|
content: [],
|
||||||
|
status: MessageStatus.Pending,
|
||||||
|
created: timestamp,
|
||||||
|
updated: timestamp,
|
||||||
|
object: 'thread.message',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type !== MessageRequestType.Summary) {
|
||||||
|
events.emit(MessageEvent.OnMessageResponse, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isCancelled = false
|
||||||
|
this.controller = new AbortController()
|
||||||
|
|
||||||
|
const model: ModelInfo = {
|
||||||
|
...(this.loadedModel ? this.loadedModel : {}),
|
||||||
|
...data.model,
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInference(this.inferenceUrl, data.messages ?? [], model, this.controller).subscribe({
|
||||||
|
next: (content: any) => {
|
||||||
|
const messageContent: ThreadContent = {
|
||||||
|
type: ContentType.Text,
|
||||||
|
text: {
|
||||||
|
value: content.trim(),
|
||||||
|
annotations: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
message.content = [messageContent]
|
||||||
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
|
},
|
||||||
|
complete: async () => {
|
||||||
|
message.status = message.content.length ? MessageStatus.Ready : MessageStatus.Error
|
||||||
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
|
},
|
||||||
|
error: async (err: any) => {
|
||||||
|
if (this.isCancelled || message.content.length) {
|
||||||
|
message.status = MessageStatus.Stopped
|
||||||
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
message.status = MessageStatus.Error
|
||||||
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the inference.
|
||||||
|
*/
|
||||||
|
onInferenceStopped() {
|
||||||
|
this.isCancelled = true
|
||||||
|
this.controller?.abort()
|
||||||
|
}
|
||||||
|
}
|
||||||
67
core/src/extensions/ai-engines/helpers/sse.ts
Normal file
67
core/src/extensions/ai-engines/helpers/sse.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Observable } from 'rxjs'
|
||||||
|
import { ModelRuntimeParams } from '../../../types'
|
||||||
|
/**
|
||||||
|
* Sends a request to the inference server to generate a response based on the recent messages.
|
||||||
|
* @param recentMessages - An array of recent messages to use as context for the inference.
|
||||||
|
* @returns An Observable that emits the generated response as a string.
|
||||||
|
*/
|
||||||
|
export function requestInference(
|
||||||
|
inferenceUrl: string,
|
||||||
|
recentMessages: any[],
|
||||||
|
model: {
|
||||||
|
id: string
|
||||||
|
parameters: ModelRuntimeParams
|
||||||
|
},
|
||||||
|
controller?: AbortController
|
||||||
|
): Observable<string> {
|
||||||
|
return new Observable((subscriber) => {
|
||||||
|
const requestBody = JSON.stringify({
|
||||||
|
messages: recentMessages,
|
||||||
|
model: model.id,
|
||||||
|
stream: true,
|
||||||
|
...model.parameters,
|
||||||
|
})
|
||||||
|
fetch(inferenceUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Accept': model.parameters.stream ? 'text/event-stream' : 'application/json',
|
||||||
|
},
|
||||||
|
body: requestBody,
|
||||||
|
signal: controller?.signal,
|
||||||
|
})
|
||||||
|
.then(async (response) => {
|
||||||
|
if (model.parameters.stream === false) {
|
||||||
|
const data = await response.json()
|
||||||
|
subscriber.next(data.choices[0]?.message?.content ?? '')
|
||||||
|
} else {
|
||||||
|
const stream = response.body
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
const reader = stream?.getReader()
|
||||||
|
let content = ''
|
||||||
|
|
||||||
|
while (true && reader) {
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const text = decoder.decode(value)
|
||||||
|
const lines = text.trim().split('\n')
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith('data: ') && !line.includes('data: [DONE]')) {
|
||||||
|
const data = JSON.parse(line.replace('data: ', ''))
|
||||||
|
content += data.choices[0]?.delta?.content ?? ''
|
||||||
|
if (content.startsWith('assistant: ')) {
|
||||||
|
content = content.replace('assistant: ', '')
|
||||||
|
}
|
||||||
|
subscriber.next(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.complete()
|
||||||
|
})
|
||||||
|
.catch((err) => subscriber.error(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
3
core/src/extensions/ai-engines/index.ts
Normal file
3
core/src/extensions/ai-engines/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './AIEngine'
|
||||||
|
export * from './OAIEngine'
|
||||||
|
export * from './LocalOAIEngine'
|
||||||
@ -28,3 +28,8 @@ export { ModelExtension } from './model'
|
|||||||
* Hugging Face extension for converting HF models to GGUF.
|
* Hugging Face extension for converting HF models to GGUF.
|
||||||
*/
|
*/
|
||||||
export { HuggingFaceExtension } from './huggingface'
|
export { HuggingFaceExtension } from './huggingface'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base AI Engines.
|
||||||
|
*/
|
||||||
|
export * from './ai-engines'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
import { ImportingModel, Model, ModelInterface, OptionType } from '../index'
|
import { GpuSetting, ImportingModel, Model, ModelInterface, OptionType } from '../index'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model extension for managing models.
|
* Model extension for managing models.
|
||||||
@ -14,6 +14,7 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter
|
|||||||
|
|
||||||
abstract downloadModel(
|
abstract downloadModel(
|
||||||
model: Model,
|
model: Model,
|
||||||
|
gpuSettings?: GpuSetting,
|
||||||
network?: { proxy: string; ignoreSSL?: boolean }
|
network?: { proxy: string; ignoreSSL?: boolean }
|
||||||
): Promise<void>
|
): Promise<void>
|
||||||
abstract cancelModelDownload(modelId: string): Promise<void>
|
abstract cancelModelDownload(modelId: string): Promise<void>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
import { MonitoringInterface } from '../index'
|
import { GpuSetting, MonitoringInterface } from '../index'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring extension for system monitoring.
|
* Monitoring extension for system monitoring.
|
||||||
@ -13,6 +13,7 @@ export abstract class MonitoringExtension extends BaseExtension implements Monit
|
|||||||
return ExtensionTypeEnum.SystemMonitoring
|
return ExtensionTypeEnum.SystemMonitoring
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract getGpuSetting(): Promise<GpuSetting>
|
||||||
abstract getResourcesInfo(): Promise<any>
|
abstract getResourcesInfo(): Promise<any>
|
||||||
abstract getCurrentLoad(): Promise<any>
|
abstract getCurrentLoad(): Promise<any>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getJanDataFolderPath } from '../../helper'
|
|||||||
import { DownloadManager } from '../../helper/download'
|
import { DownloadManager } from '../../helper/download'
|
||||||
import { createWriteStream, renameSync } from 'fs'
|
import { createWriteStream, renameSync } from 'fs'
|
||||||
import { Processor } from './Processor'
|
import { Processor } from './Processor'
|
||||||
import { DownloadState } from '../../../types'
|
import { DownloadRequest, DownloadState, NetworkConfig } from '../../../types'
|
||||||
|
|
||||||
export class Downloader implements Processor {
|
export class Downloader implements Processor {
|
||||||
observer?: Function
|
observer?: Function
|
||||||
@ -20,24 +20,27 @@ export class Downloader implements Processor {
|
|||||||
return func(this.observer, ...args)
|
return func(this.observer, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFile(observer: any, url: string, localPath: string, network: any) {
|
downloadFile(observer: any, downloadRequest: DownloadRequest, network?: NetworkConfig) {
|
||||||
const request = require('request')
|
const request = require('request')
|
||||||
const progress = require('request-progress')
|
const progress = require('request-progress')
|
||||||
|
|
||||||
const strictSSL = !network?.ignoreSSL
|
const strictSSL = !network?.ignoreSSL
|
||||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
||||||
|
|
||||||
|
const { localPath, url } = downloadRequest
|
||||||
|
let normalizedPath = localPath
|
||||||
if (typeof localPath === 'string') {
|
if (typeof localPath === 'string') {
|
||||||
localPath = normalizeFilePath(localPath)
|
normalizedPath = normalizeFilePath(localPath)
|
||||||
}
|
}
|
||||||
const array = localPath.split(sep)
|
const array = normalizedPath.split(sep)
|
||||||
const fileName = array.pop() ?? ''
|
const fileName = array.pop() ?? ''
|
||||||
const modelId = array.pop() ?? ''
|
const modelId = array.pop() ?? ''
|
||||||
|
|
||||||
const destination = resolve(getJanDataFolderPath(), localPath)
|
const destination = resolve(getJanDataFolderPath(), normalizedPath)
|
||||||
const rq = request({ url, strictSSL, proxy })
|
const rq = request({ url, strictSSL, proxy })
|
||||||
|
|
||||||
// Put request to download manager instance
|
// Put request to download manager instance
|
||||||
DownloadManager.instance.setRequest(localPath, rq)
|
DownloadManager.instance.setRequest(normalizedPath, rq)
|
||||||
|
|
||||||
// Downloading file to a temp file first
|
// Downloading file to a temp file first
|
||||||
const downloadingTempFile = `${destination}.download`
|
const downloadingTempFile = `${destination}.download`
|
||||||
@ -56,16 +59,25 @@ export class Downloader implements Processor {
|
|||||||
total: 0,
|
total: 0,
|
||||||
transferred: 0,
|
transferred: 0,
|
||||||
},
|
},
|
||||||
|
children: [],
|
||||||
downloadState: 'downloading',
|
downloadState: 'downloading',
|
||||||
|
extensionId: downloadRequest.extensionId,
|
||||||
|
downloadType: downloadRequest.downloadType,
|
||||||
|
localPath: normalizedPath,
|
||||||
}
|
}
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = initialDownloadState
|
DownloadManager.instance.downloadProgressMap[modelId] = initialDownloadState
|
||||||
|
|
||||||
|
if (downloadRequest.downloadType === 'extension') {
|
||||||
|
observer?.(DownloadEvent.onFileDownloadUpdate, initialDownloadState)
|
||||||
|
}
|
||||||
|
|
||||||
progress(rq, {})
|
progress(rq, {})
|
||||||
.on('progress', (state: any) => {
|
.on('progress', (state: any) => {
|
||||||
|
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||||
const downloadState: DownloadState = {
|
const downloadState: DownloadState = {
|
||||||
|
...currentDownloadState,
|
||||||
...state,
|
...state,
|
||||||
modelId,
|
fileName: fileName,
|
||||||
fileName,
|
|
||||||
downloadState: 'downloading',
|
downloadState: 'downloading',
|
||||||
}
|
}
|
||||||
console.debug('progress: ', downloadState)
|
console.debug('progress: ', downloadState)
|
||||||
@ -76,22 +88,22 @@ export class Downloader implements Processor {
|
|||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||||
const downloadState: DownloadState = {
|
const downloadState: DownloadState = {
|
||||||
...currentDownloadState,
|
...currentDownloadState,
|
||||||
|
fileName: fileName,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
downloadState: 'error',
|
downloadState: 'error',
|
||||||
}
|
}
|
||||||
if (currentDownloadState) {
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
|
||||||
}
|
|
||||||
|
|
||||||
observer?.(DownloadEvent.onFileDownloadError, downloadState)
|
observer?.(DownloadEvent.onFileDownloadError, downloadState)
|
||||||
|
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||||
})
|
})
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||||
if (currentDownloadState && DownloadManager.instance.networkRequests[localPath]) {
|
if (currentDownloadState && DownloadManager.instance.networkRequests[normalizedPath]) {
|
||||||
// Finished downloading, rename temp file to actual file
|
// Finished downloading, rename temp file to actual file
|
||||||
renameSync(downloadingTempFile, destination)
|
renameSync(downloadingTempFile, destination)
|
||||||
const downloadState: DownloadState = {
|
const downloadState: DownloadState = {
|
||||||
...currentDownloadState,
|
...currentDownloadState,
|
||||||
|
fileName: fileName,
|
||||||
downloadState: 'end',
|
downloadState: 'end',
|
||||||
}
|
}
|
||||||
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
|
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
|
||||||
|
|||||||
@ -1,7 +1,16 @@
|
|||||||
import fs from 'fs'
|
import {
|
||||||
|
existsSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync,
|
||||||
|
mkdirSync,
|
||||||
|
appendFileSync,
|
||||||
|
createWriteStream,
|
||||||
|
rmdirSync,
|
||||||
|
} from 'fs'
|
||||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../index'
|
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../types'
|
||||||
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
|
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
|
||||||
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
||||||
|
|
||||||
@ -9,12 +18,12 @@ import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
|||||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(directoryPath)) {
|
if (!existsSync(directoryPath)) {
|
||||||
console.debug('model folder not found')
|
console.debug('model folder not found')
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const files: string[] = fs.readdirSync(directoryPath)
|
const files: string[] = readdirSync(directoryPath)
|
||||||
|
|
||||||
const allDirectories: string[] = []
|
const allDirectories: string[] = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
@ -46,8 +55,8 @@ export const getBuilder = async (configuration: RouteConfiguration) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const readModelMetadata = (path: string): string | undefined => {
|
const readModelMetadata = (path: string): string | undefined => {
|
||||||
if (fs.existsSync(path)) {
|
if (existsSync(path)) {
|
||||||
return fs.readFileSync(path, 'utf-8')
|
return readFileSync(path, 'utf-8')
|
||||||
} else {
|
} else {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@ -81,7 +90,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
const objectPath = join(directoryPath, id)
|
const objectPath = join(directoryPath, id)
|
||||||
fs.rmdirSync(objectPath, { recursive: true })
|
rmdirSync(objectPath, { recursive: true })
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
object: configuration.delete.object,
|
object: configuration.delete.object,
|
||||||
@ -96,20 +105,19 @@ export const getMessages = async (threadId: string): Promise<ThreadMessage[]> =>
|
|||||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||||
const messageFile = 'messages.jsonl'
|
const messageFile = 'messages.jsonl'
|
||||||
try {
|
try {
|
||||||
const files: string[] = fs.readdirSync(threadDirPath)
|
const files: string[] = readdirSync(threadDirPath)
|
||||||
if (!files.includes(messageFile)) {
|
if (!files.includes(messageFile)) {
|
||||||
console.error(`${threadDirPath} not contains message file`)
|
console.error(`${threadDirPath} not contains message file`)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageFilePath = join(threadDirPath, messageFile)
|
const messageFilePath = join(threadDirPath, messageFile)
|
||||||
if (!fs.existsSync(messageFilePath)) {
|
if (!existsSync(messageFilePath)) {
|
||||||
console.debug('message file not found')
|
console.debug('message file not found')
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = fs
|
const lines = readFileSync(messageFilePath, 'utf-8')
|
||||||
.readFileSync(messageFilePath, 'utf-8')
|
|
||||||
.toString()
|
.toString()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((line: any) => line !== '')
|
.filter((line: any) => line !== '')
|
||||||
@ -157,11 +165,11 @@ export const createThread = async (thread: any) => {
|
|||||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||||
|
|
||||||
if (!fs.existsSync(threadDirPath)) {
|
if (!existsSync(threadDirPath)) {
|
||||||
fs.mkdirSync(threadDirPath)
|
mkdirSync(threadDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
await writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||||
return updatedThread
|
return updatedThread
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
@ -191,7 +199,7 @@ export const updateThread = async (threadId: string, thread: any) => {
|
|||||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||||
|
|
||||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
await writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||||
return updatedThread
|
return updatedThread
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
@ -233,10 +241,10 @@ export const createMessage = async (threadId: string, message: any) => {
|
|||||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||||
const threadMessagePath = join(threadDirPath, threadMessagesFileName)
|
const threadMessagePath = join(threadDirPath, threadMessagesFileName)
|
||||||
|
|
||||||
if (!fs.existsSync(threadDirPath)) {
|
if (!existsSync(threadDirPath)) {
|
||||||
fs.mkdirSync(threadDirPath)
|
mkdirSync(threadDirPath)
|
||||||
}
|
}
|
||||||
fs.appendFileSync(threadMessagePath, JSON.stringify(threadMessage) + '\n')
|
appendFileSync(threadMessagePath, JSON.stringify(threadMessage) + '\n')
|
||||||
return threadMessage
|
return threadMessage
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
@ -259,8 +267,8 @@ export const downloadModel = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const directoryPath = join(getJanDataFolderPath(), 'models', modelId)
|
const directoryPath = join(getJanDataFolderPath(), 'models', modelId)
|
||||||
if (!fs.existsSync(directoryPath)) {
|
if (!existsSync(directoryPath)) {
|
||||||
fs.mkdirSync(directoryPath)
|
mkdirSync(directoryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// path to model binary
|
// path to model binary
|
||||||
@ -281,7 +289,7 @@ export const downloadModel = async (
|
|||||||
.on('end', function () {
|
.on('end', function () {
|
||||||
console.debug('end')
|
console.debug('end')
|
||||||
})
|
})
|
||||||
.pipe(fs.createWriteStream(modelBinaryPath))
|
.pipe(createWriteStream(modelBinaryPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
|||||||
const modelFolderFullPath = join(janDataFolderPath, 'models', modelId)
|
const modelFolderFullPath = join(janDataFolderPath, 'models', modelId)
|
||||||
|
|
||||||
if (!fs.existsSync(modelFolderFullPath)) {
|
if (!fs.existsSync(modelFolderFullPath)) {
|
||||||
throw `Model not found: ${modelId}`
|
throw new Error(`Model not found: ${modelId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const files: string[] = fs.readdirSync(modelFolderFullPath)
|
const files: string[] = fs.readdirSync(modelFolderFullPath)
|
||||||
@ -53,7 +53,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
|||||||
const modelMetadata: Model = JSON.parse(fs.readFileSync(modelMetadataPath, 'utf-8'))
|
const modelMetadata: Model = JSON.parse(fs.readFileSync(modelMetadataPath, 'utf-8'))
|
||||||
|
|
||||||
if (!ggufBinFile) {
|
if (!ggufBinFile) {
|
||||||
throw 'No GGUF model file found'
|
throw new Error('No GGUF model file found')
|
||||||
}
|
}
|
||||||
const modelBinaryPath = join(modelFolderFullPath, ggufBinFile)
|
const modelBinaryPath = join(modelFolderFullPath, ggufBinFile)
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
|||||||
const promptTemplate = modelMetadata.settings.prompt_template
|
const promptTemplate = modelMetadata.settings.prompt_template
|
||||||
const prompt = promptTemplateConverter(promptTemplate)
|
const prompt = promptTemplateConverter(promptTemplate)
|
||||||
if (prompt?.error) {
|
if (prompt?.error) {
|
||||||
return Promise.reject(prompt.error)
|
throw new Error(prompt.error)
|
||||||
}
|
}
|
||||||
nitroModelSettings.system_prompt = prompt.system_prompt
|
nitroModelSettings.system_prompt = prompt.system_prompt
|
||||||
nitroModelSettings.user_prompt = prompt.user_prompt
|
nitroModelSettings.user_prompt = prompt.user_prompt
|
||||||
|
|||||||
@ -4,13 +4,13 @@ import fs from 'fs'
|
|||||||
import os from 'os'
|
import os from 'os'
|
||||||
import childProcess from 'child_process'
|
import childProcess from 'child_process'
|
||||||
|
|
||||||
// TODO: move this to core
|
|
||||||
const configurationFileName = 'settings.json'
|
const configurationFileName = 'settings.json'
|
||||||
|
|
||||||
// TODO: do no specify app name in framework module
|
// TODO: do no specify app name in framework module
|
||||||
const defaultJanDataFolder = join(os.homedir(), 'jan')
|
const defaultJanDataFolder = join(os.homedir(), 'jan')
|
||||||
const defaultAppConfig: AppConfiguration = {
|
const defaultAppConfig: AppConfiguration = {
|
||||||
data_folder: defaultJanDataFolder,
|
data_folder: defaultJanDataFolder,
|
||||||
|
quick_ask: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export type AppConfiguration = {
|
export type AppConfiguration = {
|
||||||
data_folder: string
|
data_folder: string
|
||||||
|
quick_ask: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,16 +4,43 @@ export type FileStat = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadState = {
|
export type DownloadState = {
|
||||||
modelId: string
|
modelId: string // TODO: change to download id
|
||||||
fileName: string
|
fileName: string
|
||||||
time: DownloadTime
|
time: DownloadTime
|
||||||
speed: number
|
speed: number
|
||||||
percent: number
|
|
||||||
|
|
||||||
|
percent: number
|
||||||
size: DownloadSize
|
size: DownloadSize
|
||||||
children?: DownloadState[]
|
|
||||||
error?: string
|
|
||||||
downloadState: 'downloading' | 'error' | 'end'
|
downloadState: 'downloading' | 'error' | 'end'
|
||||||
|
children?: DownloadState[]
|
||||||
|
|
||||||
|
error?: string
|
||||||
|
extensionId?: string
|
||||||
|
downloadType?: DownloadType
|
||||||
|
localPath?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DownloadType = 'model' | 'extension'
|
||||||
|
|
||||||
|
export type DownloadRequest = {
|
||||||
|
/**
|
||||||
|
* The URL to download the file from.
|
||||||
|
*/
|
||||||
|
url: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The local path to save the file to.
|
||||||
|
*/
|
||||||
|
localPath: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The extension ID of the extension that initiated the download.
|
||||||
|
*
|
||||||
|
* Can be extension name.
|
||||||
|
*/
|
||||||
|
extensionId?: string
|
||||||
|
|
||||||
|
downloadType?: DownloadType
|
||||||
}
|
}
|
||||||
|
|
||||||
type DownloadTime = {
|
type DownloadTime = {
|
||||||
|
|||||||
7
core/src/types/miscellaneous/appUpdate.ts
Normal file
7
core/src/types/miscellaneous/appUpdate.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export type AppUpdateInfo = {
|
||||||
|
total: number
|
||||||
|
delta: number
|
||||||
|
transferred: number
|
||||||
|
percent: number
|
||||||
|
bytesPerSecond: number
|
||||||
|
}
|
||||||
8
core/src/types/miscellaneous/fileDownloadRequest.ts
Normal file
8
core/src/types/miscellaneous/fileDownloadRequest.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export type FileDownloadRequest = {
|
||||||
|
downloadId: string
|
||||||
|
url: string
|
||||||
|
localPath: string
|
||||||
|
fileName: string
|
||||||
|
displayName: string
|
||||||
|
metadata: Record<string, string | number>
|
||||||
|
}
|
||||||
@ -1,2 +1,5 @@
|
|||||||
export * from './systemResourceInfo'
|
export * from './systemResourceInfo'
|
||||||
export * from './promptTemplate'
|
export * from './promptTemplate'
|
||||||
|
export * from './appUpdate'
|
||||||
|
export * from './fileDownloadRequest'
|
||||||
|
export * from './networkConfig'
|
||||||
4
core/src/types/miscellaneous/networkConfig.ts
Normal file
4
core/src/types/miscellaneous/networkConfig.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type NetworkConfig = {
|
||||||
|
proxy?: string
|
||||||
|
ignoreSSL?: boolean
|
||||||
|
}
|
||||||
@ -2,3 +2,31 @@ export type SystemResourceInfo = {
|
|||||||
numCpuPhysicalCore: number
|
numCpuPhysicalCore: number
|
||||||
memAvailable: number
|
memAvailable: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RunMode = 'cpu' | 'gpu'
|
||||||
|
|
||||||
|
export type GpuSetting = {
|
||||||
|
notify: boolean
|
||||||
|
run_mode: RunMode
|
||||||
|
nvidia_driver: {
|
||||||
|
exist: boolean
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
cuda: {
|
||||||
|
exist: boolean
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
gpus: GpuSettingInfo[]
|
||||||
|
gpu_highest_vram: string
|
||||||
|
gpus_in_use: string[]
|
||||||
|
is_initial: boolean
|
||||||
|
// TODO: This needs to be set based on user toggle in settings
|
||||||
|
vulkan: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GpuSettingInfo = {
|
||||||
|
id: string
|
||||||
|
vram: string
|
||||||
|
name: string
|
||||||
|
arch?: string
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export enum InferenceEngine {
|
|||||||
nitro = 'nitro',
|
nitro = 'nitro',
|
||||||
openai = 'openai',
|
openai = 'openai',
|
||||||
triton_trtllm = 'triton_trtllm',
|
triton_trtllm = 'triton_trtllm',
|
||||||
|
nitro_tensorrt_llm = 'nitro-tensorrt-llm',
|
||||||
|
|
||||||
tool_retrieval_enabled = 'tool_retrieval_enabled',
|
tool_retrieval_enabled = 'tool_retrieval_enabled',
|
||||||
}
|
}
|
||||||
@ -138,3 +139,7 @@ export type ModelRuntimeParams = {
|
|||||||
presence_penalty?: number
|
presence_penalty?: number
|
||||||
engine?: string
|
engine?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelInitFailed = Model & {
|
||||||
|
error: Error
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { GpuSetting } from '../miscellaneous'
|
||||||
import { Model } from './modelEntity'
|
import { Model } from './modelEntity'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -10,7 +11,11 @@ export interface ModelInterface {
|
|||||||
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||||
* @returns A Promise that resolves when the model has been downloaded.
|
* @returns A Promise that resolves when the model has been downloaded.
|
||||||
*/
|
*/
|
||||||
downloadModel(model: Model, network?: { ignoreSSL?: boolean; proxy?: string }): Promise<void>
|
downloadModel(
|
||||||
|
model: Model,
|
||||||
|
gpuSettings?: GpuSetting,
|
||||||
|
network?: { ignoreSSL?: boolean; proxy?: string }
|
||||||
|
): Promise<void>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the download of a specific model.
|
* Cancels the download of a specific model.
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './monitoringInterface'
|
export * from './monitoringInterface'
|
||||||
|
export * from './resourceInfo'
|
||||||
|
|||||||
6
core/src/types/monitoring/resourceInfo.ts
Normal file
6
core/src/types/monitoring/resourceInfo.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export type ResourceInfo = {
|
||||||
|
mem: {
|
||||||
|
totalMemory: number
|
||||||
|
usedMemory: number
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@
|
|||||||
"declarationDir": "dist/types",
|
"declarationDir": "dist/types",
|
||||||
"outDir": "dist/lib",
|
"outDir": "dist/lib",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"types": ["@types/jest"]
|
"types": ["@types/jest"],
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"],
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
# Website
|
# Website & Docs
|
||||||
|
|
||||||
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
|
This website is built using [Docusaurus 3.0](https://docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
## Information Architecture
|
### Information Architecture
|
||||||
|
|
||||||
We try to **keep routes consistent** to maintain SEO.
|
We try to **keep routes consistent** to maintain SEO.
|
||||||
|
|
||||||
- `/guides`: Guides on how to use the Jan application, with GIFs. For end users who are directly using Jan. Always assume users are not technical.
|
- **`/guides/`**: Guides on how to use the Jan application. For end users who are directly using Jan.
|
||||||
|
|
||||||
- `/developer`: Developer docs on how to extend Jan. These pages are about what people can build with our software. We must hide the complexity of HOW the app is built, but explain just enough of the high level architecture so devs know enough to build on top of it.
|
- **`/developer/`**: Developer docs on how to extend Jan. These pages are about what people can build with our software.
|
||||||
|
|
||||||
- `/api-reference`: Reference documentation, written in Swagger/OpenAPI format.
|
- **`/api-reference/`**: Reference documentation for the Jan API server, written in Swagger/OpenAPI format.
|
||||||
|
|
||||||
- `/docs`: Engineering specs and product specs, i.e. HOW the app is built. Mostly for internal reference and for our core contributors who are building the SDK itself.
|
- **`/changelog/`**: A list of changes made to the Jan application with each release.
|
||||||
|
|
||||||
|
- **`/blog/`**: A blog for the Jan application.
|
||||||
|
|
||||||
### Sidebar Autogeneration
|
### Sidebar Autogeneration
|
||||||
|
|
||||||
@ -20,34 +22,36 @@ The order of each page is either explicitly defined in `sidebar.js` or follows t
|
|||||||
|
|
||||||
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
|
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
|
||||||
|
|
||||||
```md
|
```
|
||||||
---
|
---
|
||||||
title: Overview
|
title: Overview
|
||||||
slug: /docs
|
slug: /docs
|
||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## How to Contribute
|
||||||
|
|
||||||
### Installation
|
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
||||||
|
|
||||||
```
|
### Pre-requisites and Installation
|
||||||
$ yarn
|
|
||||||
```
|
|
||||||
|
|
||||||
### Local Development
|
- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher)
|
||||||
|
- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher)
|
||||||
|
|
||||||
```
|
#### Installation
|
||||||
$ cp .env.example .env
|
|
||||||
$ yarn start
|
```bash
|
||||||
|
cd jan/docs
|
||||||
|
yarn install
|
||||||
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||||
|
|
||||||
### Build
|
#### Build
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
@ -56,25 +60,27 @@ This command generates static content into the `build` directory and can be serv
|
|||||||
|
|
||||||
Using SSH:
|
Using SSH:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ USE_SSH=true yarn deploy
|
USE_SSH=true yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
Not using SSH:
|
Not using SSH:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
GIT_USER=<Your GitHub username> yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
|
|
||||||
### Preview URL, Pre-release and Publishing Documentation
|
### Preview URL, Pre-release and Publishing Documentation
|
||||||
|
|
||||||
When a PR is created, the preview URL will be automatically commented on the PR.
|
- When a pull request is created, the preview URL will be automatically commented on the pull request.
|
||||||
|
|
||||||
The documentation will then be published to [https://jan.ai/](https://jan.ai/) when the PR is merged to `main`.
|
- The documentation will then be published to [https://dev.jan.ai/](https://dev.jan.ai/) when the pull request is merged to `dev`.
|
||||||
|
|
||||||
|
- Our open-source maintainers will sync the updated content from `dev` to `docs` branch, which will then be published to [https://jan.ai/](https://jan.ai/).
|
||||||
|
|
||||||
### Additional Plugins
|
### Additional Plugins
|
||||||
|
|
||||||
- @docusaurus/theme-live-codeblock
|
- @docusaurus/theme-live-codeblock
|
||||||
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/OpenAPISpec.json`
|
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/jan.yaml` to update the API reference documentation.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: About Jan
|
title: About Jan
|
||||||
slug: /about
|
slug: /about
|
||||||
description: Jan is a productivity tool to customize AI to your needs and workflows.
|
description: Jan is a desktop application that turns computers into thinking machines.
|
||||||
keywords:
|
keywords:
|
||||||
[
|
[
|
||||||
Jan AI,
|
Jan AI,
|
||||||
@ -12,45 +12,74 @@ keywords:
|
|||||||
conversational AI,
|
conversational AI,
|
||||||
no-subscription fee,
|
no-subscription fee,
|
||||||
large language model,
|
large language model,
|
||||||
|
about Jan,
|
||||||
|
desktop application,
|
||||||
|
thinking machine,
|
||||||
]
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
Jan is a [open-source](https://en.wikipedia.org/wiki/Open_source), [local-first](https://www.inkandswitch.com/local-first/) tool to [create, customize and use AI](https://www.gatesnotes.com/AI-agents) for everyday tasks.
|
Jan turns computers into thinking machines to change how we use them.
|
||||||
|
Jan is created and maintained by Jan Labs, a robotics company.
|
||||||
|
|
||||||
You can:
|
With Jan, you can:
|
||||||
|
|
||||||
- Run locally using [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/)
|
- Run [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) locally or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/).
|
||||||
- Fine-tune AI with specific knowledge
|
- Fine-tune AI with specific knowledge.
|
||||||
- Search the web and other databases
|
- Supercharge your productivity by leveraging AI.
|
||||||
- Connect AI to your everyday tools and (with your permission) do work on your behalf
|
- Search the web and databases.
|
||||||
|
- Integrate AI with everyday tools to work on your behalf (with permission).
|
||||||
|
- Customize and add features with Extensions.
|
||||||
|
|
||||||
Longer-term, Jan is building a cognitive framework for future robots. We envision a world where we have personal or company robots that we continually improve and customize, growing together with us.
|
:::tip
|
||||||
|
|
||||||
|
Jan aims for long-term human-robot collaboration, envisioning AI as a harmonious extension of human capabilities. Our goal is to build customizable robots that we continually improve and customize, growing together.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Why do we exist
|
## Jan’s principles
|
||||||
|
|
||||||
At Jan, our mission is to advance human-machine collaboration. We achieve this through delivering the best open-source, local-first tools to allow users to run, customize and tinker with AI.
|
- **Ownership**: Jan is committed to developing a product that fully belongs to users. You're the true owner, free from data tracking and storage by us.
|
||||||
|
- **Privacy**: Jan works locally by default, allowing use without an internet connection. Your data stays on your device in a universal format, giving you complete privacy control.
|
||||||
|
- **100% User Supported**: Every user can access, develop, and customize Jan's codebases to suit their needs.
|
||||||
|
- **Rejecting Dark Patterns**: We never use tricks to extract more money or lock you into an ecosystem.
|
||||||
|
|
||||||
## What's different about it?
|
## Why do we exist?
|
||||||
|
|
||||||
|
> _"I do not fear computers. I fear the lack of them." - Isaac Asimov_
|
||||||
|
|
||||||
|
Jan was founded on the belief that AI should coexist with humans, not replace them. Our mission is to democratize AI access, ensuring everyone can easily utilize it with full ownership and control over their data, free from privacy concerns.
|
||||||
|
|
||||||
|
### What are the things Jan committed on?
|
||||||
|
|
||||||
|
We are committed to creating open, local-first products that extend individual freedom, rejecting dark patterns and ecosystem lock-ins, and embracing an open-source ethos.
|
||||||
|
|
||||||
|
#### What's different about it?
|
||||||
|
|
||||||
| | Status Quo | Jan |
|
| | Status Quo | Jan |
|
||||||
| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | -------------------------- | ---------------------------------------------------------------------- |
|
||||||
| Ownership | AI Monopolies owned by Big Tech | AI that you own and control |
|
| **Ownership** | Owned by Big Tech | Fully owned by you |
|
||||||
| Openness? | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
||||||
| Your role | Consume | Create, Tinker and Customize |
|
| **Your Role** | Consumer | Creator |
|
||||||
| Approach | Cloud | [Local-first](https://www.inkandswitch.com/local-first/), running 100% on your devices |
|
| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) |
|
||||||
| Data | Data stored on their servers | Data stored in your local filesystem in open, non-proprietary file formats |
|
| **Data Handling** | Stored on external servers | Stored locally, openly accessible |
|
||||||
| Privacy | 😂 | Runs 100% on your own machine, predictably, privately and offline |
|
| **Privacy** | Questionable | Private and offline |
|
||||||
| Transparency | "Black Box" | Runs predictability with code available to tinker and customize |
|
| **Transparency** | Opaque "Black Box" | Open-source and customizable |
|
||||||
| What happens if there's an outage or goes out of business? | Your life's work held hostage in the cloud in proprietary data formats[^1] | Continues to run 100% on your computer, your data is safe in your local folder |
|
| **Outage Resilience** | Potential data hostage | Continues to work on your device |
|
||||||
| Driving Philosophy | Monetize your users | [Privacy as a human right](https://en.wikipedia.org/wiki/Right_to_privacy) and the [Right to Repair](https://www.repair.org/) |
|
| **Philosophy** | User monetization | Empowerment with the right to repair |
|
||||||
|
|
||||||
## How do I get it?
|
## How we work
|
||||||
|
|
||||||
You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
Jan is an open-source product with transparent development and future features. Users have the right to modify and customize Jan. We are committed to building an open-source AI ecosystem.
|
||||||
|
|
||||||
You can read the [User Guide](/docs/user-guide) if you need some help to get started.
|
Jan is building in public using GitHub, where anyone is welcome to join. Key resources include Jan's [Kanban](https://github.com/orgs/janhq/projects/5/views/7) and Jan's [Roadmap](https://github.com/orgs/janhq/projects/5/views/29).
|
||||||
|
|
||||||
|
Jan has a fully-remote team, primarily based in the APAC timezone, and we use Discord and GitHub for collaboration. Our community is central to our operations, and we embrace asynchronous work. We hold meetings only for synchronization and vision sharing, using [Excalidraw](https://excalidraw.com/) or [Miro](https://miro.com/) for visualization and sharing notes on Discord for alignment. We also use [HackMD](https://hackmd.io/) to document our ideas and build a Jan library.
|
||||||
|
|
||||||
|
## How to get it?
|
||||||
|
|
||||||
|
You can install and start using Jan in less than 5 minutes, from [Jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
||||||
|
|
||||||
## What license is the code under?
|
## What license is the code under?
|
||||||
|
|
||||||
@ -58,8 +87,6 @@ Jan is licensed under the [AGPLv3 License](https://github.com/janhq/jan/blob/mai
|
|||||||
|
|
||||||
We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2].
|
We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2].
|
||||||
|
|
||||||
We also have a [Contributor Program](/docs/team/contributor-program) to provide ownership and upside to contributors who have made significant contributions to the project.
|
|
||||||
|
|
||||||
## What was it built with?
|
## What was it built with?
|
||||||
|
|
||||||
[Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]).
|
[Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]).
|
||||||
@ -73,11 +100,9 @@ We follow [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/
|
|||||||
|
|
||||||
Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale.
|
Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale.
|
||||||
|
|
||||||
:::tip
|
## Join the team
|
||||||
|
|
||||||
**At its core, Jan is a software development kit to build and run copilots on personal devices**. The Desktop Client many folks use is, rather, a specific set of extensions packaged by default. We're excited to see what developers do with the SDK (once its in better shape).
|
Join us on this journey at Jan Labs, where we embrace open-source collaboration and transparency. Together, let's shape a future where Jan becomes an essential companion in the open-source community. Explore [careers](https://janai.bamboohr.com/careers) with us.
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
|||||||
65
docs/docs/about/faq.md
Normal file
65
docs/docs/about/faq.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Frequently Asked Questions (FAQ)
|
||||||
|
|
||||||
|
## What is Jan?
|
||||||
|
|
||||||
|
Jan is software that helps you run large language models (LLMs) on your everyday tasks. For details, read the [About page](https://jan.ai/about/).
|
||||||
|
|
||||||
|
## How do I use Jan?
|
||||||
|
|
||||||
|
Download Jan to your computer, choose a compatible LLM, or connect to a remote AI with the API code to start. You can switch between them as needed.
|
||||||
|
|
||||||
|
## Is Jan compatible with my operating system?
|
||||||
|
|
||||||
|
Jan is available for Mac, Windows, and Linux, ensuring wide compatibility.
|
||||||
|
|
||||||
|
## Do you use my data?
|
||||||
|
|
||||||
|
No. See our data and analytics policy [here](https://jan.ai/privacy/#:~:text=We%20do%20not%20share%20your,with%20a%20better%20user%20experience.).
|
||||||
|
|
||||||
|
## Do you sell my data?
|
||||||
|
|
||||||
|
No. We don't even track your data. Jan is yours.
|
||||||
|
|
||||||
|
## How does Jan ensure my data remains private?
|
||||||
|
|
||||||
|
Jan prioritizes your privacy by running open-source AI models 100% offline on your computer, ensuring all conversations, documents, and files stay private.
|
||||||
|
|
||||||
|
## What does "Jan" stand for?
|
||||||
|
|
||||||
|
Jan stands for “Just Another Neuron”, as we are passionate about building software that complements in your existing neural pathways. But in the spirit of full transparency, it was also just a nice 3 letter domain name we owned 😂.
|
||||||
|
|
||||||
|
## Can I use Jan without an internet connection?
|
||||||
|
|
||||||
|
Yes, Jan can run locally without an internet connection for many features.
|
||||||
|
|
||||||
|
## Are there any costs associated with using Jan?
|
||||||
|
|
||||||
|
Jan is free to use. However, if you want to connect to remote APIs, like GPT-4, you will need to put in your own API key.
|
||||||
|
|
||||||
|
## What types of AI models can I download or import with Jan?
|
||||||
|
|
||||||
|
You can download popular AI models or import any model of your choice through Jan's Hub.
|
||||||
|
|
||||||
|
## How do I customize Jan using the programmable API?
|
||||||
|
|
||||||
|
The API allows you to tailor Jan to your needs, but specific details on usage would require consulting Jan's documentation.
|
||||||
|
|
||||||
|
## How can I contribute to Jan's development or suggest features?
|
||||||
|
|
||||||
|
Contributions can be made through [GitHub](https://github.com/janhq/jan) and [Discord](https://discord.gg/Exe46xPMbK), where you can also suggest features and contribute.
|
||||||
|
|
||||||
|
## How can I get involved with the Jan community?
|
||||||
|
|
||||||
|
Joining [Jan's Discord server](https://discord.gg/qSwXFx6Krr) is a great way to get involved with the community.
|
||||||
|
|
||||||
|
## How do I troubleshoot issues with installing or using Jan?
|
||||||
|
|
||||||
|
For troubleshooting, you should reach out on Discord and check GitHub for assistance and support from the community and the development team.
|
||||||
|
|
||||||
|
## Can I self-host?
|
||||||
|
|
||||||
|
Yes! We love the self-hosted movement. Jan is available as a Helm chart/ Docker composes which can be run across home servers or even production-level environments.
|
||||||
|
|
||||||
|
## Are you hiring?
|
||||||
|
|
||||||
|
We often hire directly from our community. If you are interested in applying, please see our careers page [here](https://janai.bamboohr.com/careers).
|
||||||
BIN
docs/docs/developer/03-build-engine/asset/plugin.png
Normal file
BIN
docs/docs/developer/03-build-engine/asset/plugin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
@ -23,7 +23,8 @@ HTTPS Proxy encrypts data between your browser and the internet, making it hard
|
|||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
When configuring Jan using an HTTPS proxy, the speed of the downloading model may be affected due to the encryption and decryption process. It also depends on the networking of the cloud service provider.
|
- When configuring Jan using an HTTPS proxy, the speed of the downloading model may be affected due to the encryption and decryption process. It also depends on the networking of the cloud service provider.
|
||||||
|
- HTTPS Proxy does not affect the remote model usage.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
@ -68,8 +68,8 @@ This guide provides you steps to troubleshoot and to resolve the issue where you
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# You can delete the `/Jan` directory in Windows's AppData Directory by visiting the following path `%APPDATA%\Jan`
|
# You can delete the `/Jan` directory in Windows's AppData Directory by visiting the following path `%APPDATA%\Jan`
|
||||||
cd C:\Users\%USERNAME%\AppData\Roaming
|
cd C:\Users\YOUR_USERNAME\AppData\Roaming
|
||||||
rmdir /S jan
|
rm -r ./Jan
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Additional Step for Versions Before 0.4.2
|
### 3. Additional Step for Versions Before 0.4.2
|
||||||
|
|||||||
31
docs/docs/guides/error-codes/no-assistant-available.mdx
Normal file
31
docs/docs/guides/error-codes/no-assistant-available.mdx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
title: No Assistant Available
|
||||||
|
sidebar_position: 7
|
||||||
|
description: Troubleshooting steps to resolve issues no assistant available.
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
troubleshooting,
|
||||||
|
no assistant available,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
When you encounter the following error message:
|
||||||
|
```
|
||||||
|
No assistant available.
|
||||||
|
```
|
||||||
|
|
||||||
|
This issue arises when a new, unintentional file appears in `/jan/assistants`.
|
||||||
|
|
||||||
|
It can be resolved through the following steps:
|
||||||
|
|
||||||
|
1. Access the `/jan/assistants` directory using a file manager or terminal.
|
||||||
|
|
||||||
|
2. Within `/jan/assistants`, this directory should only contain a folder named `jan`. Identify any file outside of this folder and remove it.
|
||||||
62
docs/docs/guides/error-codes/stuck-on-loading-model.mdx
Normal file
62
docs/docs/guides/error-codes/stuck-on-loading-model.mdx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
title: Stuck on Loading Model
|
||||||
|
sidebar_position: 8
|
||||||
|
description: Troubleshooting steps to resolve issues related to the loading model.
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
troubleshooting,
|
||||||
|
stuck on loading model,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Issue: Model Loading Stuck Due To Missing Windows Management Instrumentation Command-line (WMIC)
|
||||||
|
|
||||||
|
Encountering a stuck-on-loading model issue in Jan is caused by errors related to the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable.
|
||||||
|
|
||||||
|
Error message:
|
||||||
|
```
|
||||||
|
index.js:47 Uncaught (in promise) Error: Error invoking remote method 'invokeExtensionFunc': Error: Command failed: WMIC CPU Get NumberOfCores
|
||||||
|
```
|
||||||
|
|
||||||
|
It can be resolved through the following steps:
|
||||||
|
|
||||||
|
1. **Open System Properties:**
|
||||||
|
- Press `Windows key + R`.
|
||||||
|
- Type `sysdm.cpl` and press `Enter`.
|
||||||
|
|
||||||
|
2. **Access Environment Variables:**
|
||||||
|
- Go to the "Advanced" tab.
|
||||||
|
- Click the "Environment Variables" button.
|
||||||
|
|
||||||
|
3. **Edit System PATH:**
|
||||||
|
- Under "System Variables" find and select `Path`.
|
||||||
|
- Click "Edit."
|
||||||
|
|
||||||
|
4. **Add WMIC Path:**
|
||||||
|
- Click "New" and enter `C:\Windows\System32\Wbem`.
|
||||||
|
|
||||||
|
5. **Save Changes:**
|
||||||
|
- Click "OK" to close and save your changes.
|
||||||
|
|
||||||
|
6. **Verify Installation:**
|
||||||
|
- Restart any command prompts or terminals.
|
||||||
|
- Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`.
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Issue: Model Loading Stuck Due To CPU Without AVX
|
||||||
|
|
||||||
|
Encountering an issue with models stuck on loading in Jan can be due to the use of older generation CPUs that do not support Advanced Vector Extensions (AVX).
|
||||||
|
|
||||||
|
To check if your CPU supports AVX, visit the following link: [CPUs with AVX](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX)
|
||||||
|
|
||||||
|
:::warning [Please use this with caution]
|
||||||
|
As a workaround, consider using an [emulator](https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html) to simulate AVX support.
|
||||||
|
:::
|
||||||
26
docs/docs/guides/error-codes/thread-disappreance.mdx
Normal file
26
docs/docs/guides/error-codes/thread-disappreance.mdx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: Thread Disappearance
|
||||||
|
sidebar_position: 6
|
||||||
|
description: Troubleshooting steps to resolve issues threads suddenly disappearance.
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
troubleshooting,
|
||||||
|
thread disappearance,
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
When you encounter the error of old threads suddenly disappear. This can happen when a new, unintentional file is created in `/jan/threads`.
|
||||||
|
|
||||||
|
It can be resolved through the following steps:
|
||||||
|
|
||||||
|
1. Go to `/jan/threads`.
|
||||||
|
|
||||||
|
2. The `/jan/threads` directory contains many folders named with the prefix `jan_` followed by an ID (e.g., `jan_123`). Look for any file not conforming to this naming pattern and remove it.
|
||||||
@ -17,6 +17,83 @@ keywords:
|
|||||||
]
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
:::caution
|
## General Issues
|
||||||
This is currently under development.
|
|
||||||
|
- **Why can't I download models like Pandora 11B Q4 and Solar Instruct 10.7B Q4?**
|
||||||
|
- These models might have been removed or taken down. Please check the [Pre-configured Models](models-list.mdx) for the latest updates on model availability.
|
||||||
|
|
||||||
|
- **Why does Jan display "Apologies, something's amiss" when I try to run it?**
|
||||||
|
- This issue may arise if you're using an older Intel chip that does not fully support AVX instructions required for running AI models. Upgrading your hardware may resolve this issue.
|
||||||
|
|
||||||
|
- **How can I use Jan in Russia?**
|
||||||
|
- To use Jan in Russia, a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) is recommended to bypass any regional restrictions that might be in place.
|
||||||
|
|
||||||
|
- **I'm experiencing an error on startup from Nitro. What should I do?**
|
||||||
|
- If you encounter errors with Nitro, try switching the path to use the Nitro executable for the version 12-0. This adjustment can help resolve path-related issues.
|
||||||
|
|
||||||
|
## Download and Installation Issues
|
||||||
|
|
||||||
|
- **What does "Error occurred: Unexpected token" mean?**
|
||||||
|
- This error usually indicates a problem with your internet connection or that your access to certain resources is being blocked. Using a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) can help avoid these issues by providing a secure and unrestricted internet connection.
|
||||||
|
|
||||||
|
- **Why aren't my downloads working?**
|
||||||
|
- If you're having trouble downloading directly through Jan, you might want to download the model separately and then import it into Jan. Detailed instructions are available on [here](install.mdx).
|
||||||
|
|
||||||
|
- **Jan AI doesn't open on my Mac with an Intel processor. What can I do?**
|
||||||
|
- Granting the `.npm` folder permission for the user can resolve issues related to permissions on macOS, especially for users with Intel processors.
|
||||||
|
|
||||||
|
- **What should I do if the model download freezes?**
|
||||||
|
- If a model download freezes, consider importing the models manually. You can find more detailed guidance on how to do this at [Manual Import](./models/import-models.mdx) article.
|
||||||
|
|
||||||
|
- **I received a message that the model GPT4 does not exist or I do not have access. What should I do?**
|
||||||
|
- This message typically means you need to top up your credit with OpenAI or check your access permissions for the model.
|
||||||
|
|
||||||
|
- **I can't download models from "Explore the Hub." What's the solution?**
|
||||||
|
- Uninstalling Jan, clearing the cache, and reinstalling it following the guide provided [here](install.mdx) may help. Also, consider downloading the `.gguf` model via a browser as an alternative approach.
|
||||||
|
|
||||||
|
## Technical Issues and Solutions
|
||||||
|
|
||||||
|
- **How can I download models with a socks5 proxy or import a local model file?**
|
||||||
|
- Nightly builds of Jan offer support for downloading models with socks5 proxies or importing local model files.
|
||||||
|
|
||||||
|
- **My device shows no GPU usage and lacks a Settings folder. What should I do?**
|
||||||
|
- Using the nightly builds of Jan can address issues related to GPU usage and the absence of a Settings folder, as these builds contain the latest fixes and features.
|
||||||
|
|
||||||
|
- **Why does Jan display a toast message saying a model is loaded when it is not actually loaded?**
|
||||||
|
- This issue can be resolved by downloading the `.gguf` file from Hugging Face and replacing it in the model folder. This ensures the correct model is loaded.
|
||||||
|
|
||||||
|
- **How to enable CORS when running Nitro?**
|
||||||
|
- By default, CORS (Cross-Origin Resource Sharing) is disabled when running Nitro. Enabling CORS can be necessary for certain operations and integrations. Check the official documentation for instructions on how to enable CORS if your workflow requires it.
|
||||||
|
|
||||||
|
## Compatibility and Support
|
||||||
|
|
||||||
|
- **How to use GPU AMD for Jan?**
|
||||||
|
- Jan now supports AMD GPUs through Vulkan. This enhancement allows users with AMD graphics cards to leverage GPU acceleration, improving performance for AI model computations.
|
||||||
|
|
||||||
|
- **Is Jan available for Android or iOS?**
|
||||||
|
- Jan is primarily focused on the Desktop app and does not currently offer mobile apps for Android or iOS. The development team is concentrating on enhancing the desktop experience.
|
||||||
|
|
||||||
|
## Development and Features
|
||||||
|
|
||||||
|
- **Does Jan support Safetensors?**
|
||||||
|
- At the moment, Jan only supports GGUF. However, there are plans to support `.safetensor` files in the future.
|
||||||
|
|
||||||
|
- **I hope to customize the installation path of each model. Is that possible?**
|
||||||
|
- Yes you can customize the installation path. Please see [here](https://jan.ai/guides/advanced-settings/#access-the-jan-data-folder) for more information.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **What should I do if there's high CPU usage while Jan is idle?**
|
||||||
|
- If you notice high CPU usage while Jan is idle, consider using the nightly builds of Jan
|
||||||
|
|
||||||
|
- **What does the error "Failed to fetch" mean, and how can I fix it?**
|
||||||
|
- The "Failed to fetch" error typically occurs due to network issues or restrictions. Using the nightly builds of Jan may help overcome these issues by providing updated fixes and features.
|
||||||
|
|
||||||
|
- **What should I do if "Failed to fetch" occurs using MacBook Pro with Intel HD Graphics 4000 1536 MB?**
|
||||||
|
- Ensure that the model size is less than 90% of your available VRAM and that the VRAM is accessible to the app. Managing the resources effectively can help mitigate this issue.
|
||||||
|
|
||||||
|
:::info[Assistance and Support]
|
||||||
|
|
||||||
|
If you have questions, please join our [Discord community](https://discord.gg/Dt7MxDyNNZ) for support, updates, and discussions.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@ -24,7 +24,10 @@ import installImageURL from './assets/jan-ai-download.png';
|
|||||||
<TabItem value="mac" label = "Mac" default>
|
<TabItem value="mac" label = "Mac" default>
|
||||||
|
|
||||||
### Pre-requisites
|
### Pre-requisites
|
||||||
Ensure that your MacOS version is 13 or higher to run Jan.
|
Before installing Jan, ensure :
|
||||||
|
- You have a Mac with an Apple Silicon Processor.
|
||||||
|
- Homebrew and its dependencies are installed. (for Installing Jan with Homebrew Package)
|
||||||
|
- Your macOS version is 10.15 or higher.
|
||||||
|
|
||||||
### Stable Releases
|
### Stable Releases
|
||||||
|
|
||||||
@ -42,9 +45,16 @@ import installImageURL from './assets/jan-ai-download.png';
|
|||||||
|
|
||||||
To enable the experimental mode, go to **Settings** > **Advanced Settings** and toggle the **Experimental Mode**
|
To enable the experimental mode, go to **Settings** > **Advanced Settings** and toggle the **Experimental Mode**
|
||||||
|
|
||||||
|
### Install with Homebrew
|
||||||
|
Install Jan with the following Homebrew command:
|
||||||
|
|
||||||
|
```brew
|
||||||
|
brew install --cask jan
|
||||||
|
```
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
|
|
||||||
If you are stuck in a broken build, go to the [Broken Build](/guides/common-error/broken-build) section of Common Errors.
|
Homebrew package installation is currently limited to **Apple Silicon Macs**, with upcoming support for Windows and Linux.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
84
docs/docs/guides/integration/groq.mdx
Normal file
84
docs/docs/guides/integration/groq.mdx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
title: Groq
|
||||||
|
sidebar_position: 10
|
||||||
|
slug: /guides/integration/groq
|
||||||
|
description: Learn how to integrate Groq API with Jan for enhanced functionality.
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Groq API,
|
||||||
|
Jan,
|
||||||
|
Jan AI,
|
||||||
|
ChatGPT alternative,
|
||||||
|
conversational AI,
|
||||||
|
large language model,
|
||||||
|
integration,
|
||||||
|
Groq integration,
|
||||||
|
API integration
|
||||||
|
]
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Integrate Mistral AI with Jan
|
||||||
|
|
||||||
|
This guide provides step-by-step instructions on integrating the Groq API with Jan, enabling users to leverage Groq's capabilities within Jan's conversational interface.
|
||||||
|
|
||||||
|
Before proceeding, ensure you have the following:
|
||||||
|
- Access to the Jan Application
|
||||||
|
- Groq API credentials
|
||||||
|
|
||||||
|
## Integration Steps
|
||||||
|
|
||||||
|
### Step 1: Obtain Groq API Credentials
|
||||||
|
|
||||||
|
If you haven't already, sign up for the Groq API and obtain your API credentials.
|
||||||
|
Obtain Groq API keys from your [Groq Console](https://console.groq.com/keys).
|
||||||
|
|
||||||
|
### Step 2: Configure Jan Settings
|
||||||
|
|
||||||
|
1. Insert the Groq AI API key into `~/jan/engines/openai.json`.
|
||||||
|
|
||||||
|
```json title="~/jan/engines/openai.json"
|
||||||
|
{
|
||||||
|
"full_url": "https://api.groq.com/openai/v1/chat/completions",
|
||||||
|
"api_key": "<your-groq-api-key>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Enable Groq Integration
|
||||||
|
|
||||||
|
To set up the configuration for Groq in Jan, follow these steps:
|
||||||
|
|
||||||
|
1. Navigate to `~/jan/models`.
|
||||||
|
2. Create a folder named `groq`.
|
||||||
|
3. Inside the groq folder, create a model.json file with the specified settings:
|
||||||
|
```json title="~/jan/models/groq/model.json
|
||||||
|
{
|
||||||
|
"id": "mixtral-8x7b-32768",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Groq Integration",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Integration with Groq API for enhanced functionality.",
|
||||||
|
"format": "api",
|
||||||
|
"sources": [],
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral",
|
||||||
|
"tags": ["Groq Integration"]
|
||||||
|
},
|
||||||
|
"engine": "openai"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Start the Model
|
||||||
|
|
||||||
|
1. Restart Jan and navigate to the **Hub**.
|
||||||
|
2. Locate your model and click the **Use** button.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you encounter any issues during the integration process or while using Groq with Jan, consider the following troubleshooting steps:
|
||||||
|
|
||||||
|
- Double-check your API credentials and ensure they are correctly entered.
|
||||||
|
- Verify that the Groq integration is enabled within Jan's settings.
|
||||||
|
- Check for any error messages or logs that may provide insight into the issue.
|
||||||
|
- Reach out to Groq API support for assistance if needed.
|
||||||
@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Manual Import
|
title: Manual Import
|
||||||
slug: /guides/using-models/import-manually/
|
|
||||||
sidebar_position: 3
|
sidebar_position: 3
|
||||||
description: A step-by-step guide on how to perform manual import feature.
|
description: A step-by-step guide on how to perform manual import feature.
|
||||||
keywords:
|
keywords:
|
||||||
@ -25,6 +24,23 @@ import janModel from './assets/jan-model-hub.png';
|
|||||||
|
|
||||||
This guide will show you how to perform manual import. In this guide, we are using a GGUF model from [HuggingFace](https://huggingface.co/) and our latest model, [Trinity](https://huggingface.co/janhq/trinity-v1-GGUF), as an example.
|
This guide will show you how to perform manual import. In this guide, we are using a GGUF model from [HuggingFace](https://huggingface.co/) and our latest model, [Trinity](https://huggingface.co/janhq/trinity-v1-GGUF), as an example.
|
||||||
|
|
||||||
|
## Newer versions - nightly versions and v0.4.8+
|
||||||
|
|
||||||
|
Starting with version 0.4.8, Jan has introduced the capability to import models using a UI drag-and-drop method. This allows you to import models directly into the Jan application UI by dragging the `.GGUF` file from your directory into the Jan application.
|
||||||
|
|
||||||
|
### 1. Get the Model
|
||||||
|
Download the model from HuggingFace in the `.GGUF` format.
|
||||||
|
|
||||||
|
### 2. Import the Model
|
||||||
|
1. Open your Jan application.
|
||||||
|
2. Click the **Import Model** button.
|
||||||
|
3. Open your downloaded model.
|
||||||
|
4. Drag the `.GGUF` file from your directory into the Jan **Import Model** window.
|
||||||
|
|
||||||
|
### 3. Done!
|
||||||
|
|
||||||
|
If your model doesn't show up in the **Model Selector** in conversations, **restart the app** or contact us via our [Discord community](https://discord.gg/Dt7MxDyNNZ).
|
||||||
|
|
||||||
## Newer versions - nightly versions and v0.4.7+
|
## Newer versions - nightly versions and v0.4.7+
|
||||||
|
|
||||||
Starting from version 0.4.7, Jan has introduced the capability to import models using an absolute file path. It allows you to import models from any directory on your computer.
|
Starting from version 0.4.7, Jan has introduced the capability to import models using an absolute file path. It allows you to import models from any directory on your computer.
|
||||||
|
|||||||
8
docs/docs/guides/providers/README.mdx
Normal file
8
docs/docs/guides/providers/README.mdx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Inference Providers
|
||||||
|
slug: /guides/providers
|
||||||
|
---
|
||||||
|
|
||||||
|
import DocCardList from "@theme/DocCardList";
|
||||||
|
|
||||||
|
<DocCardList />
|
||||||
BIN
docs/docs/guides/providers/image.png
Normal file
BIN
docs/docs/guides/providers/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
10
docs/docs/guides/providers/llama-cpp.md
Normal file
10
docs/docs/guides/providers/llama-cpp.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: llama.cpp
|
||||||
|
slug: /guides/providers/llama-cpp
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
[Nitro](https://github.com/janhq/nitro) is an inference server on top of [llama.cpp](https://github.com/ggerganov/llama.cpp). It provides an OpenAI-compatible API, queue, & scaling.
|
||||||
|
|
||||||
|
Nitro is the default AI engine downloaded with Jan. There is no additional setup needed.
|
||||||
87
docs/docs/guides/providers/tensorrt-llm.md
Normal file
87
docs/docs/guides/providers/tensorrt-llm.md
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
title: TensorRT-LLM
|
||||||
|
slug: /guides/providers/tensorrt-llm
|
||||||
|
---
|
||||||
|
|
||||||
|
Users with Nvidia GPUs can get **20-40% faster\* token speeds** on their laptop or desktops by using [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM). The greater implication is that you are running FP16, which is also more accurate than quantized models.
|
||||||
|
|
||||||
|
This guide walks you through how to install Jan's official [TensorRT-LLM Extension](https://github.com/janhq/nitro-tensorrt-llm). This extension uses [Nitro-TensorRT-LLM](https://github.com/janhq/nitro-tensorrt-llm) as the AI engine, instead of the default [Nitro-Llama-CPP](https://github.com/janhq/nitro). It includes an efficient C++ server to natively execute the [TRT-LLM C++ runtime](https://nvidia.github.io/TensorRT-LLM/gpt_runtime.html). It also comes with additional feature and performance improvements like OpenAI compatibility, tokenizer improvements, and queues.
|
||||||
|
|
||||||
|
*Compared to using LlamaCPP engine.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
This feature is only available for Windows users. Linux is coming soon.
|
||||||
|
|
||||||
|
Additionally, we only prebuilt a few demo models. You can always build your desired models directly on your machine. [Read here](#build-your-own-tensorrt-models).
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- A Windows PC
|
||||||
|
- Nvidia GPU(s): Ada or Ampere series (i.e. RTX 4000s & 3000s). More will be supported soon.
|
||||||
|
- 3GB+ of disk space to download TRT-LLM artifacts and a Nitro binary
|
||||||
|
- Jan v0.4.9+ or Jan v0.4.8-321+ (nightly)
|
||||||
|
- Nvidia Driver v535+ ([installation guide](https://jan.ai/guides/common-error/not-using-gpu/#1-ensure-gpu-mode-requirements))
|
||||||
|
- CUDA Toolkit v12.2+ ([installation guide](https://jan.ai/guides/common-error/not-using-gpu/#1-ensure-gpu-mode-requirements))
|
||||||
|
|
||||||
|
## Install TensorRT-Extension
|
||||||
|
|
||||||
|
1. Go to Settings > Extensions
|
||||||
|
2. Click install next to the TensorRT-LLM Extension
|
||||||
|
3. Check that files are correctly downloaded
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls ~\jan\extensions\@janhq\tensorrt-llm-extension\dist\bin
|
||||||
|
# Your Extension Folder should now include `nitro.exe`, among other artifacts needed to run TRT-LLM
|
||||||
|
```
|
||||||
|
|
||||||
|
## Download a Compatible Model
|
||||||
|
TensorRT-LLM can only run models in `TensorRT` format. These models, aka "TensorRT Engines", are prebuilt specifically for each target OS+GPU architecture.
|
||||||
|
|
||||||
|
We offer a handful of precompiled models for Ampere and Ada cards that you can immediately download and play with:
|
||||||
|
|
||||||
|
1. Restart the application and go to the Hub
|
||||||
|
2. Look for models with the `TensorRT-LLM` label in the recommended models list. Click download. This step might take some time. 🙏
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Click use and start chatting!
|
||||||
|
4. You may need to allow Nitro in your network
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
:::warning
|
||||||
|
If you are our nightly builds, you may have to reinstall the TensorRT-LLM extension each time you update the app. We're working on better extension lifecyles - stay tuned.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Configure Settings
|
||||||
|
|
||||||
|
You can customize the default parameters for how Jan runs TensorRT-LLM.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
coming soon
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Incompatible Extension vs Engine versions
|
||||||
|
|
||||||
|
For now, the model versions are pinned to the extension versions.
|
||||||
|
|
||||||
|
### Uninstall Extension
|
||||||
|
|
||||||
|
1. Quit the app
|
||||||
|
2. Go to Settings > Extensions
|
||||||
|
3. Delete the entire Extensions folder.
|
||||||
|
4. Reopen the app, only the default extensions should be restored.
|
||||||
|
|
||||||
|
### Install Nitro-TensorRT-LLM manually
|
||||||
|
|
||||||
|
To manually build the artifacts needed to run the server and TensorRT-LLM, you can reference the source code. [Read here](https://github.com/janhq/nitro-tensorrt-llm?tab=readme-ov-file#quickstart).
|
||||||
|
|
||||||
|
### Build your own TensorRT models
|
||||||
|
|
||||||
|
:::info
|
||||||
|
coming soon
|
||||||
|
:::
|
||||||
@ -1,3 +1,88 @@
|
|||||||
---
|
---
|
||||||
title: Website & Docs
|
title: Website & Docs
|
||||||
---
|
---
|
||||||
|
|
||||||
|
This website is built using [Docusaurus 3.0](https://docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
|
### Information Architecture
|
||||||
|
|
||||||
|
We try to **keep routes consistent** to maintain SEO.
|
||||||
|
|
||||||
|
- **`/guides/`**: Guides on how to use the Jan application. For end users who are directly using Jan.
|
||||||
|
|
||||||
|
- **`/developer/`**: Developer docs on how to extend Jan. These pages are about what people can build with our software.
|
||||||
|
|
||||||
|
- **`/api-reference/`**: Reference documentation for the Jan API server, written in Swagger/OpenAPI format.
|
||||||
|
|
||||||
|
- **`/changelog/`**: A list of changes made to the Jan application with each release.
|
||||||
|
|
||||||
|
- **`/blog/`**: A blog for the Jan application.
|
||||||
|
|
||||||
|
### Sidebar Autogeneration
|
||||||
|
|
||||||
|
The order of each page is either explicitly defined in `sidebar.js` or follows the [Docusaurus autogenerated](https://docusaurus.io/docs/next/sidebar/autogenerated) naming format, `##-path-name.md`.
|
||||||
|
|
||||||
|
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: Overview
|
||||||
|
slug: /docs
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
||||||
|
|
||||||
|
### Pre-requisites and Installation
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher)
|
||||||
|
- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher)
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd jan/docs
|
||||||
|
yarn install
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
|
|
||||||
|
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||||
|
|
||||||
|
#### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
Using SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
USE_SSH=true yarn deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Not using SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GIT_USER=<Your GitHub username> yarn deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
|
|
||||||
|
### Preview URL, Pre-release and Publishing Documentation
|
||||||
|
|
||||||
|
- When a pull request is created, the preview URL will be automatically commented on the pull request.
|
||||||
|
|
||||||
|
- The documentation will then be published to [https://dev.jan.ai/](https://dev.jan.ai/) when the pull request is merged to `dev`.
|
||||||
|
|
||||||
|
- Our open-source maintainers will sync the updated content from `dev` to `docs` branch, which will then be published to [https://jan.ai/](https://jan.ai/).
|
||||||
|
|
||||||
|
### Additional Plugins
|
||||||
|
|
||||||
|
- @docusaurus/theme-live-codeblock
|
||||||
|
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/jan.yaml` to update the API reference documentation.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 16
|
sidebar_position: 17
|
||||||
slug: /changelog/changelog-v0.2.0
|
slug: /changelog/changelog-v0.2.0
|
||||||
---
|
---
|
||||||
# v0.2.0
|
# v0.2.0
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 15
|
sidebar_position: 16
|
||||||
slug: /changelog/changelog-v0.2.1
|
slug: /changelog/changelog-v0.2.1
|
||||||
---
|
---
|
||||||
# v0.2.1
|
# v0.2.1
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 14
|
sidebar_position: 15
|
||||||
slug: /changelog/changelog-v0.2.2
|
slug: /changelog/changelog-v0.2.2
|
||||||
---
|
---
|
||||||
# v0.2.2
|
# v0.2.2
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 13
|
sidebar_position: 14
|
||||||
slug: /changelog/changelog-v0.2.3
|
slug: /changelog/changelog-v0.2.3
|
||||||
---
|
---
|
||||||
# v0.2.3
|
# v0.2.3
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 12
|
sidebar_position: 13
|
||||||
slug: /changelog/changelog-v0.3.0
|
slug: /changelog/changelog-v0.3.0
|
||||||
---
|
---
|
||||||
# v0.3.0
|
# v0.3.0
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 11
|
sidebar_position: 12
|
||||||
slug: /changelog/changelog-v0.3.1
|
slug: /changelog/changelog-v0.3.1
|
||||||
---
|
---
|
||||||
# v0.3.1
|
# v0.3.1
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 10
|
sidebar_position: 11
|
||||||
slug: /changelog/changelog-v0.3.2
|
slug: /changelog/changelog-v0.3.2
|
||||||
---
|
---
|
||||||
# v0.3.2
|
# v0.3.2
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 9
|
sidebar_position: 10
|
||||||
slug: /changelog/changelog-v0.3.3
|
slug: /changelog/changelog-v0.3.3
|
||||||
---
|
---
|
||||||
# v0.3.3
|
# v0.3.3
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 8
|
sidebar_position: 9
|
||||||
slug: /changelog/changelog-v0.4.0
|
slug: /changelog/changelog-v0.4.0
|
||||||
---
|
---
|
||||||
# v0.4.0
|
# v0.4.0
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 7
|
sidebar_position: 8
|
||||||
slug: /changelog/changelog-v0.4.1
|
slug: /changelog/changelog-v0.4.1
|
||||||
---
|
---
|
||||||
# v0.4.1
|
# v0.4.1
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 6
|
sidebar_position: 7
|
||||||
slug: /changelog/changelog-v0.4.2
|
slug: /changelog/changelog-v0.4.2
|
||||||
---
|
---
|
||||||
# v0.4.2
|
# v0.4.2
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 5
|
sidebar_position: 6
|
||||||
slug: /changelog/changelog-v0.4.3
|
slug: /changelog/changelog-v0.4.3
|
||||||
---
|
---
|
||||||
# v0.4.3
|
# v0.4.3
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 4
|
sidebar_position: 5
|
||||||
slug: /changelog/changelog-v0.4.4
|
slug: /changelog/changelog-v0.4.4
|
||||||
---
|
---
|
||||||
# v0.4.4
|
# v0.4.4
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 3
|
sidebar_position: 4
|
||||||
slug: /changelog/changelog-v0.4.5
|
slug: /changelog/changelog-v0.4.5
|
||||||
---
|
---
|
||||||
# v0.4.5
|
# v0.4.5
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 3
|
||||||
slug: /changelog/changelog-v0.4.6
|
slug: /changelog/changelog-v0.4.6
|
||||||
---
|
---
|
||||||
# v0.4.6
|
# v0.4.6
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 1
|
sidebar_position: 2
|
||||||
slug: /changelog/changelog-v0.4.7
|
slug: /changelog/changelog-v0.4.7
|
||||||
---
|
---
|
||||||
# v0.4.7
|
# v0.4.7
|
||||||
|
|||||||
98
docs/docs/releases/changelog/changelog-v0.4.8.mdx
Normal file
98
docs/docs/releases/changelog/changelog-v0.4.8.mdx
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 1
|
||||||
|
slug: /changelog/changelog-v0.4.8
|
||||||
|
---
|
||||||
|
# v0.4.8
|
||||||
|
|
||||||
|
For more details, [GitHub Issues](https://github.com/janhq/jan/releases/tag/v0.4.8)
|
||||||
|
|
||||||
|
Highlighted Issue: [Issue #2267: Release cut v0.4.8](https://github.com/janhq/jan/pull/2267)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Release cut v0.4.8 @louis-jan (#2267)
|
||||||
|
- Add modify notary team in CI @hiento09 (#2265)
|
||||||
|
- Chore: Update new models to model hub @hahuyhoang411 (#2192)
|
||||||
|
- Macos Notarize migrage to new Team ID @hiento09 (#2228)
|
||||||
|
- docs: update API Reference assistants\_id endpoint from DevDocs @avb-is-me (#2195)
|
||||||
|
- docs: update API Reference assistants endpoint from DevDocs @avb-is-me (#2194)
|
||||||
|
- docs: update API Reference threads endpoint from DevDocs @avb-is-me (#2182)
|
||||||
|
- fix: wrong profile parameter in docker command @mooncool (#2159)
|
||||||
|
- Sync release 0.4.7 to dev @louis-jan (#2151)
|
||||||
|
- docs: add upstream acknowledgements @hieu-jan (#2136)
|
||||||
|
- Sync dev branch to docs branch @hieu-jan (#2131)
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
- feat: prompt user to download an update manually @louis-jan (#2261)
|
||||||
|
- feat: Jan can see @hiro-v (#2069)
|
||||||
|
- Revert feat: temporary remove dark mode @urmauur (#2221)
|
||||||
|
- feat: add turborepo @louis-jan (#2220)
|
||||||
|
- fix: change button import model on hub page @urmauur (#2178)
|
||||||
|
- feat: temporary remove dark mode :( @urmauur (#2168)
|
||||||
|
- feat: add import model feature @namchuai (#2104)
|
||||||
|
- feat: restore docusaurus style @urmauur (#2152)
|
||||||
|
- feat: add a simple way to convert Hugging Face model to GGUF @Helloyunho (#1972)
|
||||||
|
|
||||||
|
## 🐛 Fixes
|
||||||
|
|
||||||
|
- codesign script force sign @hiento09 (#2291)
|
||||||
|
- fix: should not attach error messages to the completion request @louis-jan (#2258)
|
||||||
|
- fix: image upload button and drag event are not enabled @louis-jan (#2248)
|
||||||
|
- fix: error message being sent along with conversation when inference @namchuai (#2242)
|
||||||
|
- fix: replaced user path from app log @namchuai (#2238)
|
||||||
|
- fix: drag and drop support image format to support vision model @urmauur (#2237)
|
||||||
|
- fix: re-configure changelog sections @hieu-jan (#2230)
|
||||||
|
- fix: import from HuggingFace with random string is causing app crash @louis-jan (#2214)
|
||||||
|
- fix: comment from QA regarding import model @namchuai (#2213)
|
||||||
|
- fix: download model error does not reset state in model hub @namchuai (#2199)
|
||||||
|
- fix: minor ui missing secondary background @urmauur (#2198)
|
||||||
|
- docs: update docker command @hieu-jan (#2180)
|
||||||
|
- fix: some bugs for import model @namchuai (#2181)
|
||||||
|
- fix: change button import model on hub page @urmauur (#2178)
|
||||||
|
- fix space between progress bar and title list of gpu @urmauur (#2177)
|
||||||
|
- fix: disabled prompt user using dangerouslySetInnerHTML @urmauur (#2176)
|
||||||
|
- fix: style list of gpus on system monitor @urmauur (#2172)
|
||||||
|
- fix: system monitor expand overlap tooltip ribbon @urmauur (#2158)
|
||||||
|
- Huggingface extension add codesign step for building on darwin @hiento09 (#2166)
|
||||||
|
- Add run codesign for huggingface extension @hiento09 (#2163)
|
||||||
|
- fix: system monitor ui @urmauur (#2135)
|
||||||
|
|
||||||
|
## 🧰 Maintenance
|
||||||
|
|
||||||
|
- chore: temporary remove convert model @namchuai (#2266)
|
||||||
|
- docs: sync slug fix from dev branch to docs branch @hieu-jan (#2264)
|
||||||
|
- docs: Update broken link and fix the slug @aindrajaya (#2260)
|
||||||
|
- docs: Fix navbar issues. Keep stay when clicked other menu items from the sidebar @aindrajaya (#2253)
|
||||||
|
- docs: sync docs hub fixes from dev to docs branch @hieu-jan (#2247)
|
||||||
|
- docs: Update content for Hub page and Guides section @aindrajaya (#2245)
|
||||||
|
- docs: Fix Dark Mode on the Hub page and Update the Navbar functionality @aindrajaya (#2243)
|
||||||
|
- chore: sync dev branch to docs branch @hieu-jan (#2239)
|
||||||
|
- Chore: add prefix latest for task clean r2 bucket @hiento09 (#2233)
|
||||||
|
- fix: re-configure changelog sections @hieu-jan (#2230)
|
||||||
|
- docs: add command run API server without frontend @hieu-jan (#2231)
|
||||||
|
- docs: revamp entire Jan guides @hieu-jan (#2139)
|
||||||
|
- chore: clean up some redundant code @namchuai (#2215)
|
||||||
|
- docs: update API Reference chatCompletions from DevDocs @avb-is-me (#2171)
|
||||||
|
- docs: update API Reference download model from DevDocs @avb-is-me (#2170)
|
||||||
|
- docs: update API Reference model\_id from DevDocs @avb-is-me (#2169)
|
||||||
|
- docs: update API Reference listModel from DevDocs @avb-is-me (#2161)
|
||||||
|
- docs: Update 08-antivirus-compatibility-testing.md @0xSage (#2186)
|
||||||
|
- docs: adding new feature for v0.4.7 to release checklist @Van-QA (#2189)
|
||||||
|
- docs: Update 01-integrate-continue.mdx @0xSage (#2187)
|
||||||
|
- chore: bump nitro 0.3.14 @louis-jan (#2183)
|
||||||
|
- docs: Sync dev branch to docs branch @hieu-jan (#2185)
|
||||||
|
- docs: update docker command @hieu-jan (#2180)
|
||||||
|
- docs: update wall of love @hieu-jan (#2179)
|
||||||
|
- docs: add Jan newsletter @hieu-jan (#2174)
|
||||||
|
- chore: make convert gguf as experimental feature @namchuai (#2156)
|
||||||
|
- docs: update acknowledgements @hieu-jan (#2147)
|
||||||
|
- feat: restore docusaurus style @urmauur (#2152)
|
||||||
|
- docs: update run Jan in Docker mode @hieu-jan (#2150)
|
||||||
|
- Docs pena team - Add Quickstart Docs @aindrajaya (#2138)
|
||||||
|
- docs: hide incomplete pages @hieu-jan (#2127)
|
||||||
|
|
||||||
|
## Contributor
|
||||||
|
|
||||||
|
@0xSage, @Helloyunho, @Van-QA, @aindrajaya, @avb-is-me, @hahuyhoang411, @hiento09, @hieu-jan, @hiro-v, @jan-service-account, @louis-jan, @mooncool, @namchuai and @urmauur
|
||||||
|
|
||||||
@ -1,37 +1,36 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
// Note: type annotations allow type checking and IDEs autocompletion
|
// Note: type annotations allow type checking and IDEs autocompletion
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
require("dotenv").config();
|
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
|
||||||
|
const path = require('path')
|
||||||
const darkCodeTheme = require("prism-react-renderer/themes/dracula");
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
/** @type {import('@docusaurus/types').Config} */
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
title: "Jan",
|
title: 'Jan',
|
||||||
tagline: "Run your own AI",
|
tagline: 'Run your own AI',
|
||||||
favicon: "img/favicon.ico",
|
favicon: 'img/favicon.ico',
|
||||||
|
|
||||||
// Set the production url of your site here
|
// Set the production url of your site here
|
||||||
url: "https://jan.ai",
|
url: 'https://jan.ai',
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
// For GitHub pages deployment, it is often '/<projectName>/'
|
// For GitHub pages deployment, it is often '/<projectName>/'
|
||||||
baseUrl: "/",
|
baseUrl: '/',
|
||||||
|
|
||||||
// GitHub pages deployment config.
|
// GitHub pages deployment config.
|
||||||
// If you aren't using GitHub pages, you don't need these.
|
// If you aren't using GitHub pages, you don't need these.
|
||||||
organizationName: "janhq", // Usually your GitHub org/user name.
|
organizationName: 'janhq', // Usually your GitHub org/user name.
|
||||||
projectName: "jan", // Usually your repo name.
|
projectName: 'jan', // Usually your repo name.
|
||||||
|
|
||||||
onBrokenLinks: "warn",
|
onBrokenLinks: 'warn',
|
||||||
onBrokenMarkdownLinks: "warn",
|
onBrokenMarkdownLinks: 'warn',
|
||||||
trailingSlash: true,
|
trailingSlash: true,
|
||||||
// Even if you don't use internalization, you can use this field to set useful
|
// Even if you don't use internalization, you can use this field to set useful
|
||||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||||
// to replace "en" with "zh-Hans".
|
// to replace "en" with "zh-Hans".
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "en",
|
defaultLocale: 'en',
|
||||||
locales: ["en"],
|
locales: ['en'],
|
||||||
},
|
},
|
||||||
|
|
||||||
markdown: {
|
markdown: {
|
||||||
@ -42,69 +41,81 @@ const config = {
|
|||||||
|
|
||||||
// Plugins we added
|
// Plugins we added
|
||||||
plugins: [
|
plugins: [
|
||||||
"docusaurus-plugin-sass",
|
'docusaurus-plugin-sass',
|
||||||
async function myPlugin(context, options) {
|
async function myPlugin(context, options) {
|
||||||
return {
|
return {
|
||||||
name: "docusaurus-tailwindcss",
|
name: 'docusaurus-tailwindcss',
|
||||||
configurePostCss(postcssOptions) {
|
configurePostCss(postcssOptions) {
|
||||||
// Appends TailwindCSS and AutoPrefixer.
|
// Appends TailwindCSS and AutoPrefixer.
|
||||||
postcssOptions.plugins.push(require("tailwindcss"));
|
postcssOptions.plugins.push(require('tailwindcss'))
|
||||||
postcssOptions.plugins.push(require("autoprefixer"));
|
postcssOptions.plugins.push(require('autoprefixer'))
|
||||||
return postcssOptions;
|
return postcssOptions
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
"posthog-docusaurus",
|
'posthog-docusaurus',
|
||||||
{
|
{
|
||||||
apiKey: process.env.POSTHOG_PROJECT_API_KEY || "XXX",
|
apiKey: process.env.POSTHOG_PROJECT_API_KEY || 'XXX',
|
||||||
appUrl: process.env.POSTHOG_APP_URL || "XXX", // optional
|
appUrl: process.env.POSTHOG_APP_URL || 'XXX', // optional
|
||||||
enableInDevelopment: false, // optional
|
enableInDevelopment: false, // optional
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"@docusaurus/plugin-client-redirects",
|
'@docusaurus/plugin-client-redirects',
|
||||||
{
|
{
|
||||||
redirects: [
|
redirects: [
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/failed-to-fetch",
|
from: '/troubleshooting/failed-to-fetch',
|
||||||
to: "/guides/error-codes/something-amiss/",
|
to: '/guides/error-codes/something-amiss/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/guides/troubleshooting/gpu-not-used/",
|
from: '/guides/troubleshooting/gpu-not-used/',
|
||||||
to: "/guides/common-error/not-using-gpu/",
|
to: '/guides/common-error/not-using-gpu/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/guides/troubleshooting/",
|
from: '/guides/troubleshooting/',
|
||||||
to: "/guides/error-codes/",
|
to: '/guides/error-codes/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/stuck-on-broken-build/",
|
from: '/troubleshooting/stuck-on-broken-build/',
|
||||||
to: "/guides/common-error/broken-build/",
|
to: '/guides/common-error/broken-build/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/guides/troubleshooting/",
|
from: '/guides/troubleshooting/',
|
||||||
to: "/guides/error-codes/",
|
to: '/guides/error-codes/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/somethings-amiss/",
|
from: '/troubleshooting/somethings-amiss/',
|
||||||
to: "/guides/error-codes/something-amiss/",
|
to: '/guides/error-codes/something-amiss/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/how-to-get-error-logs/",
|
from: '/troubleshooting/how-to-get-error-logs/',
|
||||||
to: "/guides/error-codes/how-to-get-error-logs/",
|
to: '/guides/error-codes/how-to-get-error-logs/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/permission-denied/",
|
from: '/troubleshooting/permission-denied/',
|
||||||
to: "/guides/error-codes/permission-denied/",
|
to: '/guides/error-codes/permission-denied/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/unexpected-token/",
|
from: '/troubleshooting/unexpected-token/',
|
||||||
to: "/guides/error-codes/unexpected-token/",
|
to: '/guides/error-codes/unexpected-token/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "/troubleshooting/undefined-issue/",
|
from: '/troubleshooting/undefined-issue/',
|
||||||
to: "/guides/error-codes/undefined-issue/",
|
to: '/guides/error-codes/undefined-issue/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/install/',
|
||||||
|
to: '/guides/install/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/guides/using-models/',
|
||||||
|
to: '/guides/models-setup/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: '/guides/using-extensions/',
|
||||||
|
to: '/guides/extensions/',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -112,40 +123,52 @@ const config = {
|
|||||||
|
|
||||||
//To input custom Plugin
|
//To input custom Plugin
|
||||||
path.resolve(__dirname, 'plugins', 'changelog-plugin'),
|
path.resolve(__dirname, 'plugins', 'changelog-plugin'),
|
||||||
|
[
|
||||||
|
'@scalar/docusaurus',
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
route: '/api-reference',
|
||||||
|
configuration: {
|
||||||
|
spec: {
|
||||||
|
url: 'https://raw.githubusercontent.com/janhq/jan/dev/docs/openapi/jan.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
// The classic preset will relay each option entry to the respective sub plugin/theme.
|
// The classic preset will relay each option entry to the respective sub plugin/theme.
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
"@docusaurus/preset-classic",
|
'@docusaurus/preset-classic',
|
||||||
{
|
{
|
||||||
// Will be passed to @docusaurus/plugin-content-docs (false to disable)
|
// Will be passed to @docusaurus/plugin-content-docs (false to disable)
|
||||||
docs: {
|
docs: {
|
||||||
routeBasePath: "/",
|
routeBasePath: '/',
|
||||||
sidebarPath: require.resolve("./sidebars.js"),
|
sidebarPath: require.resolve('./sidebars.js'),
|
||||||
editUrl: "https://github.com/janhq/jan/tree/main/docs",
|
editUrl: 'https://github.com/janhq/jan/tree/dev/docs',
|
||||||
showLastUpdateAuthor: true,
|
showLastUpdateAuthor: true,
|
||||||
showLastUpdateTime: true,
|
showLastUpdateTime: true,
|
||||||
},
|
},
|
||||||
// Will be passed to @docusaurus/plugin-content-sitemap (false to disable)
|
// Will be passed to @docusaurus/plugin-content-sitemap (false to disable)
|
||||||
sitemap: {
|
sitemap: {
|
||||||
changefreq: "daily",
|
changefreq: 'daily',
|
||||||
priority: 1.0,
|
priority: 1.0,
|
||||||
ignorePatterns: ["/tags/**"],
|
ignorePatterns: ['/tags/**'],
|
||||||
filename: "sitemap.xml",
|
filename: 'sitemap.xml',
|
||||||
},
|
},
|
||||||
// Will be passed to @docusaurus/plugin-content-blog (false to disable)
|
// Will be passed to @docusaurus/plugin-content-blog (false to disable)
|
||||||
blog: {
|
blog: {
|
||||||
blogSidebarTitle: "All Posts",
|
blogSidebarTitle: 'All Posts',
|
||||||
blogSidebarCount: "ALL",
|
blogSidebarCount: 'ALL',
|
||||||
},
|
},
|
||||||
// Will be passed to @docusaurus/theme-classic.
|
// Will be passed to @docusaurus/theme-classic.
|
||||||
theme: {
|
theme: {
|
||||||
customCss: require.resolve("./src/styles/main.scss"),
|
customCss: require.resolve('./src/styles/main.scss'),
|
||||||
},
|
},
|
||||||
// GTM is always inactive in development and only active in production to avoid polluting the analytics statistics.
|
// GTM is always inactive in development and only active in production to avoid polluting the analytics statistics.
|
||||||
googleTagManager: {
|
googleTagManager: {
|
||||||
containerId: process.env.GTM_ID || "XXX",
|
containerId: process.env.GTM_ID || 'XXX',
|
||||||
},
|
},
|
||||||
// Will be passed to @docusaurus/plugin-content-pages (false to disable)
|
// Will be passed to @docusaurus/plugin-content-pages (false to disable)
|
||||||
// pages: {},
|
// pages: {},
|
||||||
@ -153,17 +176,17 @@ const config = {
|
|||||||
],
|
],
|
||||||
// Redoc preset
|
// Redoc preset
|
||||||
[
|
[
|
||||||
"redocusaurus",
|
'redocusaurus',
|
||||||
{
|
{
|
||||||
specs: [
|
specs: [
|
||||||
{
|
{
|
||||||
spec: "openapi/jan.yaml", // can be local file, url, or parsed json object
|
spec: 'openapi/jan.yaml', // can be local file, url, or parsed json object
|
||||||
route: "/api-reference/", // path where to render docs
|
route: '/api-reference-1.0/', // path where to render docs
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
primaryColor: "#1a73e8",
|
primaryColor: '#1a73e8',
|
||||||
primaryColorDark: "#1a73e8",
|
primaryColorDark: '#1a73e8',
|
||||||
options: {
|
options: {
|
||||||
requiredPropsFirst: true,
|
requiredPropsFirst: true,
|
||||||
noAutoAuth: true,
|
noAutoAuth: true,
|
||||||
@ -176,10 +199,10 @@ const config = {
|
|||||||
|
|
||||||
// Docs: https://docusaurus.io/docs/api/themes/configuration
|
// Docs: https://docusaurus.io/docs/api/themes/configuration
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
image: "img/og-image.png",
|
image: 'img/og-image.png',
|
||||||
// Only for react live
|
// Only for react live
|
||||||
liveCodeBlock: {
|
liveCodeBlock: {
|
||||||
playgroundPosition: "bottom",
|
playgroundPosition: 'bottom',
|
||||||
},
|
},
|
||||||
docs: {
|
docs: {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
@ -189,89 +212,89 @@ const config = {
|
|||||||
},
|
},
|
||||||
// Algolia Search Configuration
|
// Algolia Search Configuration
|
||||||
algolia: {
|
algolia: {
|
||||||
appId: process.env.ALGOLIA_APP_ID || "XXX",
|
appId: process.env.ALGOLIA_APP_ID || 'XXX',
|
||||||
apiKey: process.env.ALGOLIA_API_KEY || "XXX",
|
apiKey: process.env.ALGOLIA_API_KEY || 'XXX',
|
||||||
indexName: "jan_docs",
|
indexName: 'jan_docs',
|
||||||
contextualSearch: true,
|
contextualSearch: true,
|
||||||
insights: true,
|
insights: true,
|
||||||
},
|
},
|
||||||
// SEO Docusarus
|
// SEO Docusarus
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
name: "description",
|
name: 'description',
|
||||||
content:
|
content:
|
||||||
"Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.",
|
'Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "keywords",
|
name: 'keywords',
|
||||||
content:
|
content:
|
||||||
"Jan AI, Jan, ChatGPT alternative, local AI, private AI, conversational AI, no-subscription fee, large language model ",
|
'Jan AI, Jan, ChatGPT alternative, local AI, private AI, conversational AI, no-subscription fee, large language model ',
|
||||||
},
|
},
|
||||||
{ name: "robots", content: "index, follow" },
|
{ name: 'robots', content: 'index, follow' },
|
||||||
{
|
{
|
||||||
property: "og:title",
|
property: 'og:title',
|
||||||
content: "Jan | Open-source ChatGPT Alternative",
|
content: 'Jan | Open-source ChatGPT Alternative',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "og:description",
|
property: 'og:description',
|
||||||
content:
|
content:
|
||||||
"Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.",
|
'Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "og:image",
|
property: 'og:image',
|
||||||
content: "https://jan.ai/img/og-image.png",
|
content: 'https://jan.ai/img/og-image.png',
|
||||||
},
|
},
|
||||||
{ property: "og:type", content: "website" },
|
{ property: 'og:type', content: 'website' },
|
||||||
{ property: "twitter:card", content: "summary_large_image" },
|
{ property: 'twitter:card', content: 'summary_large_image' },
|
||||||
{ property: "twitter:site", content: "@janframework" },
|
{ property: 'twitter:site', content: '@janframework' },
|
||||||
{
|
{
|
||||||
property: "twitter:title",
|
property: 'twitter:title',
|
||||||
content: "Jan | Open-source ChatGPT Alternative",
|
content: 'Jan | Open-source ChatGPT Alternative',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "twitter:description",
|
property: 'twitter:description',
|
||||||
content:
|
content:
|
||||||
"Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.",
|
'Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "twitter:image",
|
property: 'twitter:image',
|
||||||
content: "https://jan.ai/img/og-image.png",
|
content: 'https://jan.ai/img/og-image.png',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
headTags: [
|
headTags: [
|
||||||
// Declare a <link> preconnect tag
|
// Declare a <link> preconnect tag
|
||||||
{
|
{
|
||||||
tagName: "link",
|
tagName: 'link',
|
||||||
attributes: {
|
attributes: {
|
||||||
rel: "preconnect",
|
rel: 'preconnect',
|
||||||
href: "https://jan.ai/",
|
href: 'https://jan.ai/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Declare some json-ld structured data
|
// Declare some json-ld structured data
|
||||||
{
|
{
|
||||||
tagName: "script",
|
tagName: 'script',
|
||||||
attributes: {
|
attributes: {
|
||||||
type: "application/ld+json",
|
type: 'application/ld+json',
|
||||||
},
|
},
|
||||||
innerHTML: JSON.stringify({
|
innerHTML: JSON.stringify({
|
||||||
"@context": "https://schema.org/",
|
'@context': 'https://schema.org/',
|
||||||
"@type": "localAI",
|
'@type': 'localAI',
|
||||||
name: "Jan",
|
'name': 'Jan',
|
||||||
description:
|
'description':
|
||||||
"Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.",
|
'Jan runs 100% offline on your computer, utilizes open-source AI models, prioritizes privacy, and is highly customizable.',
|
||||||
keywords:
|
'keywords':
|
||||||
"Jan AI, Jan, ChatGPT alternative, local AI, private AI, conversational AI, no-subscription fee, large language model ",
|
'Jan AI, Jan, ChatGPT alternative, local AI, private AI, conversational AI, no-subscription fee, large language model ',
|
||||||
applicationCategory: "BusinessApplication",
|
'applicationCategory': 'BusinessApplication',
|
||||||
operatingSystem: "Multiple",
|
'operatingSystem': 'Multiple',
|
||||||
url: "https://jan.ai/",
|
'url': 'https://jan.ai/',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
navbar: {
|
navbar: {
|
||||||
title: "Jan",
|
title: 'Jan',
|
||||||
logo: {
|
logo: {
|
||||||
alt: "Jan Logo",
|
alt: 'Jan Logo',
|
||||||
src: "img/logo.svg",
|
src: 'img/logo.svg',
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
// Navbar Left
|
// Navbar Left
|
||||||
@ -282,38 +305,38 @@ const config = {
|
|||||||
// label: "About",
|
// label: "About",
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
label: "About",
|
label: 'About',
|
||||||
position: "left",
|
position: 'left',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: 'doc',
|
||||||
label: "What is Jan?",
|
label: 'What is Jan?',
|
||||||
docId: "about/about",
|
docId: 'about/about',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: 'doc',
|
||||||
label: "Who we are",
|
label: 'Who we are',
|
||||||
docId: "team/team",
|
docId: 'team/team',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: 'doc',
|
||||||
label: "Wall of love",
|
label: 'Wall of love',
|
||||||
docId: "wall-of-love",
|
docId: 'wall-of-love',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "docSidebar",
|
type: 'docSidebar',
|
||||||
sidebarId: "productSidebar",
|
sidebarId: 'productSidebar',
|
||||||
positionL: "left",
|
positionL: 'left',
|
||||||
label: "Product",
|
label: 'Product',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "docSidebar",
|
type: 'docSidebar',
|
||||||
sidebarId: "ecosystemSidebar",
|
sidebarId: 'ecosystemSidebar',
|
||||||
position: "left",
|
position: 'left',
|
||||||
label: "Ecosystem",
|
label: 'Ecosystem',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// type: "docSidebar",
|
// type: "docSidebar",
|
||||||
@ -323,29 +346,29 @@ const config = {
|
|||||||
// },
|
// },
|
||||||
// Navbar right
|
// Navbar right
|
||||||
{
|
{
|
||||||
type: "dropdown",
|
type: 'dropdown',
|
||||||
label: "Docs",
|
label: 'Docs',
|
||||||
to: "docs",
|
to: 'docs',
|
||||||
position: "right",
|
position: 'right',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: "docSidebar",
|
type: 'docSidebar',
|
||||||
sidebarId: "guidesSidebar",
|
sidebarId: 'guidesSidebar',
|
||||||
label: "Guides",
|
label: 'Guides',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "docSidebar",
|
type: 'docSidebar',
|
||||||
sidebarId: "developerSidebar",
|
sidebarId: 'developerSidebar',
|
||||||
label: "Developer",
|
label: 'Developer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/api-reference",
|
to: '/api-reference',
|
||||||
label: "API Reference",
|
label: 'API Reference',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "docSidebar",
|
type: 'docSidebar',
|
||||||
sidebarId: "releasesSidebar",
|
sidebarId: 'releasesSidebar',
|
||||||
label: "Changelog",
|
label: 'Changelog',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// type: "docSidebar",
|
// type: "docSidebar",
|
||||||
@ -355,9 +378,9 @@ const config = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "blog",
|
to: 'blog',
|
||||||
label: "Blog",
|
label: 'Blog',
|
||||||
position: "right",
|
position: 'right',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -365,22 +388,22 @@ const config = {
|
|||||||
theme: darkCodeTheme,
|
theme: darkCodeTheme,
|
||||||
darkTheme: darkCodeTheme,
|
darkTheme: darkCodeTheme,
|
||||||
additionalLanguages: [
|
additionalLanguages: [
|
||||||
"python",
|
'python',
|
||||||
"powershell",
|
'powershell',
|
||||||
"bash",
|
'bash',
|
||||||
"json",
|
'json',
|
||||||
"javascript",
|
'javascript',
|
||||||
"jsx",
|
'jsx',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
colorMode: {
|
colorMode: {
|
||||||
defaultMode: "light",
|
defaultMode: 'light',
|
||||||
disableSwitch: false,
|
disableSwitch: false,
|
||||||
respectPrefersColorScheme: false,
|
respectPrefersColorScheme: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
themes: ["@docusaurus/theme-live-codeblock", "@docusaurus/theme-mermaid"],
|
themes: ['@docusaurus/theme-live-codeblock', '@docusaurus/theme-mermaid'],
|
||||||
};
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config
|
||||||
|
|||||||
2486
docs/openapi/jan.json
Normal file
2486
docs/openapi/jan.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@
|
|||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"@redocly/cli": "^1.4.1",
|
"@redocly/cli": "^1.4.1",
|
||||||
|
"@scalar/docusaurus": "^0.1.3",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
async function fetchData(siteConfig) {
|
async function fetchData(siteConfig, forceRefresh = false) {
|
||||||
const owner = siteConfig.organizationName;
|
const owner = siteConfig.organizationName;
|
||||||
const repo = siteConfig.projectName;
|
const repo = siteConfig.projectName;
|
||||||
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases`;
|
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases`;
|
||||||
|
|
||||||
const outputDirectory = path.join(__dirname, '../../docs/guides/changelogs');
|
const outputDirectory = path.join(__dirname, '../../docs/releases/changelog');
|
||||||
|
|
||||||
if (!fs.existsSync(outputDirectory)) {
|
if (!fs.existsSync(outputDirectory)) {
|
||||||
fs.mkdirSync(outputDirectory);
|
fs.mkdirSync(outputDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
const categoryFilePath = path.join(outputDirectory, '_category_.json');
|
|
||||||
const cacheFilePath = path.join(outputDirectory, 'cache.json');
|
const cacheFilePath = path.join(outputDirectory, 'cache.json');
|
||||||
|
|
||||||
let cachedData = {};
|
let cachedData = {};
|
||||||
if (fs.existsSync(cacheFilePath)) {
|
if (fs.existsSync(cacheFilePath) && !forceRefresh) {
|
||||||
cachedData = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
|
cachedData = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ async function fetchData(siteConfig) {
|
|||||||
// Fetch releases from GitHub API or load from cache
|
// Fetch releases from GitHub API or load from cache
|
||||||
let releases = [];
|
let releases = [];
|
||||||
try {
|
try {
|
||||||
if (cachedData.releases) {
|
if (cachedData.releases && !forceRefresh) {
|
||||||
console.log('Loading releases from cache...');
|
console.log('Loading releases from cache...');
|
||||||
releases = cachedData.releases;
|
releases = cachedData.releases;
|
||||||
} else {
|
} else {
|
||||||
@ -68,9 +68,47 @@ async function fetchData(siteConfig) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there are new releases
|
||||||
|
const newReleases = releases.filter(release => {
|
||||||
|
const version = release.tag_name;
|
||||||
|
const existingChangelogPath = path.join(outputDirectory, `changelog-${version}.mdx`);
|
||||||
|
return !fs.existsSync(existingChangelogPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there are new releases, update existing changelog files' sidebar positions
|
||||||
|
if (newReleases.length > 0) {
|
||||||
|
console.log(`Updating sidebar positions for ${newReleases.length} new releases...`);
|
||||||
|
const existingChangelogFiles = fs.readdirSync(outputDirectory)
|
||||||
|
.filter(file => file.startsWith('changelog-'));
|
||||||
|
|
||||||
|
existingChangelogFiles.forEach((filename, index) => {
|
||||||
|
const version = filename.substring(10, filename.length - 4);
|
||||||
|
const existingChangelogPath = path.join(outputDirectory, filename);
|
||||||
|
const content = fs.readFileSync(existingChangelogPath, 'utf-8');
|
||||||
|
const sidebarPositionMatch = content.match(/sidebar_position: (\d+)/);
|
||||||
|
let sidebarPosition = index + 1;
|
||||||
|
|
||||||
|
if (sidebarPositionMatch) {
|
||||||
|
sidebarPosition = parseInt(sidebarPositionMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedContent = content.replace(/sidebar_position: (\d+)/, `sidebar_position: ${sidebarPosition}`);
|
||||||
|
fs.writeFileSync(existingChangelogPath, updatedContent, 'utf-8');
|
||||||
|
console.log(`Sidebar position updated for changelog-${version}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Process the GitHub releases data here
|
// Process the GitHub releases data here
|
||||||
for (const release of releases) {
|
for (const release of releases) {
|
||||||
const version = release.tag_name;
|
const version = release.tag_name;
|
||||||
|
|
||||||
|
// Check if the changelog file already exists for the current version
|
||||||
|
const existingChangelogPath = path.join(outputDirectory, `changelog-${version}.mdx`);
|
||||||
|
if (fs.existsSync(existingChangelogPath)) {
|
||||||
|
console.log(`Changelog for version ${version} already exists. Skipping...`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const releaseUrl = release.html_url;
|
const releaseUrl = release.html_url;
|
||||||
const issueNumberMatch = release.body.match(/#(\d+)/);
|
const issueNumberMatch = release.body.match(/#(\d+)/);
|
||||||
const issueNumber = issueNumberMatch ? parseInt(issueNumberMatch[1], 10) : null;
|
const issueNumber = issueNumberMatch ? parseInt(issueNumberMatch[1], 10) : null;
|
||||||
@ -83,7 +121,7 @@ async function fetchData(siteConfig) {
|
|||||||
|
|
||||||
const changes = release.body;
|
const changes = release.body;
|
||||||
|
|
||||||
let markdownContent = `---\nsidebar_position: ${counter}\n---\n# ${version}\n\nFor more details, [GitHub Issues](${releaseUrl})\n\nHighlighted Issue: ${issueLink}\n\n${changes}\n`;
|
let markdownContent = `---\nsidebar_position: ${counter}\nslug: /changelog/changelog-${version}\n---\n# ${version}\n\nFor more details, [GitHub Issues](${releaseUrl})\n\nHighlighted Issue: ${issueLink}\n\n${changes}\n`;
|
||||||
|
|
||||||
// Write to a separate markdown file for each version
|
// Write to a separate markdown file for each version
|
||||||
const outputFilePath = path.join(outputDirectory, `changelog-${version}.mdx`);
|
const outputFilePath = path.join(outputDirectory, `changelog-${version}.mdx`);
|
||||||
@ -93,20 +131,6 @@ async function fetchData(siteConfig) {
|
|||||||
|
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create _category_.json file
|
|
||||||
const categoryContent = {
|
|
||||||
label: 'Changelogs',
|
|
||||||
position: 5,
|
|
||||||
link: {
|
|
||||||
type: 'generated-index',
|
|
||||||
description: 'Changelog for Jan',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(categoryFilePath, JSON.stringify(categoryContent, null, 2), 'utf-8');
|
|
||||||
|
|
||||||
console.log(`_category_.json has been created at: ${categoryFilePath}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = fetchData;
|
module.exports = fetchData;
|
||||||
|
|||||||
@ -24,7 +24,7 @@ module.exports = function (context, options) {
|
|||||||
|
|
||||||
async onPostBuild() {
|
async onPostBuild() {
|
||||||
// If you need additional actions after the build, you can include them here.
|
// If you need additional actions after the build, you can include them here.
|
||||||
await fetchData(siteConfig);
|
await fetchData(siteConfig, true);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
154
docs/sidebars.js
154
docs/sidebars.js
@ -52,6 +52,13 @@ const sidebars = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
"acknowledgements",
|
"acknowledgements",
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "FAQ",
|
||||||
|
link: { type: "doc", id: "about/faq" },
|
||||||
|
items:
|
||||||
|
[],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
productSidebar: [
|
productSidebar: [
|
||||||
{
|
{
|
||||||
@ -150,10 +157,153 @@ const sidebars = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
// guidesSidebar: [
|
||||||
|
// {
|
||||||
|
// type: "autogenerated",
|
||||||
|
// dirName: "guides",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
guidesSidebar: [
|
guidesSidebar: [
|
||||||
{
|
{
|
||||||
type: "autogenerated",
|
type: "category",
|
||||||
dirName: "guides",
|
label: "Get Started",
|
||||||
|
collapsible: false,
|
||||||
|
className: "head_Menu",
|
||||||
|
items: [
|
||||||
|
"guides/quickstart",
|
||||||
|
"guides/install",
|
||||||
|
"guides/start-server",
|
||||||
|
"guides/models-list"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Guides",
|
||||||
|
collapsible: false,
|
||||||
|
className: "head_Menu",
|
||||||
|
items: [
|
||||||
|
"guides/best-practices",
|
||||||
|
"guides/thread",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Advanced Features",
|
||||||
|
collapsible: false,
|
||||||
|
className: "head_Menu",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Advanced Settings",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/advanced-settings/advanced-settings",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/advanced-settings/http-proxy",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Advanced Model Setup",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/models/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/models/customize-engine",
|
||||||
|
"guides/models/import-models",
|
||||||
|
"guides/models/integrate-remote",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Inference Providers",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/providers/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/providers/llama-cpp",
|
||||||
|
"guides/providers/tensorrt-llm",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Extensions",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/extensions/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/extensions/import-ext",
|
||||||
|
"guides/extensions/setup-ext",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Integrations",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/integration/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/integration/azure",
|
||||||
|
"guides/integration/discord",
|
||||||
|
"guides/integration/groq",
|
||||||
|
"guides/integration/lmstudio",
|
||||||
|
"guides/integration/mistral",
|
||||||
|
"guides/integration/ollama",
|
||||||
|
"guides/integration/openinterpreter",
|
||||||
|
"guides/integration/openrouter",
|
||||||
|
"guides/integration/raycast",
|
||||||
|
"guides/integration/vscode",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Troubleshooting",
|
||||||
|
collapsible: false,
|
||||||
|
className: "head_Menu",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Error Codes",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/error-codes/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/error-codes/how-to-get-error-logs",
|
||||||
|
"guides/error-codes/permission-denied",
|
||||||
|
"guides/error-codes/something-amiss",
|
||||||
|
"guides/error-codes/undefined-issue",
|
||||||
|
"guides/error-codes/unexpected-token",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Common Error",
|
||||||
|
className: "head_SubMenu",
|
||||||
|
link: {
|
||||||
|
type: 'doc',
|
||||||
|
id: "guides/common-error/README",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
"guides/common-error/broken-build",
|
||||||
|
"guides/common-error/not-using-gpu",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"guides/faq"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
developerSidebar: [
|
developerSidebar: [
|
||||||
|
|||||||
@ -1,8 +1,56 @@
|
|||||||
|
/* Hide descriptions in cards without a description */
|
||||||
.DocCardList--no-description .card p {
|
.DocCardList--no-description .card p {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For dark theme */
|
/* For dark theme */
|
||||||
[data-theme="dark"] .DocSearch {
|
[data-theme='dark'] .DocSearch {
|
||||||
--docsearch-hit-active-color: #090a11; /* Keep the color unchanged */
|
--docsearch-hit-active-color: #090a11; /* Keep the color unchanged */
|
||||||
}
|
}
|
||||||
|
/* Sidebar styles based on Docusaurus light theme */
|
||||||
|
[data-theme='light'] .head_Menu div {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
margin-left: 0.7rem;
|
||||||
|
font-size: larger;
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='light'] .head_Menu li {
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='light'] .head_SubMenu div {
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
margin-left: 0rem;
|
||||||
|
font-size: medium;
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode styles based on Docusaurus dark theme */
|
||||||
|
[data-theme='dark'] .head_Menu div {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
margin-left: 0.7rem;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .head_Menu li {
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .head_SubMenu div {
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
color: var(--ifm-font-color-base);
|
||||||
|
margin-left: 0rem;
|
||||||
|
font-size: medium;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
h1,
|
h1,
|
||||||
.h1 {
|
.h1 {
|
||||||
line-height: 48px;
|
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
}
|
}
|
||||||
@ -8,35 +7,24 @@ h2,
|
|||||||
.h2 {
|
.h2 {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
line-height: 40px;
|
|
||||||
}
|
}
|
||||||
h3,
|
h3,
|
||||||
.h3 {
|
.h3 {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
line-height: 40px;
|
|
||||||
}
|
}
|
||||||
h4,
|
h4,
|
||||||
.h4 {
|
.h4 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
line-height: 32px;
|
|
||||||
}
|
}
|
||||||
h5,
|
h5,
|
||||||
.h5 {
|
.h5 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
line-height: 28px;
|
|
||||||
}
|
}
|
||||||
h6,
|
h6,
|
||||||
.h6 {
|
.h6 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@apply font-bold text-black dark:text-white;
|
@apply font-bold text-black dark:text-white;
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
.paragraph {
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
.theme-doc-markdown {
|
.theme-doc-markdown,
|
||||||
|
.markdown {
|
||||||
a,
|
a,
|
||||||
p,
|
p,
|
||||||
span,
|
span,
|
||||||
li {
|
li {
|
||||||
@apply leading-loose;
|
@apply leading-relaxed;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
@apply text-blue-600 dark:text-blue-400;
|
@apply text-blue-600 dark:text-blue-400;
|
||||||
@ -18,7 +19,6 @@
|
|||||||
ol {
|
ol {
|
||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
li {
|
li {
|
||||||
@apply leading-loose;
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -34,14 +34,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2 {
|
h2,
|
||||||
@apply mb-3;
|
h3 {
|
||||||
|
@apply mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-item {
|
.task-list-item {
|
||||||
|
|||||||
1192
docs/yarn.lock
1192
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,20 @@
|
|||||||
import { Handler, RequestHandler } from '@janhq/core/node'
|
import { Handler, RequestHandler } from '@janhq/core/node'
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import { WindowManager } from '../managers/window'
|
import { windowManager } from '../managers/window'
|
||||||
|
|
||||||
export function injectHandler() {
|
export function injectHandler() {
|
||||||
const ipcWrapper: Handler = (
|
const ipcWrapper: Handler = (
|
||||||
route: string,
|
route: string,
|
||||||
listener: (...args: any[]) => any
|
listener: (...args: any[]) => any
|
||||||
) => {
|
) =>
|
||||||
return ipcMain.handle(route, async (event, ...args: any[]) => {
|
ipcMain.handle(route, async (_event, ...args: any[]) => {
|
||||||
return listener(...args)
|
return listener(...args)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const handler = new RequestHandler(
|
const handler = new RequestHandler(
|
||||||
ipcWrapper,
|
ipcWrapper,
|
||||||
(channel: string, args: any) => {
|
(channel: string, args: any) =>
|
||||||
return WindowManager.instance.currentWindow?.webContents.send(
|
windowManager.mainWindow?.webContents.send(channel, args)
|
||||||
channel,
|
|
||||||
args
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
handler.handle()
|
handler.handle()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { app, ipcMain, dialog, shell } from 'electron'
|
import { app, ipcMain, dialog, shell } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { WindowManager } from '../managers/window'
|
import { windowManager } from '../managers/window'
|
||||||
import {
|
import {
|
||||||
ModuleManager,
|
ModuleManager,
|
||||||
getJanDataFolderPath,
|
getJanDataFolderPath,
|
||||||
getJanExtensionsPath,
|
getJanExtensionsPath,
|
||||||
init,
|
init,
|
||||||
} from '@janhq/core/node'
|
} from '@janhq/core/node'
|
||||||
import { NativeRoute } from '@janhq/core'
|
import { AppEvent, NativeRoute } from '@janhq/core'
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
export function handleAppIPCs() {
|
||||||
/**
|
/**
|
||||||
@ -62,12 +62,12 @@ export function handleAppIPCs() {
|
|||||||
// Path to install extension to
|
// Path to install extension to
|
||||||
extensionsPath: getJanExtensionsPath(),
|
extensionsPath: getJanExtensionsPath(),
|
||||||
})
|
})
|
||||||
WindowManager.instance.currentWindow?.reload()
|
windowManager.mainWindow?.reload()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(NativeRoute.selectDirectory, async () => {
|
ipcMain.handle(NativeRoute.selectDirectory, async () => {
|
||||||
const mainWindow = WindowManager.instance.currentWindow
|
const mainWindow = windowManager.mainWindow
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
console.error('No main window found')
|
console.error('No main window found')
|
||||||
return
|
return
|
||||||
@ -85,7 +85,7 @@ export function handleAppIPCs() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(NativeRoute.selectModelFiles, async () => {
|
ipcMain.handle(NativeRoute.selectModelFiles, async () => {
|
||||||
const mainWindow = WindowManager.instance.currentWindow
|
const mainWindow = windowManager.mainWindow
|
||||||
if (!mainWindow) {
|
if (!mainWindow) {
|
||||||
console.error('No main window found')
|
console.error('No main window found')
|
||||||
return
|
return
|
||||||
@ -101,4 +101,35 @@ export function handleAppIPCs() {
|
|||||||
|
|
||||||
return filePaths
|
return filePaths
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
NativeRoute.hideQuickAskWindow,
|
||||||
|
async (): Promise<void> => windowManager.hideQuickAskWindow()
|
||||||
|
)
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
NativeRoute.sendQuickAskInput,
|
||||||
|
async (_event, input: string): Promise<void> => {
|
||||||
|
windowManager.mainWindow?.webContents.send(
|
||||||
|
AppEvent.onUserSubmitQuickAsk,
|
||||||
|
input
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
NativeRoute.hideMainWindow,
|
||||||
|
async (): Promise<void> => windowManager.hideMainWindow()
|
||||||
|
)
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
NativeRoute.showMainWindow,
|
||||||
|
async (): Promise<void> => windowManager.showMainWindow()
|
||||||
|
)
|
||||||
|
|
||||||
|
ipcMain.handle(
|
||||||
|
NativeRoute.quickAskSizeUpdated,
|
||||||
|
async (_event, heightOffset: number): Promise<void> =>
|
||||||
|
windowManager.expandQuickAskWindow(heightOffset)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import { app, dialog } from 'electron'
|
import { app, dialog } from 'electron'
|
||||||
import { WindowManager } from './../managers/window'
|
import { windowManager } from './../managers/window'
|
||||||
import { autoUpdater } from 'electron-updater'
|
import {
|
||||||
|
ProgressInfo,
|
||||||
|
UpdateDownloadedEvent,
|
||||||
|
UpdateInfo,
|
||||||
|
autoUpdater,
|
||||||
|
} from 'electron-updater'
|
||||||
import { AppEvent } from '@janhq/core'
|
import { AppEvent } from '@janhq/core'
|
||||||
|
import { trayManager } from '../managers/tray'
|
||||||
|
|
||||||
export let waitingToInstallVersion: string | undefined = undefined
|
export let waitingToInstallVersion: string | undefined = undefined
|
||||||
|
|
||||||
@ -11,18 +17,19 @@ export function handleAppUpdates() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
/* New Update Available */
|
/* New Update Available */
|
||||||
autoUpdater.on('update-available', async (_info: any) => {
|
autoUpdater.on('update-available', async (_info: UpdateInfo) => {
|
||||||
const action = await dialog.showMessageBox({
|
const action = await dialog.showMessageBox({
|
||||||
title: 'Update Available',
|
title: 'Update Available',
|
||||||
message: 'Would you like to download and install it now?',
|
message: 'Would you like to download and install it now?',
|
||||||
buttons: ['Download', 'Later'],
|
buttons: ['Download', 'Later'],
|
||||||
})
|
})
|
||||||
|
trayManager.destroyCurrentTray()
|
||||||
if (action.response === 0) await autoUpdater.downloadUpdate()
|
if (action.response === 0) await autoUpdater.downloadUpdate()
|
||||||
})
|
})
|
||||||
|
|
||||||
/* App Update Completion Message */
|
/* App Update Completion Message */
|
||||||
autoUpdater.on('update-downloaded', async (_info: any) => {
|
autoUpdater.on('update-downloaded', async (_info: UpdateDownloadedEvent) => {
|
||||||
WindowManager.instance.currentWindow?.webContents.send(
|
windowManager.mainWindow?.webContents.send(
|
||||||
AppEvent.onAppUpdateDownloadSuccess,
|
AppEvent.onAppUpdateDownloadSuccess,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
@ -37,23 +44,24 @@ export function handleAppUpdates() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
/* App Update Error */
|
/* App Update Error */
|
||||||
autoUpdater.on('error', (info: any) => {
|
autoUpdater.on('error', (info: Error) => {
|
||||||
WindowManager.instance.currentWindow?.webContents.send(
|
windowManager.mainWindow?.webContents.send(
|
||||||
AppEvent.onAppUpdateDownloadError,
|
AppEvent.onAppUpdateDownloadError,
|
||||||
{ failedToInstallVersion: waitingToInstallVersion, info }
|
{ failedToInstallVersion: waitingToInstallVersion, info }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/* App Update Progress */
|
/* App Update Progress */
|
||||||
autoUpdater.on('download-progress', (progress: any) => {
|
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
|
||||||
console.debug('app update progress: ', progress.percent)
|
console.debug('app update progress: ', progress.percent)
|
||||||
WindowManager.instance.currentWindow?.webContents.send(
|
windowManager.mainWindow?.webContents.send(
|
||||||
AppEvent.onAppUpdateDownloadUpdate,
|
AppEvent.onAppUpdateDownloadUpdate,
|
||||||
{
|
{
|
||||||
percent: progress.percent,
|
...progress,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
autoUpdater.autoDownload = false
|
autoUpdater.autoDownload = false
|
||||||
autoUpdater.autoInstallOnAppQuit = true
|
autoUpdater.autoInstallOnAppQuit = true
|
||||||
if (process.env.CI !== 'e2e') {
|
if (process.env.CI !== 'e2e') {
|
||||||
|
|||||||
BIN
electron/icons/512x512.png
Normal file
BIN
electron/icons/512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
electron/icons/icon-tray.png
Normal file
BIN
electron/icons/icon-tray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
electron/icons/icon-tray@2x.png
Normal file
BIN
electron/icons/icon-tray@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
113
electron/main.ts
113
electron/main.ts
@ -1,10 +1,11 @@
|
|||||||
import { app, BrowserWindow, shell } from 'electron'
|
import { app, BrowserWindow, Tray } from 'electron'
|
||||||
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
/**
|
/**
|
||||||
* Managers
|
* Managers
|
||||||
**/
|
**/
|
||||||
import { WindowManager } from './managers/window'
|
import { windowManager } from './managers/window'
|
||||||
import { log } from '@janhq/core/node'
|
import { getAppConfigurations, log } from '@janhq/core/node'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPC Handlers
|
* IPC Handlers
|
||||||
@ -25,8 +26,29 @@ import { setupCore } from './utils/setup'
|
|||||||
import { setupReactDevTool } from './utils/dev'
|
import { setupReactDevTool } from './utils/dev'
|
||||||
import { cleanLogs } from './utils/log'
|
import { cleanLogs } from './utils/log'
|
||||||
|
|
||||||
|
import { registerShortcut } from './utils/selectedText'
|
||||||
|
import { trayManager } from './managers/tray'
|
||||||
|
|
||||||
|
const preloadPath = join(__dirname, 'preload.js')
|
||||||
|
const rendererPath = join(__dirname, '..', 'renderer')
|
||||||
|
const quickAskPath = join(rendererPath, 'search.html')
|
||||||
|
const mainPath = join(rendererPath, 'index.html')
|
||||||
|
|
||||||
|
const mainUrl = 'http://localhost:3000'
|
||||||
|
const quickAskUrl = `${mainUrl}/search`
|
||||||
|
|
||||||
|
const quickAskHotKey = 'CommandOrControl+J'
|
||||||
|
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
|
||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
|
.then(() => {
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit()
|
||||||
|
throw new Error('Another instance of the app is already running')
|
||||||
|
}
|
||||||
|
})
|
||||||
.then(setupReactDevTool)
|
.then(setupReactDevTool)
|
||||||
.then(setupCore)
|
.then(setupCore)
|
||||||
.then(createUserSpace)
|
.then(createUserSpace)
|
||||||
@ -35,55 +57,80 @@ app
|
|||||||
.then(setupMenu)
|
.then(setupMenu)
|
||||||
.then(handleIPCs)
|
.then(handleIPCs)
|
||||||
.then(handleAppUpdates)
|
.then(handleAppUpdates)
|
||||||
|
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
|
||||||
.then(createMainWindow)
|
.then(createMainWindow)
|
||||||
|
.then(() => {
|
||||||
|
if (!app.isPackaged) {
|
||||||
|
windowManager.mainWindow?.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => process.env.CI !== 'e2e' && trayManager.createSystemTray())
|
||||||
|
.then(() => {
|
||||||
|
log(`Version: ${app.getVersion()}`)
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (!BrowserWindow.getAllWindows().length) {
|
if (!BrowserWindow.getAllWindows().length) {
|
||||||
createMainWindow()
|
createMainWindow()
|
||||||
|
} else {
|
||||||
|
windowManager.showMainWindow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(() => cleanLogs())
|
.then(() => cleanLogs())
|
||||||
|
|
||||||
app.once('window-all-closed', () => {
|
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
||||||
cleanUpAndQuit()
|
windowManager.showMainWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('ready', () => {
|
||||||
|
registerGlobalShortcuts()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('before-quit', function (evt) {
|
||||||
|
trayManager.destroyCurrentTray()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.once('quit', () => {
|
app.once('quit', () => {
|
||||||
cleanUpAndQuit()
|
cleanUpAndQuit()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.once('window-all-closed', () => {
|
||||||
|
// Feature Toggle for Quick Ask
|
||||||
|
if (getAppConfigurations().quick_ask) return
|
||||||
|
cleanUpAndQuit()
|
||||||
|
})
|
||||||
|
|
||||||
|
function createQuickAskWindow() {
|
||||||
|
// Feature Toggle for Quick Ask
|
||||||
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
const startUrl = app.isPackaged ? `file://${quickAskPath}` : quickAskUrl
|
||||||
|
windowManager.createQuickAskWindow(preloadPath, startUrl)
|
||||||
|
}
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
/* Create main window */
|
const startUrl = app.isPackaged ? `file://${mainPath}` : mainUrl
|
||||||
const mainWindow = WindowManager.instance.createWindow({
|
windowManager.createMainWindow(preloadPath, startUrl)
|
||||||
webPreferences: {
|
}
|
||||||
nodeIntegration: true,
|
|
||||||
preload: join(__dirname, 'preload.js'),
|
function registerGlobalShortcuts() {
|
||||||
webSecurity: false,
|
const ret = registerShortcut(quickAskHotKey, (selectedText: string) => {
|
||||||
},
|
// Feature Toggle for Quick Ask
|
||||||
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
|
||||||
|
if (!windowManager.isQuickAskWindowVisible()) {
|
||||||
|
windowManager.showQuickAskWindow()
|
||||||
|
windowManager.sendQuickAskSelectedText(selectedText)
|
||||||
|
} else {
|
||||||
|
windowManager.hideQuickAskWindow()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const startURL = app.isPackaged
|
if (!ret) {
|
||||||
? `file://${join(__dirname, '..', 'renderer', 'index.html')}`
|
console.error('Global shortcut registration failed')
|
||||||
: 'http://localhost:3000'
|
} else {
|
||||||
|
console.log('Global shortcut registered successfully')
|
||||||
/* Load frontend app to the window */
|
}
|
||||||
mainWindow.loadURL(startURL)
|
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => mainWindow?.show())
|
|
||||||
mainWindow.on('closed', () => {
|
|
||||||
if (process.platform !== 'darwin') app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Open external links in the default browser */
|
|
||||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
||||||
shell.openExternal(url)
|
|
||||||
return { action: 'deny' }
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Enable dev tools for development */
|
|
||||||
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
|
||||||
log(`Version: ${app.getVersion()}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
16
electron/managers/mainWindowConfig.ts
Normal file
16
electron/managers/mainWindowConfig.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const DEFAULT_WIDTH = 1200
|
||||||
|
const DEFAULT_HEIGHT = 800
|
||||||
|
|
||||||
|
export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||||
|
width: DEFAULT_WIDTH,
|
||||||
|
minWidth: DEFAULT_WIDTH,
|
||||||
|
height: DEFAULT_HEIGHT,
|
||||||
|
skipTaskbar: false,
|
||||||
|
show: true,
|
||||||
|
trafficLightPosition: {
|
||||||
|
x: 10,
|
||||||
|
y: 15,
|
||||||
|
},
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
|
vibrancy: 'sidebar',
|
||||||
|
}
|
||||||
22
electron/managers/quickAskWindowConfig.ts
Normal file
22
electron/managers/quickAskWindowConfig.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const DEFAULT_WIDTH = 556
|
||||||
|
|
||||||
|
const DEFAULT_HEIGHT = 60
|
||||||
|
|
||||||
|
export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||||
|
width: DEFAULT_WIDTH,
|
||||||
|
height: DEFAULT_HEIGHT,
|
||||||
|
skipTaskbar: true,
|
||||||
|
acceptFirstMouse: true,
|
||||||
|
hasShadow: true,
|
||||||
|
alwaysOnTop: true,
|
||||||
|
show: false,
|
||||||
|
fullscreenable: false,
|
||||||
|
resizable: false,
|
||||||
|
center: true,
|
||||||
|
movable: false,
|
||||||
|
maximizable: false,
|
||||||
|
focusable: true,
|
||||||
|
transparent: true,
|
||||||
|
frame: false,
|
||||||
|
type: 'panel',
|
||||||
|
}
|
||||||
51
electron/managers/tray.ts
Normal file
51
electron/managers/tray.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { join } from 'path'
|
||||||
|
import { Tray, app, Menu } from 'electron'
|
||||||
|
import { windowManager } from '../managers/window'
|
||||||
|
import { getAppConfigurations } from '@janhq/core/node'
|
||||||
|
|
||||||
|
class TrayManager {
|
||||||
|
currentTray: Tray | undefined
|
||||||
|
|
||||||
|
createSystemTray = () => {
|
||||||
|
// Feature Toggle for Quick Ask
|
||||||
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
|
||||||
|
if (this.currentTray) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png')
|
||||||
|
const tray = new Tray(iconPath)
|
||||||
|
tray.setToolTip(app.getName())
|
||||||
|
|
||||||
|
tray.on('click', () => {
|
||||||
|
windowManager.showQuickAskWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add context menu for windows only
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{
|
||||||
|
label: 'Open Jan',
|
||||||
|
type: 'normal',
|
||||||
|
click: () => windowManager.showMainWindow(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Open Quick Ask',
|
||||||
|
type: 'normal',
|
||||||
|
click: () => windowManager.showQuickAskWindow(),
|
||||||
|
},
|
||||||
|
{ label: 'Quit', type: 'normal', click: () => app.quit() },
|
||||||
|
])
|
||||||
|
|
||||||
|
tray.setContextMenu(contextMenu)
|
||||||
|
}
|
||||||
|
this.currentTray = tray
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyCurrentTray() {
|
||||||
|
this.currentTray?.destroy()
|
||||||
|
this.currentTray = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const trayManager = new TrayManager()
|
||||||
@ -1,37 +1,123 @@
|
|||||||
import { BrowserWindow } from 'electron'
|
import { BrowserWindow, app, shell } from 'electron'
|
||||||
|
import { quickAskWindowConfig } from './quickAskWindowConfig'
|
||||||
|
import { AppEvent } from '@janhq/core'
|
||||||
|
import { mainWindowConfig } from './mainWindowConfig'
|
||||||
|
import { getAppConfigurations } from '@janhq/core/node'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the current window instance.
|
* Manages the current window instance.
|
||||||
*/
|
*/
|
||||||
export class WindowManager {
|
// TODO: refactor this
|
||||||
public static instance: WindowManager = new WindowManager()
|
let isAppQuitting = false
|
||||||
public currentWindow?: BrowserWindow
|
class WindowManager {
|
||||||
|
public mainWindow?: BrowserWindow
|
||||||
constructor() {
|
private _quickAskWindow: BrowserWindow | undefined = undefined
|
||||||
if (WindowManager.instance) {
|
private _quickAskWindowVisible = false
|
||||||
return WindowManager.instance
|
private _mainWindowVisible = false
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new window instance.
|
* Creates a new window instance.
|
||||||
* @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with.
|
* @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with.
|
||||||
* @returns The created window instance.
|
* @returns The created window instance.
|
||||||
*/
|
*/
|
||||||
createWindow(options?: Electron.BrowserWindowConstructorOptions | undefined) {
|
createMainWindow(preloadPath: string, startUrl: string) {
|
||||||
this.currentWindow = new BrowserWindow({
|
this.mainWindow = new BrowserWindow({
|
||||||
width: 1200,
|
...mainWindowConfig,
|
||||||
minWidth: 1200,
|
webPreferences: {
|
||||||
height: 800,
|
nodeIntegration: true,
|
||||||
show: true,
|
preload: preloadPath,
|
||||||
trafficLightPosition: {
|
webSecurity: false,
|
||||||
x: 10,
|
|
||||||
y: 15,
|
|
||||||
},
|
},
|
||||||
titleBarStyle: 'hiddenInset',
|
|
||||||
vibrancy: 'sidebar',
|
|
||||||
...options,
|
|
||||||
})
|
})
|
||||||
return this.currentWindow
|
|
||||||
|
/* Load frontend app to the window */
|
||||||
|
this.mainWindow.loadURL(startUrl)
|
||||||
|
|
||||||
|
/* Open external links in the default browser */
|
||||||
|
this.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
|
shell.openExternal(url)
|
||||||
|
return { action: 'deny' }
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('before-quit', function () {
|
||||||
|
isAppQuitting = true
|
||||||
|
})
|
||||||
|
|
||||||
|
windowManager.mainWindow?.on('close', function (evt) {
|
||||||
|
// Feature Toggle for Quick Ask
|
||||||
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
|
||||||
|
if (!isAppQuitting) {
|
||||||
|
evt.preventDefault()
|
||||||
|
windowManager.hideMainWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createQuickAskWindow(preloadPath: string, startUrl: string): void {
|
||||||
|
this._quickAskWindow = new BrowserWindow({
|
||||||
|
...quickAskWindowConfig,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
preload: preloadPath,
|
||||||
|
webSecurity: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this._quickAskWindow.loadURL(startUrl)
|
||||||
|
this._quickAskWindow.on('blur', () => {
|
||||||
|
this.hideQuickAskWindow()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
isMainWindowVisible(): boolean {
|
||||||
|
return this._mainWindowVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
hideMainWindow(): void {
|
||||||
|
this.mainWindow?.hide()
|
||||||
|
this._mainWindowVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
showMainWindow(): void {
|
||||||
|
this.mainWindow?.show()
|
||||||
|
this._mainWindowVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
hideQuickAskWindow(): void {
|
||||||
|
this._quickAskWindow?.hide()
|
||||||
|
this._quickAskWindowVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuickAskWindow(): void {
|
||||||
|
this._quickAskWindow?.show()
|
||||||
|
this._quickAskWindowVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuickAskWindowVisible(): boolean {
|
||||||
|
return this._quickAskWindowVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
expandQuickAskWindow(heightOffset: number): void {
|
||||||
|
const width = quickAskWindowConfig.width!
|
||||||
|
const height = quickAskWindowConfig.height! + heightOffset
|
||||||
|
this._quickAskWindow?.setMinimumSize(width, height)
|
||||||
|
this._quickAskWindow?.setSize(width, height, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendQuickAskSelectedText(selectedText: string): void {
|
||||||
|
this._quickAskWindow?.webContents.send(
|
||||||
|
AppEvent.onSelectedText,
|
||||||
|
selectedText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanUp(): void {
|
||||||
|
this.mainWindow?.destroy()
|
||||||
|
this._quickAskWindow?.destroy()
|
||||||
|
this._quickAskWindowVisible = false
|
||||||
|
this._mainWindowVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const windowManager = new WindowManager()
|
||||||
|
|||||||
@ -16,13 +16,15 @@
|
|||||||
"pre-install",
|
"pre-install",
|
||||||
"models/**/*",
|
"models/**/*",
|
||||||
"docs/**/*",
|
"docs/**/*",
|
||||||
"scripts/**/*"
|
"scripts/**/*",
|
||||||
|
"icons/**/*"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"pre-install",
|
"pre-install",
|
||||||
"models",
|
"models",
|
||||||
"docs",
|
"docs",
|
||||||
"scripts"
|
"scripts",
|
||||||
|
"icons"
|
||||||
],
|
],
|
||||||
"publish": [
|
"publish": [
|
||||||
{
|
{
|
||||||
@ -81,7 +83,6 @@
|
|||||||
"@janhq/core": "link:./core",
|
"@janhq/core": "link:./core",
|
||||||
"@janhq/server": "link:./server",
|
"@janhq/server": "link:./server",
|
||||||
"@npmcli/arborist": "^7.1.0",
|
"@npmcli/arborist": "^7.1.0",
|
||||||
"@uiball/loaders": "^1.3.0",
|
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "^6.1.7",
|
"electron-updater": "^6.1.7",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
@ -90,7 +91,7 @@
|
|||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-progress": "^3.0.0",
|
"request-progress": "^3.0.0",
|
||||||
"ulid": "^2.3.0",
|
"ulid": "^2.3.0",
|
||||||
"use-debounce": "^9.0.4"
|
"@nut-tree/nut-js": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron/notarize": "^2.1.0",
|
"@electron/notarize": "^2.1.0",
|
||||||
@ -101,13 +102,15 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
"@typescript-eslint/parser": "^6.7.3",
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"electron": "28.0.0",
|
"electron": "28.0.0",
|
||||||
"electron-builder": "^24.9.1",
|
"electron-builder": "^24.13.3",
|
||||||
|
"electron-builder-squirrel-windows": "^24.13.3",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-playwright-helpers": "^1.6.0",
|
"electron-playwright-helpers": "^1.6.0",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint": "8.57.0",
|
||||||
|
"eslint-plugin-react": "^7.34.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"run-script-os": "^1.1.6",
|
"run-script-os": "^1.1.6",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"installConfig": {
|
"installConfig": {
|
||||||
"hoistingLimits": "workspaces"
|
"hoistingLimits": "workspaces"
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { ModuleManager } from '@janhq/core/node'
|
import { ModuleManager } from '@janhq/core/node'
|
||||||
import { WindowManager } from './../managers/window'
|
import { windowManager } from './../managers/window'
|
||||||
import { dispose } from './disposable'
|
import { dispose } from './disposable'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
export function cleanUpAndQuit() {
|
export function cleanUpAndQuit() {
|
||||||
if (!ModuleManager.instance.cleaningResource) {
|
if (!ModuleManager.instance.cleaningResource) {
|
||||||
ModuleManager.instance.cleaningResource = true
|
ModuleManager.instance.cleaningResource = true
|
||||||
WindowManager.instance.currentWindow?.destroy()
|
windowManager.cleanUp()
|
||||||
dispose(ModuleManager.instance.requiredModules)
|
dispose(ModuleManager.instance.requiredModules)
|
||||||
ModuleManager.instance.clearImportedModules()
|
ModuleManager.instance.clearImportedModules()
|
||||||
app.quit()
|
app.quit()
|
||||||
|
|||||||
44
electron/utils/selectedText.ts
Normal file
44
electron/utils/selectedText.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { clipboard, globalShortcut } from 'electron'
|
||||||
|
import { keyboard, Key } from '@nut-tree/nut-js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets selected text by synthesizing the keyboard shortcut
|
||||||
|
* "CommandOrControl+c" then reading text from the clipboard
|
||||||
|
*/
|
||||||
|
export const getSelectedText = async () => {
|
||||||
|
const currentClipboardContent = clipboard.readText() // preserve clipboard content
|
||||||
|
clipboard.clear()
|
||||||
|
const hotkeys: Key[] = [
|
||||||
|
process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl,
|
||||||
|
Key.C,
|
||||||
|
]
|
||||||
|
await keyboard.pressKey(...hotkeys)
|
||||||
|
await keyboard.releaseKey(...hotkeys)
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200)) // add a delay before checking clipboard
|
||||||
|
const selectedText = clipboard.readText()
|
||||||
|
clipboard.writeText(currentClipboardContent)
|
||||||
|
return selectedText
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a global shortcut of `accelerator`. The `callback` is called
|
||||||
|
* with the selected text when the registered shorcut is pressed by the user
|
||||||
|
*
|
||||||
|
* Returns `true` if the shortcut was registered successfully
|
||||||
|
*/
|
||||||
|
export const registerShortcut = (
|
||||||
|
accelerator: Electron.Accelerator,
|
||||||
|
callback: (selectedText: string) => void
|
||||||
|
) => {
|
||||||
|
return globalShortcut.register(accelerator, async () => {
|
||||||
|
callback(await getSelectedText())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a global shortcut of `accelerator` and
|
||||||
|
* is equivalent to electron.globalShortcut.unregister
|
||||||
|
*/
|
||||||
|
export const unregisterShortcut = (accelerator: Electron.Accelerator) => {
|
||||||
|
globalShortcut.unregister(accelerator)
|
||||||
|
}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"rollup-plugin-define": "^1.0.1",
|
"rollup-plugin-define": "^1.0.1",
|
||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||||
"rollup-plugin-typescript2": "^0.36.0",
|
"rollup-plugin-typescript2": "^0.36.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.3.3",
|
||||||
"run-script-os": "^1.1.6"
|
"run-script-os": "^1.1.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
events,
|
events,
|
||||||
DownloadEvent,
|
DownloadEvent,
|
||||||
log,
|
log,
|
||||||
|
DownloadRequest,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { ggufMetadata } from 'hyllama'
|
import { ggufMetadata } from 'hyllama'
|
||||||
|
|
||||||
@ -148,7 +149,11 @@ export default class JanHuggingFaceExtension extends HuggingFaceExtension {
|
|||||||
|
|
||||||
if (this.interrupted) return
|
if (this.interrupted) return
|
||||||
if (!(await fs.existsSync(localPath))) {
|
if (!(await fs.existsSync(localPath))) {
|
||||||
downloadFile(url, localPath, network)
|
const downloadRequest: DownloadRequest = {
|
||||||
|
url,
|
||||||
|
localPath,
|
||||||
|
}
|
||||||
|
downloadFile(downloadRequest, network)
|
||||||
filePaths.push(filePath)
|
filePaths.push(filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
@echo off
|
@echo off
|
||||||
set /p NITRO_VERSION=<./bin/version.txt
|
set /p NITRO_VERSION=<./bin/version.txt
|
||||||
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/win-vulkan && .\node_modules\.bin\download https://delta.jan.ai/vulkaninfoSDK.exe -o ./bin
|
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/win-vulkan
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||||
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/linux-vulkan && chmod +x ./bin/linux-vulkan/nitro && download https://delta.jan.ai/vulkaninfo -o ./bin && chmod +x ./bin/vulkaninfo",
|
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/linux-vulkan && chmod +x ./bin/linux-vulkan/nitro",
|
||||||
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
||||||
"downloadnitro:win32": "download.bat",
|
"downloadnitro:win32": "download.bat",
|
||||||
"downloadnitro": "run-script-os",
|
"downloadnitro": "run-script-os",
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||||
"rollup-plugin-typescript2": "^0.36.0",
|
"rollup-plugin-typescript2": "^0.36.0",
|
||||||
"run-script-os": "^1.1.6",
|
"run-script-os": "^1.1.6",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.3.3",
|
||||||
"@types/os-utils": "^0.0.4",
|
"@types/os-utils": "^0.0.4",
|
||||||
"@rollup/plugin-replace": "^5.0.5"
|
"@rollup/plugin-replace": "^5.0.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -108,9 +108,6 @@ export default class JanInferenceNitroExtension extends InferenceExtension {
|
|||||||
events.on(InferenceEvent.OnInferenceStopped, () =>
|
events.on(InferenceEvent.OnInferenceStopped, () =>
|
||||||
this.onInferenceStopped()
|
this.onInferenceStopped()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Attempt to fetch nvidia info
|
|
||||||
await executeOnMain(NODE, 'updateNvidiaInfo', {})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,237 +0,0 @@
|
|||||||
import { writeFileSync, existsSync, readFileSync } from 'fs'
|
|
||||||
import { exec, spawn } from 'child_process'
|
|
||||||
import path from 'path'
|
|
||||||
import { getJanDataFolderPath, log } from '@janhq/core/node'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default GPU settings
|
|
||||||
* TODO: This needs to be refactored to support multiple accelerators
|
|
||||||
**/
|
|
||||||
const DEFALT_SETTINGS = {
|
|
||||||
notify: true,
|
|
||||||
run_mode: 'cpu',
|
|
||||||
nvidia_driver: {
|
|
||||||
exist: false,
|
|
||||||
version: '',
|
|
||||||
},
|
|
||||||
cuda: {
|
|
||||||
exist: false,
|
|
||||||
version: '',
|
|
||||||
},
|
|
||||||
gpus: [],
|
|
||||||
gpu_highest_vram: '',
|
|
||||||
gpus_in_use: [],
|
|
||||||
is_initial: true,
|
|
||||||
// TODO: This needs to be set based on user toggle in settings
|
|
||||||
vulkan: false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path to the settings file
|
|
||||||
**/
|
|
||||||
export const GPU_INFO_FILE = path.join(
|
|
||||||
getJanDataFolderPath(),
|
|
||||||
'settings',
|
|
||||||
'settings.json'
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current nitro process
|
|
||||||
*/
|
|
||||||
let nitroProcessInfo: NitroProcessInfo | undefined = undefined
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nitro process info
|
|
||||||
*/
|
|
||||||
export interface NitroProcessInfo {
|
|
||||||
isRunning: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will retrive GPU informations and persist settings.json
|
|
||||||
* Will be called when the extension is loaded to turn on GPU acceleration if supported
|
|
||||||
*/
|
|
||||||
export async function updateNvidiaInfo() {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
let data
|
|
||||||
try {
|
|
||||||
data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
|
||||||
} catch (error) {
|
|
||||||
data = DEFALT_SETTINGS
|
|
||||||
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
|
||||||
}
|
|
||||||
updateNvidiaDriverInfo()
|
|
||||||
updateGpuInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve current nitro process
|
|
||||||
*/
|
|
||||||
export const getNitroProcessInfo = (subprocess: any): NitroProcessInfo => {
|
|
||||||
nitroProcessInfo = {
|
|
||||||
isRunning: subprocess != null,
|
|
||||||
}
|
|
||||||
return nitroProcessInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate nvidia and cuda for linux and windows
|
|
||||||
*/
|
|
||||||
export async function updateNvidiaDriverInfo(): Promise<void> {
|
|
||||||
exec(
|
|
||||||
'nvidia-smi --query-gpu=driver_version --format=csv,noheader',
|
|
||||||
(error, stdout) => {
|
|
||||||
let data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
|
||||||
|
|
||||||
if (!error) {
|
|
||||||
const firstLine = stdout.split('\n')[0].trim()
|
|
||||||
data['nvidia_driver'].exist = true
|
|
||||||
data['nvidia_driver'].version = firstLine
|
|
||||||
} else {
|
|
||||||
data['nvidia_driver'].exist = false
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
|
||||||
Promise.resolve()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if file exists in paths
|
|
||||||
*/
|
|
||||||
export function checkFileExistenceInPaths(
|
|
||||||
file: string,
|
|
||||||
paths: string[]
|
|
||||||
): boolean {
|
|
||||||
return paths.some((p) => existsSync(path.join(p, file)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate cuda for linux and windows
|
|
||||||
*/
|
|
||||||
export function updateCudaExistence(
|
|
||||||
data: Record<string, any> = DEFALT_SETTINGS
|
|
||||||
): Record<string, any> {
|
|
||||||
let filesCuda12: string[]
|
|
||||||
let filesCuda11: string[]
|
|
||||||
let paths: string[]
|
|
||||||
let cudaVersion: string = ''
|
|
||||||
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
filesCuda12 = ['cublas64_12.dll', 'cudart64_12.dll', 'cublasLt64_12.dll']
|
|
||||||
filesCuda11 = ['cublas64_11.dll', 'cudart64_11.dll', 'cublasLt64_11.dll']
|
|
||||||
paths = process.env.PATH ? process.env.PATH.split(path.delimiter) : []
|
|
||||||
} else {
|
|
||||||
filesCuda12 = ['libcudart.so.12', 'libcublas.so.12', 'libcublasLt.so.12']
|
|
||||||
filesCuda11 = ['libcudart.so.11.0', 'libcublas.so.11', 'libcublasLt.so.11']
|
|
||||||
paths = process.env.LD_LIBRARY_PATH
|
|
||||||
? process.env.LD_LIBRARY_PATH.split(path.delimiter)
|
|
||||||
: []
|
|
||||||
paths.push('/usr/lib/x86_64-linux-gnu/')
|
|
||||||
}
|
|
||||||
|
|
||||||
let cudaExists = filesCuda12.every(
|
|
||||||
(file) => existsSync(file) || checkFileExistenceInPaths(file, paths)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!cudaExists) {
|
|
||||||
cudaExists = filesCuda11.every(
|
|
||||||
(file) => existsSync(file) || checkFileExistenceInPaths(file, paths)
|
|
||||||
)
|
|
||||||
if (cudaExists) {
|
|
||||||
cudaVersion = '11'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cudaVersion = '12'
|
|
||||||
}
|
|
||||||
|
|
||||||
data['cuda'].exist = cudaExists
|
|
||||||
data['cuda'].version = cudaVersion
|
|
||||||
console.debug(data['is_initial'], data['gpus_in_use'])
|
|
||||||
if (cudaExists && data['is_initial'] && data['gpus_in_use'].length > 0) {
|
|
||||||
data.run_mode = 'gpu'
|
|
||||||
}
|
|
||||||
data.is_initial = false
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get GPU information
|
|
||||||
*/
|
|
||||||
export async function updateGpuInfo(): Promise<void> {
|
|
||||||
let data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8'))
|
|
||||||
|
|
||||||
// Cuda
|
|
||||||
if (data['vulkan'] === true) {
|
|
||||||
// Vulkan
|
|
||||||
exec(
|
|
||||||
process.platform === 'win32'
|
|
||||||
? `${__dirname}\\..\\bin\\vulkaninfoSDK.exe --summary`
|
|
||||||
: `${__dirname}/../bin/vulkaninfo --summary`,
|
|
||||||
(error, stdout) => {
|
|
||||||
if (!error) {
|
|
||||||
const output = stdout.toString()
|
|
||||||
log(output)
|
|
||||||
const gpuRegex = /GPU(\d+):(?:[\s\S]*?)deviceName\s*=\s*(.*)/g
|
|
||||||
|
|
||||||
let gpus = []
|
|
||||||
let match
|
|
||||||
while ((match = gpuRegex.exec(output)) !== null) {
|
|
||||||
const id = match[1]
|
|
||||||
const name = match[2]
|
|
||||||
gpus.push({ id, vram: 0, name })
|
|
||||||
}
|
|
||||||
data.gpus = gpus
|
|
||||||
|
|
||||||
if (!data['gpus_in_use'] || data['gpus_in_use'].length === 0) {
|
|
||||||
data.gpus_in_use = [data.gpus.length > 1 ? '1' : '0']
|
|
||||||
}
|
|
||||||
|
|
||||||
data = updateCudaExistence(data)
|
|
||||||
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
|
||||||
}
|
|
||||||
Promise.resolve()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
exec(
|
|
||||||
'nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits',
|
|
||||||
(error, stdout) => {
|
|
||||||
if (!error) {
|
|
||||||
log(stdout)
|
|
||||||
// Get GPU info and gpu has higher memory first
|
|
||||||
let highestVram = 0
|
|
||||||
let highestVramId = '0'
|
|
||||||
let gpus = stdout
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => {
|
|
||||||
let [id, vram, name] = line.split(', ')
|
|
||||||
vram = vram.replace(/\r/g, '')
|
|
||||||
if (parseFloat(vram) > highestVram) {
|
|
||||||
highestVram = parseFloat(vram)
|
|
||||||
highestVramId = id
|
|
||||||
}
|
|
||||||
return { id, vram, name }
|
|
||||||
})
|
|
||||||
|
|
||||||
data.gpus = gpus
|
|
||||||
data.gpu_highest_vram = highestVramId
|
|
||||||
} else {
|
|
||||||
data.gpus = []
|
|
||||||
data.gpu_highest_vram = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data['gpus_in_use'] || data['gpus_in_use'].length === 0) {
|
|
||||||
data.gpus_in_use = [data['gpu_highest_vram']]
|
|
||||||
}
|
|
||||||
|
|
||||||
data = updateCudaExistence(data)
|
|
||||||
writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2))
|
|
||||||
Promise.resolve()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,19 @@
|
|||||||
|
import { getJanDataFolderPath } from '@janhq/core/node'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { GPU_INFO_FILE } from './accelerator'
|
|
||||||
|
|
||||||
export interface NitroExecutableOptions {
|
export interface NitroExecutableOptions {
|
||||||
executablePath: string
|
executablePath: string
|
||||||
cudaVisibleDevices: string
|
cudaVisibleDevices: string
|
||||||
vkVisibleDevices: string
|
vkVisibleDevices: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const GPU_INFO_FILE = path.join(
|
||||||
|
getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
'settings.json'
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find which executable file to run based on the current platform.
|
* Find which executable file to run based on the current platform.
|
||||||
* @returns The name of the executable file to run.
|
* @returns The name of the executable file to run.
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
|||||||
import tcpPortUsed from 'tcp-port-used'
|
import tcpPortUsed from 'tcp-port-used'
|
||||||
import fetchRT from 'fetch-retry'
|
import fetchRT from 'fetch-retry'
|
||||||
import { log, getSystemResourceInfo } from '@janhq/core/node'
|
import { log, getSystemResourceInfo } from '@janhq/core/node'
|
||||||
import { getNitroProcessInfo, updateNvidiaInfo } from './accelerator'
|
|
||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
InferenceEngine,
|
InferenceEngine,
|
||||||
@ -385,11 +384,26 @@ function dispose() {
|
|||||||
killSubprocess()
|
killSubprocess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nitro process info
|
||||||
|
*/
|
||||||
|
export interface NitroProcessInfo {
|
||||||
|
isRunning: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve current nitro process
|
||||||
|
*/
|
||||||
|
const getCurrentNitroProcessInfo = (): NitroProcessInfo => {
|
||||||
|
return {
|
||||||
|
isRunning: subprocess != null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
runModel,
|
runModel,
|
||||||
stopModel,
|
stopModel,
|
||||||
killSubprocess,
|
killSubprocess,
|
||||||
dispose,
|
dispose,
|
||||||
updateNvidiaInfo,
|
getCurrentNitroProcessInfo,
|
||||||
getCurrentNitroProcessInfo: () => getNitroProcessInfo(subprocess),
|
|
||||||
}
|
}
|
||||||
|
|||||||
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