From d7f161f668f59a1207c8daa51a3e2b0662ad3aa1 Mon Sep 17 00:00:00 2001 From: NamH Date: Tue, 4 Jun 2024 10:14:11 +0700 Subject: [PATCH 01/21] fix: scan the models folder recursive to find model metadata file (#2982) Co-authored-by: James --- extensions/model-extension/src/index.ts | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index 887ce7474..aa8f6603b 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -417,6 +417,30 @@ export default class JanModelExtension extends ModelExtension { ) } + private async getModelJsonPath( + folderFullPath: string + ): Promise { + // try to find model.json recursively inside each folder + if (!(await fs.existsSync(folderFullPath))) return undefined + const files: string[] = await fs.readdirSync(folderFullPath) + if (files.length === 0) return undefined + if (files.includes(JanModelExtension._modelMetadataFileName)) { + return joinPath([ + folderFullPath, + JanModelExtension._modelMetadataFileName, + ]) + } + // continue recursive + for (const file of files) { + const path = await joinPath([folderFullPath, file]) + const fileStats = await fs.fileStat(path) + if (fileStats.isDirectory) { + const result = await this.getModelJsonPath(path) + if (result) return result + } + } + } + private async getModelsMetadata( selector?: (path: string, model: Model) => Promise ): Promise { @@ -438,11 +462,11 @@ export default class JanModelExtension extends ModelExtension { const readJsonPromises = allDirectories.map(async (dirName) => { // filter out directories that don't match the selector // read model.json - const jsonPath = await joinPath([ + const folderFullPath = await joinPath([ JanModelExtension._homeDir, dirName, - JanModelExtension._modelMetadataFileName, ]) + const jsonPath = await this.getModelJsonPath(folderFullPath) if (await fs.existsSync(jsonPath)) { // if we have the model.json file, read it From d6bd493d93acdd5227ca0c118321d416c3b6ada2 Mon Sep 17 00:00:00 2001 From: Realmbird <56459277+Realmbird@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:20:43 -0700 Subject: [PATCH 02/21] Added NVIDIA API to new jan after jan rework (#2934) * Added NVIDIA API to new jan * Changed paramters * chore: some small text update - remove databrick since it does not work when I tested - correct some texts --------- Co-authored-by: James Nguyen --- .../inference-nvidia-extension/README.md | 79 +++++++++++++++++++ .../inference-nvidia-extension/package.json | 43 ++++++++++ .../resources/models.json | 31 ++++++++ .../resources/settings.json | 24 ++++++ .../inference-nvidia-extension/src/index.ts | 66 ++++++++++++++++ .../inference-nvidia-extension/tsconfig.json | 14 ++++ .../webpack.config.js | 42 ++++++++++ 7 files changed, 299 insertions(+) create mode 100644 extensions/inference-nvidia-extension/README.md create mode 100644 extensions/inference-nvidia-extension/package.json create mode 100644 extensions/inference-nvidia-extension/resources/models.json create mode 100644 extensions/inference-nvidia-extension/resources/settings.json create mode 100644 extensions/inference-nvidia-extension/src/index.ts create mode 100644 extensions/inference-nvidia-extension/tsconfig.json create mode 100644 extensions/inference-nvidia-extension/webpack.config.js diff --git a/extensions/inference-nvidia-extension/README.md b/extensions/inference-nvidia-extension/README.md new file mode 100644 index 000000000..65a1b2b59 --- /dev/null +++ b/extensions/inference-nvidia-extension/README.md @@ -0,0 +1,79 @@ +# Nvidia Engine Extension + +Created using Jan extension example + +# Create a Jan Extension using Typescript + +Use this template to bootstrap the creation of a TypeScript Jan extension. πŸš€ + +## Create Your Own Extension + +To create your own extension, you can use this repository as a template! Just follow the below instructions: + +1. Click the Use this template button at the top of the repository +2. Select Create a new repository +3. Select an owner and name for your new repository +4. Click Create repository +5. Clone your new repository + +## Initial Setup + +After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension. + +> [!NOTE] +> +> You'll need to have a reasonably modern version of +> [Node.js](https://nodejs.org) handy. If you are using a version manager like +> [`nodenv`](https://github.com/nodenv/nodenv) or +> [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the +> root of your repository to install the version specified in +> [`package.json`](./package.json). Otherwise, 20.x or later should work! + +1. :hammer_and_wrench: Install the dependencies + + ```bash + npm install + ``` + +1. :building_construction: Package the TypeScript for distribution + + ```bash + npm run bundle + ``` + +1. :white_check_mark: Check your artifact + + There will be a tgz file in your extension directory now + +## Update the Extension Metadata + +The [`package.json`](package.json) file defines metadata about your extension, such as +extension name, main entry, description and version. + +When you copy this repository, update `package.json` with the name, description for your extension. + +## Update the Extension Code + +The [`src/`](./src/) directory is the heart of your extension! This contains the +source code that will be run when your extension functions are invoked. You can replace the +contents of this directory with your own code. + +There are a few things to keep in mind when writing your extension code: + +- Most Jan Extension functions are processed asynchronously. + In `index.ts`, you will see that the extension function will return a `Promise`. + + ```typescript + import { events, MessageEvent, MessageRequest } from '@janhq/core' + + function onStart(): Promise { + return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => + this.inference(data) + ) + } + ``` + + For more information about the Jan Extension Core module, see the + [documentation](https://github.com/janhq/jan/blob/main/core/README.md). + +So, what are you waiting for? Go ahead and start customizing your extension! diff --git a/extensions/inference-nvidia-extension/package.json b/extensions/inference-nvidia-extension/package.json new file mode 100644 index 000000000..8bd7708bc --- /dev/null +++ b/extensions/inference-nvidia-extension/package.json @@ -0,0 +1,43 @@ +{ + "name": "@janhq/inference-nvidia-extension", + "productName": "NVIDIA NIM Inference Engine", + "version": "1.0.1", + "description": "This extension enables NVIDIA chat completion API calls", + "main": "dist/index.js", + "module": "dist/module.js", + "engine": "nvidia", + "author": "Jan ", + "license": "AGPL-3.0", + "scripts": { + "build": "tsc -b . && webpack --config webpack.config.js", + "build:publish": "rimraf *.tgz --glob && yarn build && npm pack && cpx *.tgz ../../pre-install" + }, + "exports": { + ".": "./dist/index.js", + "./main": "./dist/module.js" + }, + "devDependencies": { + "cpx": "^1.5.0", + "rimraf": "^3.0.2", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "ts-loader": "^9.5.0" + }, + "dependencies": { + "@janhq/core": "file:../../core", + "fetch-retry": "^5.0.6", + "path-browserify": "^1.0.1", + "ulidx": "^2.3.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "dist/*", + "package.json", + "README.md" + ], + "bundleDependencies": [ + "fetch-retry" + ] +} diff --git a/extensions/inference-nvidia-extension/resources/models.json b/extensions/inference-nvidia-extension/resources/models.json new file mode 100644 index 000000000..b97644fc9 --- /dev/null +++ b/extensions/inference-nvidia-extension/resources/models.json @@ -0,0 +1,31 @@ +[ + { + "sources": [ + { + "url": "https://integrate.api.nvidia.com/v1/chat/completions" + } + ], + "id": "mistralai/mistral-7b-instruct-v0.2", + "object": "model", + "name": "Mistral 7B", + "version": "1.1", + "description": "Mistral 7B with NVIDIA", + "format": "api", + "settings": {}, + "parameters": { + "max_tokens": 1024, + "temperature": 0.3, + "top_p": 1, + "stream": false, + "frequency_penalty": 0, + "presence_penalty": 0, + "stop": null, + "seed": null + }, + "metadata": { + "author": "NVIDIA", + "tags": ["General"] + }, + "engine": "nvidia" + } +] diff --git a/extensions/inference-nvidia-extension/resources/settings.json b/extensions/inference-nvidia-extension/resources/settings.json new file mode 100644 index 000000000..e7647b562 --- /dev/null +++ b/extensions/inference-nvidia-extension/resources/settings.json @@ -0,0 +1,24 @@ +[ + { + "key": "chat-completions-endpoint", + "title": "Chat Completions Endpoint", + "description": "The endpoint to use for chat completions. See the [NVIDIA API documentation](https://www.nvidia.com/en-us/ai/) for more information.", + "controllerType": "input", + "controllerProps": { + "placeholder": "https://integrate.api.nvidia.com/v1/chat/completions", + "value": "https://integrate.api.nvidia.com/v1/chat/completions" + } + }, + { + "key": "nvidia-api-key", + "title": "API Key", + "description": "The NVIDIA API uses API keys for authentication. Visit your [API Keys](https://org.ngc.nvidia.com/setup/personal-keys) page to retrieve the API key you'll use in your requests..", + "controllerType": "input", + "controllerProps": { + "placeholder": "nvapi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "value": "", + "type": "password", + "inputActions": ["unobscure", "copy"] + } + } +] diff --git a/extensions/inference-nvidia-extension/src/index.ts b/extensions/inference-nvidia-extension/src/index.ts new file mode 100644 index 000000000..9af27d90c --- /dev/null +++ b/extensions/inference-nvidia-extension/src/index.ts @@ -0,0 +1,66 @@ +/** + * @file This file exports a class that implements the InferenceExtension interface from the @janhq/core package. + * The class provides methods for initializing and stopping a model, and for making inference requests. + * It also subscribes to events emitted by the @janhq/core package and handles new message requests. + * @version 1.0.0 + * @module inference-mistral-extension/src/index + */ + +import { RemoteOAIEngine } from '@janhq/core' + +declare const SETTINGS: Array +declare const MODELS: Array + +enum Settings { + apiKey = 'nvidia-api-key', + chatCompletionsEndPoint = 'chat-completions-endpoint', +} +/** + * A class that implements the InferenceExtension interface from the @janhq/core package. + * The class provides methods for initializing and stopping a model, and for making inference requests. + * It also subscribes to events emitted by the @janhq/core package and handles new message requests. + */ +export default class JanNVIDIANIMInferenceEngine extends RemoteOAIEngine { + inferenceUrl: string = '' + provider: string = 'nvidia' + + override async onLoad(): Promise { + super.onLoad() + + // Register Settings + this.registerSettings(SETTINGS) + this.registerModels(MODELS) + + this.apiKey = await this.getSetting(Settings.apiKey, '') + this.inferenceUrl = await this.getSetting( + Settings.chatCompletionsEndPoint, + '' + ) + + if (this.inferenceUrl.length === 0) { + SETTINGS.forEach((setting) => { + if (setting.key === Settings.chatCompletionsEndPoint) { + this.inferenceUrl = setting.controllerProps.value as string + } + }) + } + } + + onSettingUpdate(key: string, value: T): void { + if (key === Settings.apiKey) { + this.apiKey = value as string + } else if (key === Settings.chatCompletionsEndPoint) { + if (typeof value !== 'string') return + + if (value.trim().length === 0) { + SETTINGS.forEach((setting) => { + if (setting.key === Settings.chatCompletionsEndPoint) { + this.inferenceUrl = setting.controllerProps.value as string + } + }) + } else { + this.inferenceUrl = value + } + } + } +} diff --git a/extensions/inference-nvidia-extension/tsconfig.json b/extensions/inference-nvidia-extension/tsconfig.json new file mode 100644 index 000000000..2477d58ce --- /dev/null +++ b/extensions/inference-nvidia-extension/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "ES6", + "moduleResolution": "node", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "skipLibCheck": true, + "rootDir": "./src" + }, + "include": ["./src"] +} diff --git a/extensions/inference-nvidia-extension/webpack.config.js b/extensions/inference-nvidia-extension/webpack.config.js new file mode 100644 index 000000000..0e35fc227 --- /dev/null +++ b/extensions/inference-nvidia-extension/webpack.config.js @@ -0,0 +1,42 @@ +const path = require('path') +const webpack = require('webpack') +const packageJson = require('./package.json') +const settingJson = require('./resources/settings.json') +const modelsJson = require('./resources/models.json') + +module.exports = { + experiments: { outputModule: true }, + entry: './src/index.ts', // Adjust the entry point to match your project's main file + mode: 'production', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new webpack.DefinePlugin({ + SETTINGS: JSON.stringify(settingJson), + ENGINE: JSON.stringify(packageJson.engine), + MODELS: JSON.stringify(modelsJson), + }), + ], + output: { + filename: 'index.js', // Adjust the output file name as needed + path: path.resolve(__dirname, 'dist'), + library: { type: 'module' }, // Specify ESM output format + }, + resolve: { + extensions: ['.ts', '.js'], + fallback: { + path: require.resolve('path-browserify'), + }, + }, + optimization: { + minimize: false, + }, + // Add loaders and other configuration as needed for your project +} From 0cae7c97612ba4f6a3387f7f72c56e065711cacf Mon Sep 17 00:00:00 2001 From: NamH Date: Wed, 5 Jun 2024 11:05:57 +0700 Subject: [PATCH 03/21] fix: specific version for turbo (#2989) Signed-off-by: James --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 204e1698f..1687f8bbe 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ install-and-build: build-joi ifeq ($(OS),Windows_NT) yarn config set network-timeout 300000 endif - yarn global add turbo + yarn global add turbo@1.13.2 yarn build:core yarn build:server yarn install From 57177d44098cfd692cc11928ab9a78f1806be30e Mon Sep 17 00:00:00 2001 From: phoval <89515930+phoval@users.noreply.github.com> Date: Mon, 10 Jun 2024 05:13:27 +0200 Subject: [PATCH 04/21] fix: undefined browser (#3011) electron is not available --- web/hooks/useLoadTheme.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/hooks/useLoadTheme.ts b/web/hooks/useLoadTheme.ts index dfa8d263d..8afba27c4 100644 --- a/web/hooks/useLoadTheme.ts +++ b/web/hooks/useLoadTheme.ts @@ -29,11 +29,11 @@ export const useLoadTheme = async () => { const setNativeTheme = useCallback( (nativeTheme: NativeThemeProps) => { if (nativeTheme === 'dark') { - window?.electronAPI.setNativeThemeDark() + window?.electronAPI?.setNativeThemeDark() setTheme('dark') localStorage.setItem('nativeTheme', 'dark') } else { - window?.electronAPI.setNativeThemeLight() + window?.electronAPI?.setNativeThemeLight() setTheme('light') localStorage.setItem('nativeTheme', 'light') } From 6ee5d16e5c4f60baba0fd03da9c6acb6de7bc85a Mon Sep 17 00:00:00 2001 From: NamH Date: Mon, 10 Jun 2024 16:19:31 +0700 Subject: [PATCH 05/21] fix: duplicate role inside messages cause some model to refuse to answer (#3006) * fix: duplicate role inside messages cause some model to refuse to answer Signed-off-by: James * update * Bump cortex to 0.4.12 * some model require not empty message update --------- Signed-off-by: James Co-authored-by: Van Pham <64197333+Van-QA@users.noreply.github.com> --- .../inference-nitro-extension/bin/version.txt | 2 +- web/hooks/useSendChatMessage.ts | 32 +++++++++++++++++++ web/utils/Stack.ts | 31 ++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 web/utils/Stack.ts diff --git a/extensions/inference-nitro-extension/bin/version.txt b/extensions/inference-nitro-extension/bin/version.txt index 5f749c136..75274d832 100644 --- a/extensions/inference-nitro-extension/bin/version.txt +++ b/extensions/inference-nitro-extension/bin/version.txt @@ -1 +1 @@ -0.4.11 +0.4.12 diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 59282aa9f..8c6013505 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -10,6 +10,7 @@ import { ConversationalExtension, EngineManager, ToolManager, + ChatCompletionMessage, } from '@janhq/core' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' @@ -19,6 +20,7 @@ import { fileUploadAtom, } from '@/containers/Providers/Jotai' +import { Stack } from '@/utils/Stack' import { compressImage, getBase64 } from '@/utils/base64' import { MessageRequestBuilder } from '@/utils/messageRequestBuilder' import { toRuntimeParams, toSettingParams } from '@/utils/modelParam' @@ -90,6 +92,33 @@ export default function useSendChatMessage() { selectedModelRef.current = selectedModel }, [selectedModel]) + const normalizeMessages = ( + messages: ChatCompletionMessage[] + ): ChatCompletionMessage[] => { + const stack = new Stack() + for (const message of messages) { + if (stack.isEmpty()) { + stack.push(message) + continue + } + const topMessage = stack.peek() + + if (message.role === topMessage.role) { + // add an empty message + stack.push({ + role: + topMessage.role === ChatCompletionRole.User + ? ChatCompletionRole.Assistant + : ChatCompletionRole.User, + content: '.', // some model requires not empty message + }) + } + stack.push(message) + } + + return stack.reverseOutput() + } + const resendChatMessage = async (currentMessage: ThreadMessage) => { if (!activeThreadRef.current) { console.error('No active thread') @@ -140,6 +169,8 @@ export default function useSendChatMessage() { ) ?? [] ) + request.messages = normalizeMessages(request.messages ?? []) + const engine = requestBuilder.model?.engine ?? selectedModelRef.current?.engine ?? '' @@ -258,6 +289,7 @@ export default function useSendChatMessage() { (assistant) => assistant.tools ?? [] ) ?? [] ) + request.messages = normalizeMessages(request.messages ?? []) // Request for inference EngineManager.instance() diff --git a/web/utils/Stack.ts b/web/utils/Stack.ts new file mode 100644 index 000000000..277b2859b --- /dev/null +++ b/web/utils/Stack.ts @@ -0,0 +1,31 @@ +export class Stack { + private array: T[] = [] + + pop(): T | undefined { + if (this.isEmpty()) throw new Error() + + return this.array.pop() + } + + push(data: T): void { + this.array.push(data) + } + + peek(): T { + if (this.isEmpty()) throw new Error() + + return this.array[this.array.length - 1] + } + + isEmpty(): boolean { + return this.array.length === 0 + } + + size(): number { + return this.array.length + } + + reverseOutput(): T[] { + return [...this.array] + } +} From aecc645a1a22176dce54c67edc114c43d9ff4e2a Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 11 Jun 2024 18:46:00 +0700 Subject: [PATCH 06/21] fix: bring back maximize button (#3024) --- electron/handlers/native.ts | 9 +++------ electron/managers/mainWindowConfig.ts | 3 ++- web/containers/Layout/TopPanel/index.tsx | 13 ++++++++++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/electron/handlers/native.ts b/electron/handlers/native.ts index 1bc815b41..869b9fd58 100644 --- a/electron/handlers/native.ts +++ b/electron/handlers/native.ts @@ -1,4 +1,4 @@ -import { app, ipcMain, dialog, shell, nativeTheme, screen } from 'electron' +import { app, ipcMain, dialog, shell, nativeTheme } from 'electron' import { join } from 'path' import { windowManager } from '../managers/window' import { @@ -41,12 +41,9 @@ export function handleAppIPCs() { windowManager.mainWindow?.minimize() }) - ipcMain.handle(NativeRoute.setMaximizeApp, async () => { + ipcMain.handle(NativeRoute.setMaximizeApp, async (_event) => { if (windowManager.mainWindow?.isMaximized()) { - // const bounds = await getBounds() - // windowManager.mainWindow?.setSize(bounds.width, bounds.height) - // windowManager.mainWindow?.setPosition(Number(bounds.x), Number(bounds.y)) - windowManager.mainWindow.restore() + windowManager.mainWindow.unmaximize() } else { windowManager.mainWindow?.maximize() } diff --git a/electron/managers/mainWindowConfig.ts b/electron/managers/mainWindowConfig.ts index 25f0635f7..c3f9c01bd 100644 --- a/electron/managers/mainWindowConfig.ts +++ b/electron/managers/mainWindowConfig.ts @@ -4,11 +4,12 @@ export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = { skipTaskbar: false, minWidth: DEFAULT_MIN_WIDTH, show: true, + transparent: true, + frame: false, titleBarStyle: 'hidden', vibrancy: 'fullscreen-ui', visualEffectState: 'active', backgroundMaterial: 'acrylic', - maximizable: false, autoHideMenuBar: true, trafficLightPosition: { x: 16, diff --git a/web/containers/Layout/TopPanel/index.tsx b/web/containers/Layout/TopPanel/index.tsx index e2966e4da..ac2c23a99 100644 --- a/web/containers/Layout/TopPanel/index.tsx +++ b/web/containers/Layout/TopPanel/index.tsx @@ -9,6 +9,7 @@ import { PanelRightCloseIcon, MinusIcon, MenuIcon, + SquareIcon, PaletteIcon, XIcon, } from 'lucide-react' @@ -51,7 +52,7 @@ const TopPanel = () => { + From cf8f401daba8d822dfdd0c9a83059ef0ecabdded Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 11 Jun 2024 18:46:12 +0700 Subject: [PATCH 07/21] feat: update icon alert warning and copies tooltip model dropdown (#3021) * feat: update icon alert warning and copies tooltip model dropdown * chore: update loader when user click download model dropdown --- web/containers/Loader/ProgressCircle.tsx | 50 ++++++++++++++++ web/containers/ModelDropdown/index.tsx | 44 +++++++++++++- .../ModelLabel/NotEnoughMemoryLabel.tsx | 57 ++++++++++++------- .../ModelLabel/SlowOnYourDeviceLabel.tsx | 57 ++++++++++++------- 4 files changed, 165 insertions(+), 43 deletions(-) create mode 100644 web/containers/Loader/ProgressCircle.tsx diff --git a/web/containers/Loader/ProgressCircle.tsx b/web/containers/Loader/ProgressCircle.tsx new file mode 100644 index 000000000..e10434113 --- /dev/null +++ b/web/containers/Loader/ProgressCircle.tsx @@ -0,0 +1,50 @@ +import React from 'react' + +interface ProgressCircleProps { + percentage: number + size?: number + strokeWidth?: number +} + +const ProgressCircle: React.FC = ({ + percentage, + size = 100, + strokeWidth = 14, +}) => { + const radius = (size - strokeWidth) / 2 + const circumference = 2 * Math.PI * radius + const offset = circumference - (percentage / 100) * circumference + + return ( + + + + + ) +} + +export default ProgressCircle diff --git a/web/containers/ModelDropdown/index.tsx b/web/containers/ModelDropdown/index.tsx index a57525698..1f33f3ceb 100644 --- a/web/containers/ModelDropdown/index.tsx +++ b/web/containers/ModelDropdown/index.tsx @@ -8,16 +8,19 @@ import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { ChevronDownIcon, DownloadCloudIcon, XIcon } from 'lucide-react' import { twMerge } from 'tailwind-merge' +import ProgressCircle from '@/containers/Loader/ProgressCircle' + import ModelLabel from '@/containers/ModelLabel' import SetupRemoteModel from '@/containers/SetupRemoteModel' import useDownloadModel from '@/hooks/useDownloadModel' +import { modelDownloadStateAtom } from '@/hooks/useDownloadState' import useRecommendedModel from '@/hooks/useRecommendedModel' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' -import { toGibibytes } from '@/utils/converter' +import { formatDownloadPercentage, toGibibytes } from '@/utils/converter' import { extensionManager } from '@/extension' @@ -64,6 +67,7 @@ const ModelDropdown = ({ const [dropdownOptions, setDropdownOptions] = useState( null ) + const downloadStates = useAtomValue(modelDownloadStateAtom) const setThreadModelParams = useSetAtom(setThreadModelParamsAtom) const { updateModelParameter } = useUpdateModelParameters() @@ -351,12 +355,29 @@ const ModelDropdown = ({ {toGibibytes(model.metadata.size)} - {!isDownloading && ( + {!isDownloading ? ( downloadModel(model)} /> + ) : ( + Object.values(downloadStates) + .filter((x) => x.modelId === model.id) + .map((item) => ( + + )) )} @@ -397,12 +418,29 @@ const ModelDropdown = ({ {toGibibytes(model.metadata.size)} - {!isDownloading && ( + {!isDownloading ? ( downloadModel(model)} /> + ) : ( + Object.values(downloadStates) + .filter((x) => x.modelId === model.id) + .map((item) => ( + + )) )} diff --git a/web/containers/ModelLabel/NotEnoughMemoryLabel.tsx b/web/containers/ModelLabel/NotEnoughMemoryLabel.tsx index fb2b7bde5..287193183 100644 --- a/web/containers/ModelLabel/NotEnoughMemoryLabel.tsx +++ b/web/containers/ModelLabel/NotEnoughMemoryLabel.tsx @@ -1,32 +1,49 @@ -import { memo } from 'react' +import { Fragment, memo } from 'react' import { Badge, Tooltip } from '@janhq/joi' -import { InfoIcon } from 'lucide-react' -import { twMerge } from 'tailwind-merge' +import { AlertTriangleIcon, InfoIcon } from 'lucide-react' type Props = { compact?: boolean unit: string } +const tooltipContent = `Your device doesn't have enough RAM to run this model. Consider upgrading your RAM or using a device with more memory capacity.` + const NotEnoughMemoryLabel = ({ unit, compact }: Props) => ( - - {!compact && Not enough {unit}} - - ) : ( - - ) - } - content="This tag signals insufficient RAM for optimal model performance. It's dynamic and may change with your system's RAM availability." - /> - + <> + {compact ? ( +
+ + } + content={ + + Not enough RAM: {tooltipContent} + + } + /> +
+ ) : ( + + Not enough {unit} + + } + content={ + + Not enough RAM: {tooltipContent} + + } + /> + + )} + ) export default memo(NotEnoughMemoryLabel) diff --git a/web/containers/ModelLabel/SlowOnYourDeviceLabel.tsx b/web/containers/ModelLabel/SlowOnYourDeviceLabel.tsx index d89fd2505..e8e9bcb4d 100644 --- a/web/containers/ModelLabel/SlowOnYourDeviceLabel.tsx +++ b/web/containers/ModelLabel/SlowOnYourDeviceLabel.tsx @@ -1,32 +1,49 @@ -import { memo } from 'react' +import { Fragment, memo } from 'react' import { Badge, Tooltip } from '@janhq/joi' -import { InfoIcon } from 'lucide-react' -import { twMerge } from 'tailwind-merge' +import { AlertTriangleIcon, InfoIcon } from 'lucide-react' type Props = { compact?: boolean } +const tooltipContent = `Your device may be running low on available RAM, which can affect the speed of this model. Try closing any unnecessary applications to free up system memory.` + const SlowOnYourDeviceLabel = ({ compact }: Props) => ( - - {!compact && Slow on your device} - - ) : ( - - ) - } - content="This tag indicates that your current RAM performance may affect model speed. It can change based on other active apps. To improve, consider closing unnecessary applications to free up RAM." - /> - + <> + {compact ? ( +
+ + } + content={ + + Slow on your device: {tooltipContent} + + } + /> +
+ ) : ( + + Slow on your device + + } + content={ + + Slow on your device: {tooltipContent} + + } + /> + + )} + ) export default memo(SlowOnYourDeviceLabel) From 44536ec929316c294ec2943958ec6d73ac3c61dc Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 11 Jun 2024 18:46:27 +0700 Subject: [PATCH 08/21] feat: enable hotkey collapse right panel (#3019) --- web/containers/Providers/KeyListener.tsx | 20 ++++++++++++++++++-- web/screens/Settings/Hotkeys/index.tsx | 7 ++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/web/containers/Providers/KeyListener.tsx b/web/containers/Providers/KeyListener.tsx index 36d513016..2731846df 100644 --- a/web/containers/Providers/KeyListener.tsx +++ b/web/containers/Providers/KeyListener.tsx @@ -8,7 +8,11 @@ import { MainViewState } from '@/constants/screens' import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import { mainViewStateAtom, showLeftPanelAtom } from '@/helpers/atoms/App.atom' +import { + mainViewStateAtom, + showLeftPanelAtom, + showRightPanelAtom, +} from '@/helpers/atoms/App.atom' import { assistantsAtom } from '@/helpers/atoms/Assistant.atom' type Props = { @@ -17,6 +21,7 @@ type Props = { export default function KeyListener({ children }: Props) { const setShowLeftPanel = useSetAtom(showLeftPanelAtom) + const setShowRightPanel = useSetAtom(showRightPanelAtom) const setMainViewState = useSetAtom(mainViewStateAtom) const { requestCreateNewThread } = useCreateNewThread() const assistants = useAtomValue(assistantsAtom) @@ -25,6 +30,11 @@ export default function KeyListener({ children }: Props) { const onKeyDown = (e: KeyboardEvent) => { const prefixKey = isMac ? e.metaKey : e.ctrlKey + if (e.key === 'b' && prefixKey && e.shiftKey) { + setShowRightPanel((showRightideBar) => !showRightideBar) + return + } + if (e.key === 'n' && prefixKey) { requestCreateNewThread(assistants[0]) setMainViewState(MainViewState.Thread) @@ -43,7 +53,13 @@ export default function KeyListener({ children }: Props) { } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) - }, [assistants, requestCreateNewThread, setMainViewState, setShowLeftPanel]) + }, [ + assistants, + requestCreateNewThread, + setMainViewState, + setShowLeftPanel, + setShowRightPanel, + ]) return {children} } diff --git a/web/screens/Settings/Hotkeys/index.tsx b/web/screens/Settings/Hotkeys/index.tsx index 6434ba295..382efad2e 100644 --- a/web/screens/Settings/Hotkeys/index.tsx +++ b/web/screens/Settings/Hotkeys/index.tsx @@ -11,6 +11,11 @@ const availableHotkeys = [ modifierKeys: [isMac ? '⌘' : 'Ctrl'], description: 'Toggle collapsible left panel', }, + { + combination: 'Shift B', + modifierKeys: [isMac ? '⌘' : 'Ctrl'], + description: 'Toggle collapsible right panel', + }, { combination: ',', modifierKeys: [isMac ? '⌘' : 'Ctrl'], @@ -21,7 +26,7 @@ const availableHotkeys = [ description: 'Send a message', }, { - combination: 'Shift + Enter', + combination: 'Shift Enter', description: 'Insert new line in input box', }, { From 93fc228a26c0e4bbe6eaea6120a12bbedba6baee Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 11 Jun 2024 18:46:40 +0700 Subject: [PATCH 09/21] fix: reduce font-weight for message (#3018) --- .../Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx b/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx index 509610bd5..43d2f02c9 100644 --- a/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx @@ -268,7 +268,7 @@ const SimpleTextMessage: React.FC = (props) => { ) : (
@@ -279,7 +279,7 @@ const SimpleTextMessage: React.FC = (props) => { ) : (
Date: Wed, 12 Jun 2024 16:09:17 +0700 Subject: [PATCH 10/21] chore: Bump-cortex-0.4.13 (#3027) --- extensions/inference-nitro-extension/bin/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/inference-nitro-extension/bin/version.txt b/extensions/inference-nitro-extension/bin/version.txt index 75274d832..1f7716999 100644 --- a/extensions/inference-nitro-extension/bin/version.txt +++ b/extensions/inference-nitro-extension/bin/version.txt @@ -1 +1 @@ -0.4.12 +0.4.13 From 491f563f0f325acf5810aff9df257c2be2ad6820 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Wed, 12 Jun 2024 21:07:19 +0700 Subject: [PATCH 11/21] fix: enable window control button on linux (#3028) --- web/containers/Layout/TopPanel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/containers/Layout/TopPanel/index.tsx b/web/containers/Layout/TopPanel/index.tsx index ac2c23a99..6dd9ba8a5 100644 --- a/web/containers/Layout/TopPanel/index.tsx +++ b/web/containers/Layout/TopPanel/index.tsx @@ -97,7 +97,7 @@ const TopPanel = () => { - {isWindows && ( + {!isMac && (