From 010802012f933c404e0d1c16cfd85dc70a4d973e Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 28 Jan 2025 23:15:02 +0700 Subject: [PATCH 01/59] fix: broken range slider ctx lenght (#4535) --- web/screens/Thread/ThreadRightPanel/index.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/web/screens/Thread/ThreadRightPanel/index.tsx b/web/screens/Thread/ThreadRightPanel/index.tsx index ba801fd0b..6023f8301 100644 --- a/web/screens/Thread/ThreadRightPanel/index.tsx +++ b/web/screens/Thread/ThreadRightPanel/index.tsx @@ -202,16 +202,6 @@ const ThreadRightPanel = () => { }, }) } - if ( - key === 'ctx_len' && - Number(value) < activeAssistant.model.parameters.max_tokens - ) { - updateModelParameter(activeThread, { - params: { - max_tokens: activeAssistant.model.settings.ctx_len, - }, - }) - } } }, [ From 144b83693586b65dcae3047fc6d4ed7511b10254 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 29 Jan 2025 21:50:17 +0700 Subject: [PATCH 02/59] fix: app does not reload model when prompt template is updated (#4537) * fix: app does not reload model when prompt template is updated * chore: lint fix --- extensions/engine-management-extension/rolldown.config.mjs | 4 ++-- extensions/inference-cortex-extension/rolldown.config.mjs | 2 +- web/containers/ModelDropdown/index.tsx | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 7e9a2d156..1290338db 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -13,7 +13,7 @@ export default defineConfig([ NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), API_URL: JSON.stringify('http://127.0.0.1:39291'), SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.46'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), }, @@ -26,7 +26,7 @@ export default defineConfig([ file: 'dist/node/index.cjs.js', }, define: { - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.46'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), }, }, { diff --git a/extensions/inference-cortex-extension/rolldown.config.mjs b/extensions/inference-cortex-extension/rolldown.config.mjs index cebfa7190..278664d3d 100644 --- a/extensions/inference-cortex-extension/rolldown.config.mjs +++ b/extensions/inference-cortex-extension/rolldown.config.mjs @@ -111,7 +111,7 @@ export default defineConfig([ SETTINGS: JSON.stringify(defaultSettingJson), CORTEX_API_URL: JSON.stringify('http://127.0.0.1:39291'), CORTEX_SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.46'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), }, }, { diff --git a/web/containers/ModelDropdown/index.tsx b/web/containers/ModelDropdown/index.tsx index 40c6fc931..de4959bc3 100644 --- a/web/containers/ModelDropdown/index.tsx +++ b/web/containers/ModelDropdown/index.tsx @@ -28,6 +28,8 @@ import ModelLabel from '@/containers/ModelLabel' import SetupRemoteModel from '@/containers/SetupRemoteModel' +import { useActiveModel } from '@/hooks/useActiveModel' + import { useCreateNewThread } from '@/hooks/useCreateNewThread' import useDownloadModel from '@/hooks/useDownloadModel' import { modelDownloadStateAtom } from '@/hooks/useDownloadState' @@ -93,6 +95,7 @@ const ModelDropdown = ({ const { updateModelParameter } = useUpdateModelParameters() const searchInputRef = useRef(null) const configuredModels = useAtomValue(configuredModelsAtom) + const { stopModel } = useActiveModel() const featuredModels = configuredModels.filter( (x) => @@ -226,6 +229,7 @@ const ModelDropdown = ({ const model = downloadedModels.find((m) => m.id === modelId) setSelectedModel(model) setOpen(false) + stopModel() if (activeThread) { // Change assistand tools based on model support RAG From 350c9f7def8a4490e6b91aef753f5c9ac3f32d54 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 29 Jan 2025 21:52:00 +0700 Subject: [PATCH 03/59] chore: maintain app distinct_id on factory reset (#4541) --- core/src/types/config/appConfigEntity.ts | 1 + web/containers/Layout/index.tsx | 14 ++++++++++++-- web/hooks/useFactoryReset.ts | 1 + web/utils/settings.ts | 22 ++++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 web/utils/settings.ts diff --git a/core/src/types/config/appConfigEntity.ts b/core/src/types/config/appConfigEntity.ts index 1402aeca1..bd352d22f 100644 --- a/core/src/types/config/appConfigEntity.ts +++ b/core/src/types/config/appConfigEntity.ts @@ -1,4 +1,5 @@ export type AppConfiguration = { data_folder: string quick_ask: boolean + distinct_id?: string } diff --git a/web/containers/Layout/index.tsx b/web/containers/Layout/index.tsx index 29fda70de..7d5f61ed3 100644 --- a/web/containers/Layout/index.tsx +++ b/web/containers/Layout/index.tsx @@ -26,6 +26,8 @@ import ImportModelOptionModal from '@/screens/Settings/ImportModelOptionModal' import ImportingModelModal from '@/screens/Settings/ImportingModelModal' import SelectingModelModal from '@/screens/Settings/SelectingModelModal' +import { getAppDistinctId, updateDistinctId } from '@/utils/settings' + import LoadingModal from '../LoadingModal' import MainViewContainer from '../MainViewContainer' @@ -93,8 +95,16 @@ const BaseLayout = () => { return properties }, }) - posthog.opt_in_capturing() - posthog.register({ app_version: VERSION }) + // Attempt to restore distinct Id from app global settings + getAppDistinctId() + .then((id) => { + if (id) posthog.identify(id) + }) + .finally(() => { + posthog.opt_in_capturing() + posthog.register({ app_version: VERSION }) + updateDistinctId(posthog.get_distinct_id()) + }) } else { posthog.opt_out_capturing() } diff --git a/web/hooks/useFactoryReset.ts b/web/hooks/useFactoryReset.ts index da2e15b03..7344b2eb1 100644 --- a/web/hooks/useFactoryReset.ts +++ b/web/hooks/useFactoryReset.ts @@ -58,6 +58,7 @@ export default function useFactoryReset() { const configuration: AppConfiguration = { data_folder: defaultJanDataFolder, quick_ask: appConfiguration?.quick_ask ?? false, + distinct_id: appConfiguration?.distinct_id, } await window.core?.api?.updateAppConfiguration(configuration) } diff --git a/web/utils/settings.ts b/web/utils/settings.ts new file mode 100644 index 000000000..75a60bc52 --- /dev/null +++ b/web/utils/settings.ts @@ -0,0 +1,22 @@ +import { AppConfiguration } from '@janhq/core' + +/** + * Update app distinct Id + * @param id + */ +export const updateDistinctId = async (id: string) => { + const appConfiguration: AppConfiguration = + await window.core?.api?.getAppConfigurations() + appConfiguration.distinct_id = id + await window.core?.api?.updateAppConfiguration(appConfiguration) +} + +/** + * Retrieve app distinct Id + * @param id + */ +export const getAppDistinctId = async (): Promise => { + const appConfiguration: AppConfiguration = + await window.core?.api?.getAppConfigurations() + return appConfiguration.distinct_id +} From 33db5135b153f1bd71fc4c21e02a72022bb58842 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 30 Jan 2025 17:26:46 +0700 Subject: [PATCH 04/59] feat: auto pull models list when setting up remote engine (#4547) --- core/src/types/model/modelEntity.ts | 2 +- .../rolldown.config.mjs | 7 ++ .../src/@types/global.d.ts | 3 + .../engine-management-extension/src/index.ts | 80 ++++++++++++++++--- 4 files changed, 78 insertions(+), 14 deletions(-) diff --git a/core/src/types/model/modelEntity.ts b/core/src/types/model/modelEntity.ts index 482dfa1ac..d7db7b9d6 100644 --- a/core/src/types/model/modelEntity.ts +++ b/core/src/types/model/modelEntity.ts @@ -71,7 +71,7 @@ export type Model = { /** * The model identifier, modern version of id. */ - mode?: string + model?: string /** * Human-readable name that is used for UI. diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 1290338db..7e8cdcd2b 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -16,6 +16,13 @@ export default defineConfig([ CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), + DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify('{{ tojson(value) }}'), + DEFAULT_RESPONSE_BODY_TRANSFORM: JSON.stringify( + '{ {% set first = true %} {% for key, value in input_request %} {% if key == "choices" or key == "created" or key == "model" or key == "service_tier" or key == "stream" or key == "object" or key == "usage" %} {% if not first %},{% endif %} "{{ key }}": {{ tojson(value) }} {% set first = false %} {% endif %} {% endfor %} }' + ), + DEFAULT_REQUEST_HEADERS_TRANSFORM: JSON.stringify( + 'Authorization: Bearer {{api_key}}' + ), }, }, { diff --git a/extensions/engine-management-extension/src/@types/global.d.ts b/extensions/engine-management-extension/src/@types/global.d.ts index 2d520d5f9..178509578 100644 --- a/extensions/engine-management-extension/src/@types/global.d.ts +++ b/extensions/engine-management-extension/src/@types/global.d.ts @@ -2,6 +2,9 @@ declare const API_URL: string declare const CORTEX_ENGINE_VERSION: string declare const SOCKET_URL: string declare const NODE: string +declare const DEFAULT_REQUEST_PAYLOAD_TRANSFORM: string +declare const DEFAULT_RESPONSE_BODY_TRANSFORM: string +declare const DEFAULT_REQUEST_HEADERS_TRANSFORM: string declare const DEFAULT_REMOTE_ENGINES: ({ id: string diff --git a/extensions/engine-management-extension/src/index.ts b/extensions/engine-management-extension/src/index.ts index 0d30bf4ea..4e3e30f75 100644 --- a/extensions/engine-management-extension/src/index.ts +++ b/extensions/engine-management-extension/src/index.ts @@ -20,6 +20,9 @@ import PQueue from 'p-queue' import { EngineError } from './error' import { getJanDataFolderPath } from '@janhq/core' +interface ModelList { + data: Model[] +} /** * JSONEngineManagementExtension is a EngineManagementExtension implementation that provides * functionality for managing engines. @@ -63,13 +66,12 @@ export default class JSONEngineManagementExtension extends EngineManagementExten * @returns A Promise that resolves to an object of list engines. */ async getRemoteModels(name: string): Promise { - return this.queue.add(() => - ky - .get(`${API_URL}/v1/models/remote/${name}`) - .json() - .then((e) => e) - .catch(() => []) - ) as Promise + return ky + .get(`${API_URL}/v1/models/remote/${name}`) + .json() + .catch(() => ({ + data: [], + })) as Promise } /** @@ -138,9 +140,36 @@ export default class JSONEngineManagementExtension extends EngineManagementExten * Add a new remote engine * @returns A Promise that resolves to intall of engine. */ - async addRemoteEngine(engineConfig: EngineConfig) { + async addRemoteEngine( + engineConfig: EngineConfig, + persistModels: boolean = true + ) { + // Populate default settings + if ( + engineConfig.metadata?.transform_req?.chat_completions && + !engineConfig.metadata.transform_req.chat_completions.template + ) + engineConfig.metadata.transform_req.chat_completions.template = + DEFAULT_REQUEST_PAYLOAD_TRANSFORM + + if ( + engineConfig.metadata?.transform_resp?.chat_completions && + !engineConfig.metadata.transform_resp.chat_completions?.template + ) + engineConfig.metadata.transform_resp.chat_completions.template = + DEFAULT_RESPONSE_BODY_TRANSFORM + + if (engineConfig.metadata && !engineConfig.metadata?.header_template) + engineConfig.metadata.header_template = DEFAULT_REQUEST_HEADERS_TRANSFORM + return this.queue.add(() => - ky.post(`${API_URL}/v1/engines`, { json: engineConfig }).then((e) => e) + ky.post(`${API_URL}/v1/engines`, { json: engineConfig }).then((e) => { + if (persistModels && engineConfig.metadata?.get_models_url) { + // Pull /models from remote models endpoint + return this.populateRemoteModels(engineConfig).then(() => e) + } + return e + }) ) as Promise<{ messages: string }> } @@ -161,9 +190,11 @@ export default class JSONEngineManagementExtension extends EngineManagementExten * @param model - Remote model object. */ async addRemoteModel(model: Model) { - return this.queue.add(() => - ky.post(`${API_URL}/v1/models/add`, { json: model }).then((e) => e) - ) + return this.queue + .add(() => + ky.post(`${API_URL}/v1/models/add`, { json: model }).then((e) => e) + ) + .then(() => {}) } /** @@ -293,7 +324,7 @@ export default class JSONEngineManagementExtension extends EngineManagementExten data.api_key = api_key /// END - Migrate legacy api key settings - await this.addRemoteEngine(data).catch(console.error) + await this.addRemoteEngine(data, false).catch(console.error) }) ) events.emit(EngineEvent.OnEngineUpdate, {}) @@ -303,4 +334,27 @@ export default class JSONEngineManagementExtension extends EngineManagementExten events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) } } + + /** + * Pulls models list from the remote provider and persist + * @param engineConfig + * @returns + */ + private populateRemoteModels = async (engineConfig: EngineConfig) => { + return this.getRemoteModels(engineConfig.engine) + .then((models: ModelList) => { + Promise.all( + models.data?.map((model) => + this.addRemoteModel({ + ...model, + engine: engineConfig.engine as InferenceEngine, + model: model.model ?? model.id, + }).catch(console.info) + ) + ).then(() => { + events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) + }) + }) + .catch(console.info) + } } From f2bb9c91de40a160cdfe08c4eb720818fd2f403d Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Thu, 30 Jan 2025 21:20:55 +0700 Subject: [PATCH 05/59] chore: align item menu action error message (#4548) --- extensions/yarn.lock | 24 ++++++++++++------------ web/containers/ErrorMessage/index.tsx | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f2c3b342b..685b1f7a6 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -509,61 +509,61 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=f3025c&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/cda3dff029cc6ce8a9ddcd8ac3ff039b783eed9252c1c3f0b3f34a2cf68c00dc2755997b56c3c5796502aa7316b69b57758b15f338e64b4a8ef14b34d23b6c99 + checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 languageName: node linkType: hard diff --git a/web/containers/ErrorMessage/index.tsx b/web/containers/ErrorMessage/index.tsx index cd9334283..7db5d83b3 100644 --- a/web/containers/ErrorMessage/index.tsx +++ b/web/containers/ErrorMessage/index.tsx @@ -104,7 +104,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => { >
Error
-
+
Date: Sat, 1 Feb 2025 22:22:45 +0700 Subject: [PATCH 06/59] chore: reasoning block (#4551) * chore: reasoning text block * chore: update interface support all theme * chore: update failed test * chore: update state collapsed based on message index * fix: use reserve_id instead of message index * chore: clean up * chore: fix loading indicator --------- Co-authored-by: Louis --- extensions/yarn.lock | 24 ++++---- web/containers/Providers/ModelHandler.tsx | 8 ++- web/package.json | 2 +- .../ThreadCenterPanel/ChatBody/index.tsx | 7 ++- .../TextMessage/ThinkingBlock.tsx | 59 +++++++++++++++++++ .../ThreadCenterPanel/TextMessage/index.tsx | 25 +++++++- web/tailwind.config.js | 5 ++ yarn.lock | 59 ++++--------------- 8 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 685b1f7a6..1be5d6cef 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -509,61 +509,61 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=546b6d&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/abb0aeb570396ef5d2fe482410a178f422ced7da132fa28f92b930eba191439a6a9b3567641c283eb6bf6413ace2950669393fc8f1e415d442e22d7107c23a44 + checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 languageName: node linkType: hard diff --git a/web/containers/Providers/ModelHandler.tsx b/web/containers/Providers/ModelHandler.tsx index 2c027539e..ad6d817f1 100644 --- a/web/containers/Providers/ModelHandler.tsx +++ b/web/containers/Providers/ModelHandler.tsx @@ -290,7 +290,13 @@ export default function ModelHandler() { .catch(() => undefined) if (updatedMessage) { deleteMessage(message.id) - addNewMessage(updatedMessage) + addNewMessage({ + ...updatedMessage, + metadata: { + ...updatedMessage.metadata, + reserve_id: message.id, + }, + }) setTokenSpeed((prev) => prev ? { ...prev, message: updatedMessage.id } : undefined ) diff --git a/web/package.json b/web/package.json index c562b6aa6..0f66b3f00 100644 --- a/web/package.json +++ b/web/package.json @@ -56,7 +56,7 @@ "slate-react": "0.110.3", "swr": "^2.2.5", "tailwind-merge": "^2.0.0", - "tailwindcss": "3.3.5", + "tailwindcss": "3.4.17", "ulidx": "^2.3.0", "use-debounce": "^10.0.0", "uuid": "^9.0.1", diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx index c47d19d67..1f4308bf3 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx @@ -160,7 +160,12 @@ const ChatBody = memo( > {items.map((virtualRow) => (
diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx new file mode 100644 index 000000000..59ff720c8 --- /dev/null +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx @@ -0,0 +1,59 @@ +import React from 'react' + +import { atom, useAtom } from 'jotai' +import { ChevronDown, ChevronUp, Loader } from 'lucide-react' + +interface Props { + text: string + status: string + id: string +} + +const thinkingBlockStateAtom = atom<{ [id: string]: boolean }>({}) + +const ThinkingBlock = ({ id, text, status }: Props) => { + const [thinkingState, setThinkingState] = useAtom(thinkingBlockStateAtom) + + const isExpanded = thinkingState[id] ?? false + + const loading = !text.includes('') && status === 'pending' + + const handleClick = () => { + setThinkingState((prev) => ({ ...prev, [id]: !isExpanded })) + } + + if (!text.replace(/<\/?think>/g, '').trim()) return null + + return ( +
+
+
+ {loading && ( + + )} + +
+ + {isExpanded && ( +
+ {text.replace(/<\/?think>/g, '').trim()} +
+ )} +
+
+ ) +} + +export default ThinkingBlock diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx index a44adb431..efa2b084c 100644 --- a/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx @@ -16,6 +16,7 @@ import MessageToolbar from '../MessageToolbar' import DocMessage from './DocMessage' import ImageMessage from './ImageMessage' import { MarkdownTextMessage } from './MarkdownTextMessage' +import ThinkingBlock from './ThinkingBlock' import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom' import { @@ -41,6 +42,21 @@ const MessageContainer: React.FC< [props.content] ) + const { reasoningSegment, textSegment } = useMemo(() => { + const isThinking = text.includes('') && !text.includes('') + if (isThinking) return { reasoningSegment: text, textSegment: '' } + + const match = text.match(/([\s\S]*?)<\/think>/) + if (match?.index === undefined) + return { reasoningSegment: undefined, textSegment: text } + + const splitIndex = match.index + match[0].length + return { + reasoningSegment: text.slice(0, splitIndex), + textSegment: text.slice(splitIndex), + } + }, [text]) + const image = useMemo( () => props.content.find((e) => e.type === ContentType.Image)?.image_url?.url, @@ -144,9 +160,16 @@ const MessageContainer: React.FC< )} dir="ltr" > + {reasoningSegment && ( + + )}
diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 2d9685c1a..e7ad20019 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -18,6 +18,7 @@ module.exports = { 'slide-in': 'slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)', 'leave': 'leave 150ms ease-in forwards', 'bounce-right': 'bounce-right 3s infinite', + 'spin': 'spin 2s linear infinite', }, keyframes: { 'wave': { @@ -47,6 +48,10 @@ module.exports = { '40%': { transform: 'translateX(-8px)' }, '60%': { transform: 'translateX(-4px)' }, }, + 'spin': { + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, + }, }, extend: { fontFamily: { diff --git a/yarn.lock b/yarn.lock index fcd00767c..5367fc4f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1138,7 +1138,7 @@ __metadata: slate-react: "npm:0.110.3" swr: "npm:^2.2.5" tailwind-merge: "npm:^2.0.0" - tailwindcss: "npm:3.3.5" + tailwindcss: "npm:3.4.17" ts-jest: "npm:^29.2.5" typescript: "npm:^5.3.3" ulidx: "npm:^2.3.0" @@ -6141,7 +6141,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": +"chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -8451,7 +8451,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -11483,7 +11483,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.19.1, jiti@npm:^1.21.6": +"jiti@npm:^1.21.6": version: 1.21.7 resolution: "jiti@npm:1.21.7" bin: @@ -11946,7 +11946,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.5, lilconfig@npm:^2.1.0": +"lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.5": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" checksum: 10c0/64645641aa8d274c99338e130554abd6a0190533c0d9eb2ce7ebfaf2e05c7d9961f3ffe2bfa39efd3b60c521ba3dd24fa236fe2775fc38501bf82bf49d4678b8 @@ -14609,7 +14609,7 @@ __metadata: languageName: node linkType: hard -"postcss-load-config@npm:^4.0.1, postcss-load-config@npm:^4.0.2": +"postcss-load-config@npm:^4.0.2": version: 4.0.2 resolution: "postcss-load-config@npm:4.0.2" dependencies: @@ -14763,7 +14763,7 @@ __metadata: languageName: node linkType: hard -"postcss-nested@npm:^6.0.1, postcss-nested@npm:^6.2.0": +"postcss-nested@npm:^6.2.0": version: 6.2.0 resolution: "postcss-nested@npm:6.2.0" dependencies: @@ -14908,7 +14908,7 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9, postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2": +"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9, postcss-selector-parser@npm:^6.1.1, postcss-selector-parser@npm:^6.1.2": version: 6.1.2 resolution: "postcss-selector-parser@npm:6.1.2" dependencies: @@ -14983,7 +14983,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.23, postcss@npm:^8.4.47": +"postcss@npm:^8.4.47": version: 8.4.49 resolution: "postcss@npm:8.4.49" dependencies: @@ -15959,7 +15959,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.7, resolve@npm:^1.11.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.2, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:^1.1.7, resolve@npm:^1.11.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -15985,7 +15985,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.11.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.11.0#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -17449,7 +17449,7 @@ __metadata: languageName: node linkType: hard -"sucrase@npm:^3.32.0, sucrase@npm:^3.35.0": +"sucrase@npm:^3.35.0": version: 3.35.0 resolution: "sucrase@npm:3.35.0" dependencies: @@ -17561,40 +17561,7 @@ __metadata: languageName: node linkType: hard -"tailwindcss@npm:3.3.5": - version: 3.3.5 - resolution: "tailwindcss@npm:3.3.5" - dependencies: - "@alloc/quick-lru": "npm:^5.2.0" - arg: "npm:^5.0.2" - chokidar: "npm:^3.5.3" - didyoumean: "npm:^1.2.2" - dlv: "npm:^1.1.3" - fast-glob: "npm:^3.3.0" - glob-parent: "npm:^6.0.2" - is-glob: "npm:^4.0.3" - jiti: "npm:^1.19.1" - lilconfig: "npm:^2.1.0" - micromatch: "npm:^4.0.5" - normalize-path: "npm:^3.0.0" - object-hash: "npm:^3.0.0" - picocolors: "npm:^1.0.0" - postcss: "npm:^8.4.23" - postcss-import: "npm:^15.1.0" - postcss-js: "npm:^4.0.1" - postcss-load-config: "npm:^4.0.1" - postcss-nested: "npm:^6.0.1" - postcss-selector-parser: "npm:^6.0.11" - resolve: "npm:^1.22.2" - sucrase: "npm:^3.32.0" - bin: - tailwind: lib/cli.js - tailwindcss: lib/cli.js - checksum: 10c0/a57c0a9cdba9db19097e34e25b7e4690fab43f31ba200afc3bb9635a03036ca93e9884a17b616fb8a2486d57d2ecc9a06862ce4685b3ace57f7a67436e7594a0 - languageName: node - linkType: hard - -"tailwindcss@npm:^3.4.1": +"tailwindcss@npm:3.4.17, tailwindcss@npm:^3.4.1": version: 3.4.17 resolution: "tailwindcss@npm:3.4.17" dependencies: From 899f01d5c44e4bd7e23b4d97659914a0b8bd8016 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 3 Feb 2025 20:36:19 +0700 Subject: [PATCH 07/59] enhancement: update ui uploading files and images (#4562) --- web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx | 4 ++-- .../Thread/ThreadCenterPanel/FileUploadPreview/index.tsx | 6 +++--- .../Thread/ThreadCenterPanel/ImageUploadPreview/index.tsx | 7 ++----- .../Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx | 2 +- .../Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx index 457758749..e582450a9 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx @@ -138,14 +138,14 @@ const ChatInput = () => { return (
+ {renderPreview(fileUpload)}
- {renderPreview(fileUpload)} { return (
{!!fileUpload && ( -
+
@@ -35,10 +35,10 @@ const FileUploadPreview = () => {
- +
)} diff --git a/web/screens/Thread/ThreadCenterPanel/ImageUploadPreview/index.tsx b/web/screens/Thread/ThreadCenterPanel/ImageUploadPreview/index.tsx index 7fa9e417a..63103f1ec 100644 --- a/web/screens/Thread/ThreadCenterPanel/ImageUploadPreview/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ImageUploadPreview/index.tsx @@ -35,13 +35,10 @@ const ImageUploadPreview: React.FC = ({ file }) => { return (
-
+
{file.name} -
- {file.name.replaceAll(/[-._]/g, ' ')} -
diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx index 9e00d2b83..a010c230d 100644 --- a/web/screens/Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx @@ -16,7 +16,7 @@ const DocMessage = ({ const { onViewFile } = usePath() return ( -
+
onViewFile(`${id}.pdf`)} diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx index bfd13b0ef..65e43e6c4 100644 --- a/web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx @@ -22,7 +22,7 @@ export const RelativeImage = ({ className={onClick ? 'cursor-pointer' : 'cursor-default'} > {src} From 72b9aaeba18e37847fc991fe2bc59f8ae437d04c Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 3 Feb 2025 20:36:31 +0700 Subject: [PATCH 08/59] enhancement: remote engine model (#4560) * enhancement: add option delete model remote engine * enhancement: add menu delete remote engine models --- .../rolldown.config.mjs | 2 +- extensions/yarn.lock | 24 +++---- .../Settings/Engines/DeleteEngineVariant.tsx | 5 +- .../Settings/Engines/ModalDeleteModel.tsx | 62 +++++++++++++++++++ .../Settings/Engines/RemoteEngineSettings.tsx | 2 + 5 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 web/screens/Settings/Engines/ModalDeleteModel.tsx diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 7e8cdcd2b..3c281c34d 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -16,7 +16,7 @@ export default defineConfig([ CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), - DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify('{{ tojson(value) }}'), + DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify(`{ {% set first = true %} {% for key, value in input_request %} {% if key == "messages" or key == "model" or key == "temperature" or key == "store" or key == "max_tokens" or key == "stream" or key == "presence_penalty" or key == "metadata" or key == "frequency_penalty" or key == "tools" or key == "tool_choice" or key == "logprobs" or key == "top_logprobs" or key == "logit_bias" or key == "n" or key == "modalities" or key == "prediction" or key == "response_format" or key == "service_tier" or key == "seed" or key == "stop" or key == "stream_options" or key == "top_p" or key == "parallel_tool_calls" or key == "user" %} {% if not first %},{% endif %} "{{ key }}": {{ tojson(value) }} {% set first = false %} {% endif %} {% endfor %} }`), DEFAULT_RESPONSE_BODY_TRANSFORM: JSON.stringify( '{ {% set first = true %} {% for key, value in input_request %} {% if key == "choices" or key == "created" or key == "model" or key == "service_tier" or key == "stream" or key == "object" or key == "usage" %} {% if not first %},{% endif %} "{{ key }}": {{ tojson(value) }} {% set first = false %} {% endif %} {% endfor %} }' ), diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 1be5d6cef..aecb2a2b7 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -509,61 +509,61 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=af6a7b&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/482f1f5363ca64f48a40c3d2937ac07dcf42a6d2dd6c82356359cec986e9312611f84ca8cdaed4ef338d978cd163909a2708d75f8e957aa51b09447aae83eca0 + checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 languageName: node linkType: hard diff --git a/web/screens/Settings/Engines/DeleteEngineVariant.tsx b/web/screens/Settings/Engines/DeleteEngineVariant.tsx index 1033164e6..d21dac3d8 100644 --- a/web/screens/Settings/Engines/DeleteEngineVariant.tsx +++ b/web/screens/Settings/Engines/DeleteEngineVariant.tsx @@ -25,7 +25,7 @@ const DeleteEngineVariant = ({ return ( Delete {variant.name}} + title={Delete Variant} open={open} onOpenChange={() => setOpen(!open)} trigger={ @@ -39,7 +39,8 @@ const DeleteEngineVariant = ({ content={

- Are you sure you want to delete this variant? + Are you sure you want to delete {variant.name}? This action cannot + be undone.

{ + const [open, setOpen] = useState(false) + + const { deleteModel } = useDeleteModel() + + return ( + Delete Model} + open={open} + onOpenChange={() => setOpen(!open)} + trigger={ + + } + content={ +
+

+ Are you sure you want to delete {model.id}? This action cannot be + undone. +

+
+ { + setOpen(!open) + e.stopPropagation() + }} + > + + + + + +
+
+ } + /> + ) +} + +export default memo(ModalDeleteModel) diff --git a/web/screens/Settings/Engines/RemoteEngineSettings.tsx b/web/screens/Settings/Engines/RemoteEngineSettings.tsx index c8d7a4861..1c81ee2bb 100644 --- a/web/screens/Settings/Engines/RemoteEngineSettings.tsx +++ b/web/screens/Settings/Engines/RemoteEngineSettings.tsx @@ -28,6 +28,7 @@ import { updateEngine, useGetEngines } from '@/hooks/useEngineManagement' import { getTitleByEngine } from '@/utils/modelEngine' import ModalAddModel from './ModalAddModel' +import ModalDeleteModel from './ModalDeleteModel' import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom' @@ -218,6 +219,7 @@ const RemoteEngineSettings = ({ > {item.name} +
From daa7c0ca21376999632be6000032adc03bbcaf52 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 3 Feb 2025 22:01:08 +0700 Subject: [PATCH 09/59] feat: better hardware setting (#4471) * feat: better hardware setting * chore: update layout * feat: better hardware setting * chore: fix title section * chore: added hardware engine management * chore: integrate gpus and enable set gpu activate * chore: update calculate ram and vram * chore: update calulate vram and ram used * fix: set active gpus * chore: fix progress bar spacing * chore: always update cache vram gpu * chore: update cpu usage percentage * chore: fix type usage cpu * chore: update ram cpus usage getsystemmonitor from new api harware engine management system * test: update test case data using hardware management extension * chore: resolve conflict lock json * chore: cleanup app services * chore: update type OperationSystemInfo * chore: update app service * chore: show list gpus on system monitor * chore: remove monitoring extension * chore: update test case app service * chore: remove unused hooks useGpusSetting * chore: remove monitor from shource index * chore: fix test core * chore: update gpu and cpu info on engine management ext * chore: fix app service test * chore: update test appService include cpu info * chore: filter gpus show or hide on system monitor based activated gpu * chore: remove unused run_mode * chore: remove tensort * chore: update check gpu run_mode * chore: handle undefined gpus * chore: cleanup PR * chore: cleanup process node error * chore: fix type --- core/src/browser/extension.ts | 1 + .../browser/extensions/hardwareManagement.ts | 26 ++ core/src/browser/extensions/index.test.ts | 7 +- core/src/browser/extensions/index.ts | 10 +- .../src/browser/extensions/monitoring.test.ts | 42 -- core/src/browser/extensions/monitoring.ts | 20 - core/src/types/hardware/index.ts | 55 +++ core/src/types/index.test.ts | 2 - core/src/types/index.ts | 2 +- .../types/miscellaneous/systemResourceInfo.ts | 32 +- core/src/types/monitoring/index.test.ts | 13 - core/src/types/monitoring/index.ts | 2 - .../types/monitoring/monitoringInterface.ts | 29 -- core/src/types/monitoring/resourceInfo.ts | 6 - .../engine-management-extension/package.json | 2 - .../rolldown.config.mjs | 12 +- .../src/@types/global.d.ts | 1 + .../engine-management-extension/src/index.ts | 7 +- .../src/node/cpuInfo.ts | 27 -- .../src/node/index.test.ts | 121 +----- .../src/node/index.ts | 102 ----- .../engine-management-extension/src/utils.ts | 81 ++++ .../jest.config.js | 5 + .../package.json | 48 +++ .../rolldown.config.mjs | 17 + .../src/@types/global.d.ts | 12 + .../src/index.ts | 67 +++ .../tsconfig.json | 6 +- .../inference-cortex-extension/src/index.ts | 4 +- .../src/node/index.ts | 18 +- extensions/model-extension/src/index.ts | 14 - .../model-extension/src/legacy/download.ts | 38 -- extensions/monitoring-extension/README.md | 75 ---- extensions/monitoring-extension/bin/.gitkeep | 0 extensions/monitoring-extension/download.bat | 2 - extensions/monitoring-extension/package.json | 49 --- .../resources/settings.json | 22 - .../monitoring-extension/rolldown.config.mjs | 32 -- .../src/@types/global.d.ts | 19 - extensions/monitoring-extension/src/index.ts | 90 ---- .../monitoring-extension/src/node/index.ts | 389 ------------------ .../monitoring-extension/src/node/logger.ts | 142 ------- extensions/yarn.lock | 87 ++-- .../SystemMonitor/SystemMonitor.test.tsx | 2 +- .../BottomPanel/SystemMonitor/index.tsx | 68 +-- web/containers/ModelLabel/index.tsx | 9 +- web/helpers/atoms/App.atom.ts | 2 + web/hooks/useGetSystemResources.test.ts | 18 +- web/hooks/useGetSystemResources.ts | 57 +-- web/hooks/useGpuSetting.test.ts | 87 ---- web/hooks/useGpuSetting.ts | 21 - web/hooks/useHardwareManagement.ts | 99 +++++ web/hooks/useSettings.ts | 35 +- web/package.json | 1 + .../Hub/ModelList/ModelHeader/index.tsx | 2 +- web/screens/Settings/Advanced/index.tsx | 11 +- .../Settings/Engines/LocalEngineSettings.tsx | 9 +- web/screens/Settings/Hardware/index.tsx | 342 +++++++++++++++ web/screens/Settings/SettingDetail/index.tsx | 4 + web/screens/Settings/index.tsx | 1 + web/services/appService.test.ts | 35 +- web/services/appService.ts | 43 +- yarn.lock | 93 ++++- 63 files changed, 1095 insertions(+), 1580 deletions(-) create mode 100644 core/src/browser/extensions/hardwareManagement.ts delete mode 100644 core/src/browser/extensions/monitoring.test.ts delete mode 100644 core/src/browser/extensions/monitoring.ts create mode 100644 core/src/types/hardware/index.ts delete mode 100644 core/src/types/monitoring/index.test.ts delete mode 100644 core/src/types/monitoring/index.ts delete mode 100644 core/src/types/monitoring/monitoringInterface.ts delete mode 100644 core/src/types/monitoring/resourceInfo.ts delete mode 100644 extensions/engine-management-extension/src/node/cpuInfo.ts create mode 100644 extensions/engine-management-extension/src/utils.ts create mode 100644 extensions/hardware-management-extension/jest.config.js create mode 100644 extensions/hardware-management-extension/package.json create mode 100644 extensions/hardware-management-extension/rolldown.config.mjs create mode 100644 extensions/hardware-management-extension/src/@types/global.d.ts create mode 100644 extensions/hardware-management-extension/src/index.ts rename extensions/{monitoring-extension => hardware-management-extension}/tsconfig.json (65%) delete mode 100644 extensions/monitoring-extension/README.md delete mode 100644 extensions/monitoring-extension/bin/.gitkeep delete mode 100644 extensions/monitoring-extension/download.bat delete mode 100644 extensions/monitoring-extension/package.json delete mode 100644 extensions/monitoring-extension/resources/settings.json delete mode 100644 extensions/monitoring-extension/rolldown.config.mjs delete mode 100644 extensions/monitoring-extension/src/@types/global.d.ts delete mode 100644 extensions/monitoring-extension/src/index.ts delete mode 100644 extensions/monitoring-extension/src/node/index.ts delete mode 100644 extensions/monitoring-extension/src/node/logger.ts delete mode 100644 web/hooks/useGpuSetting.test.ts delete mode 100644 web/hooks/useGpuSetting.ts create mode 100644 web/hooks/useHardwareManagement.ts create mode 100644 web/screens/Settings/Hardware/index.tsx diff --git a/core/src/browser/extension.ts b/core/src/browser/extension.ts index 1d641980b..d768473c9 100644 --- a/core/src/browser/extension.ts +++ b/core/src/browser/extension.ts @@ -12,6 +12,7 @@ export enum ExtensionTypeEnum { SystemMonitoring = 'systemMonitoring', HuggingFace = 'huggingFace', Engine = 'engine', + Hardware = 'hardware', } export interface ExtensionType { diff --git a/core/src/browser/extensions/hardwareManagement.ts b/core/src/browser/extensions/hardwareManagement.ts new file mode 100644 index 000000000..1f7c36287 --- /dev/null +++ b/core/src/browser/extensions/hardwareManagement.ts @@ -0,0 +1,26 @@ +import { HardwareInformation } from '../../types' +import { BaseExtension, ExtensionTypeEnum } from '../extension' + +/** + * Engine management extension. Persists and retrieves engine management. + * @abstract + * @extends BaseExtension + */ +export abstract class HardwareManagementExtension extends BaseExtension { + type(): ExtensionTypeEnum | undefined { + return ExtensionTypeEnum.Hardware + } + + /** + * @returns A Promise that resolves to an object of list hardware. + */ + abstract getHardware(): Promise + + /** + * @returns A Promise that resolves to an object of set active gpus. + */ + abstract setAvtiveGpu(data: { gpus: number[] }): Promise<{ + message: string + activated_gpus: number[] + }> +} diff --git a/core/src/browser/extensions/index.test.ts b/core/src/browser/extensions/index.test.ts index 26cbda8c5..bc5a7c358 100644 --- a/core/src/browser/extensions/index.test.ts +++ b/core/src/browser/extensions/index.test.ts @@ -1,6 +1,5 @@ import { ConversationalExtension } from './index'; import { InferenceExtension } from './index'; -import { MonitoringExtension } from './index'; import { AssistantExtension } from './index'; import { ModelExtension } from './index'; import * as Engines from './index'; @@ -14,10 +13,6 @@ describe('index.ts exports', () => { expect(InferenceExtension).toBeDefined(); }); - test('should export MonitoringExtension', () => { - expect(MonitoringExtension).toBeDefined(); - }); - test('should export AssistantExtension', () => { expect(AssistantExtension).toBeDefined(); }); @@ -29,4 +24,4 @@ describe('index.ts exports', () => { test('should export Engines', () => { expect(Engines).toBeDefined(); }); -}); \ No newline at end of file +}); diff --git a/core/src/browser/extensions/index.ts b/core/src/browser/extensions/index.ts index 9dbfe1afe..f11c7b09f 100644 --- a/core/src/browser/extensions/index.ts +++ b/core/src/browser/extensions/index.ts @@ -9,10 +9,7 @@ export { ConversationalExtension } from './conversational' */ export { InferenceExtension } from './inference' -/** - * Monitoring extension for system monitoring. - */ -export { MonitoringExtension } from './monitoring' + /** * Assistant extension for managing assistants. @@ -33,3 +30,8 @@ export * from './engines' * Engines Management */ export * from './enginesManagement' + +/** + * Hardware Management + */ +export * from './hardwareManagement' diff --git a/core/src/browser/extensions/monitoring.test.ts b/core/src/browser/extensions/monitoring.test.ts deleted file mode 100644 index 9bba89a8c..000000000 --- a/core/src/browser/extensions/monitoring.test.ts +++ /dev/null @@ -1,42 +0,0 @@ - -import { ExtensionTypeEnum } from '../extension'; -import { MonitoringExtension } from './monitoring'; - -it('should have the correct type', () => { - class TestMonitoringExtension extends MonitoringExtension { - getGpuSetting(): Promise { - throw new Error('Method not implemented.'); - } - getResourcesInfo(): Promise { - throw new Error('Method not implemented.'); - } - getCurrentLoad(): Promise { - throw new Error('Method not implemented.'); - } - getOsInfo(): Promise { - throw new Error('Method not implemented.'); - } - } - const monitoringExtension = new TestMonitoringExtension(); - expect(monitoringExtension.type()).toBe(ExtensionTypeEnum.SystemMonitoring); -}); - - -it('should create an instance of MonitoringExtension', () => { - class TestMonitoringExtension extends MonitoringExtension { - getGpuSetting(): Promise { - throw new Error('Method not implemented.'); - } - getResourcesInfo(): Promise { - throw new Error('Method not implemented.'); - } - getCurrentLoad(): Promise { - throw new Error('Method not implemented.'); - } - getOsInfo(): Promise { - throw new Error('Method not implemented.'); - } - } - const monitoringExtension = new TestMonitoringExtension(); - expect(monitoringExtension).toBeInstanceOf(MonitoringExtension); -}); diff --git a/core/src/browser/extensions/monitoring.ts b/core/src/browser/extensions/monitoring.ts deleted file mode 100644 index cb544b6b7..000000000 --- a/core/src/browser/extensions/monitoring.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BaseExtension, ExtensionTypeEnum } from '../extension' -import { GpuSetting, MonitoringInterface, OperatingSystemInfo } from '../../types' - -/** - * Monitoring extension for system monitoring. - * @extends BaseExtension - */ -export abstract class MonitoringExtension extends BaseExtension implements MonitoringInterface { - /** - * Monitoring extension type. - */ - type(): ExtensionTypeEnum | undefined { - return ExtensionTypeEnum.SystemMonitoring - } - - abstract getGpuSetting(): Promise - abstract getResourcesInfo(): Promise - abstract getCurrentLoad(): Promise - abstract getOsInfo(): Promise -} diff --git a/core/src/types/hardware/index.ts b/core/src/types/hardware/index.ts new file mode 100644 index 000000000..f729ba4e5 --- /dev/null +++ b/core/src/types/hardware/index.ts @@ -0,0 +1,55 @@ +export type Cpu = { + arch: string + cores: number + instructions: string[] + model: string + usage: number +} + +export type GpuAdditionalInformation = { + compute_cap: string + driver_version: string +} + +export type Gpu = { + activated: boolean + additional_information: GpuAdditionalInformation + free_vram: number + id: string + name: string + total_vram: number + uuid: string + version: string +} + +export type Os = { + name: string + version: string +} + +export type Power = { + battery_life: number + charging_status: string + is_power_saving: boolean +} + +export type Ram = { + available: number + total: number + type: string +} + +export type Storage = { + available: number + total: number + type: string +} + +export type HardwareInformation = { + cpu: Cpu + gpus: Gpu[] + os: Os + power: Power + ram: Ram + storage: Storage +} diff --git a/core/src/types/index.test.ts b/core/src/types/index.test.ts index 9dc001c4d..d938feee9 100644 --- a/core/src/types/index.test.ts +++ b/core/src/types/index.test.ts @@ -4,7 +4,6 @@ import * as model from './model'; import * as thread from './thread'; import * as message from './message'; import * as inference from './inference'; -import * as monitoring from './monitoring'; import * as file from './file'; import * as config from './config'; import * as huggingface from './huggingface'; @@ -18,7 +17,6 @@ import * as setting from './setting'; expect(thread).toBeDefined(); expect(message).toBeDefined(); expect(inference).toBeDefined(); - expect(monitoring).toBeDefined(); expect(file).toBeDefined(); expect(config).toBeDefined(); expect(huggingface).toBeDefined(); diff --git a/core/src/types/index.ts b/core/src/types/index.ts index e30dd18c3..3d262a6b7 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -3,7 +3,6 @@ export * from './model' export * from './thread' export * from './message' export * from './inference' -export * from './monitoring' export * from './file' export * from './config' export * from './huggingface' @@ -11,3 +10,4 @@ export * from './miscellaneous' export * from './api' export * from './setting' export * from './engine' +export * from './hardware' diff --git a/core/src/types/miscellaneous/systemResourceInfo.ts b/core/src/types/miscellaneous/systemResourceInfo.ts index 82db5d941..c29327e74 100644 --- a/core/src/types/miscellaneous/systemResourceInfo.ts +++ b/core/src/types/miscellaneous/systemResourceInfo.ts @@ -1,33 +1,24 @@ + + export type SystemResourceInfo = { 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 + cpu?: any } export type GpuSettingInfo = { - id: string - vram: string - name: string - arch?: string + activated: boolean; + free_vram: number; + id: string; + name: string; + total_vram: number; + uuid: string; + version: string; } export type SystemInformation = { @@ -42,9 +33,6 @@ export type SupportedPlatform = SupportedPlatformTuple[number] export type OperatingSystemInfo = { platform: SupportedPlatform | 'unknown' arch: string - release: string - machine: string - version: string totalMem: number freeMem: number } diff --git a/core/src/types/monitoring/index.test.ts b/core/src/types/monitoring/index.test.ts deleted file mode 100644 index 56c5879e4..000000000 --- a/core/src/types/monitoring/index.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as monitoringInterface from './monitoringInterface' -import * as resourceInfo from './resourceInfo' - -import * as index from './index' - -it('should re-export all symbols from monitoringInterface and resourceInfo', () => { - for (const key in monitoringInterface) { - expect(index[key]).toBe(monitoringInterface[key]) - } - for (const key in resourceInfo) { - expect(index[key]).toBe(resourceInfo[key]) - } -}) diff --git a/core/src/types/monitoring/index.ts b/core/src/types/monitoring/index.ts deleted file mode 100644 index b96c518fd..000000000 --- a/core/src/types/monitoring/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './monitoringInterface' -export * from './resourceInfo' diff --git a/core/src/types/monitoring/monitoringInterface.ts b/core/src/types/monitoring/monitoringInterface.ts deleted file mode 100644 index 5ab1394a1..000000000 --- a/core/src/types/monitoring/monitoringInterface.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { GpuSetting, OperatingSystemInfo } from '../miscellaneous' - -/** - * Monitoring extension for system monitoring. - * @extends BaseExtension - */ -export interface MonitoringInterface { - /** - * Returns information about the system resources. - * @returns {Promise} A promise that resolves with the system resources information. - */ - getResourcesInfo(): Promise - - /** - * Returns the current system load. - * @returns {Promise} A promise that resolves with the current system load. - */ - getCurrentLoad(): Promise - - /** - * Returns the GPU configuration. - */ - getGpuSetting(): Promise - - /** - * Returns information about the operating system. - */ - getOsInfo(): Promise -} diff --git a/core/src/types/monitoring/resourceInfo.ts b/core/src/types/monitoring/resourceInfo.ts deleted file mode 100644 index b19da5462..000000000 --- a/core/src/types/monitoring/resourceInfo.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type ResourceInfo = { - mem: { - totalMemory: number - usedMemory: number - } -} diff --git a/extensions/engine-management-extension/package.json b/extensions/engine-management-extension/package.json index 96f962ffd..571a3852b 100644 --- a/extensions/engine-management-extension/package.json +++ b/extensions/engine-management-extension/package.json @@ -29,12 +29,10 @@ }, "dependencies": { "@janhq/core": "../../core/package.tgz", - "cpu-instructions": "^0.0.13", "ky": "^1.7.2", "p-queue": "^8.0.1" }, "bundledDependencies": [ - "cpu-instructions", "@janhq/core" ], "engines": { diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 3c281c34d..4b473d09b 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -13,6 +13,7 @@ export default defineConfig([ NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), API_URL: JSON.stringify('http://127.0.0.1:39291'), SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), + PLATFORM: JSON.stringify(process.platform), CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), @@ -36,15 +37,4 @@ export default defineConfig([ CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), }, }, - { - input: 'src/node/cpuInfo.ts', - output: { - format: 'cjs', - file: 'dist/node/cpuInfo.js', - }, - external: ['cpu-instructions'], - resolve: { - extensions: ['.ts', '.js', '.svg'], - }, - }, ]) diff --git a/extensions/engine-management-extension/src/@types/global.d.ts b/extensions/engine-management-extension/src/@types/global.d.ts index 178509578..55874ea9a 100644 --- a/extensions/engine-management-extension/src/@types/global.d.ts +++ b/extensions/engine-management-extension/src/@types/global.d.ts @@ -1,5 +1,6 @@ declare const API_URL: string declare const CORTEX_ENGINE_VERSION: string +declare const PLATFORM: string declare const SOCKET_URL: string declare const NODE: string declare const DEFAULT_REQUEST_PAYLOAD_TRANSFORM: string diff --git a/extensions/engine-management-extension/src/index.ts b/extensions/engine-management-extension/src/index.ts index 4e3e30f75..d9fbbdd97 100644 --- a/extensions/engine-management-extension/src/index.ts +++ b/extensions/engine-management-extension/src/index.ts @@ -19,6 +19,7 @@ import ky, { HTTPError } from 'ky' import PQueue from 'p-queue' import { EngineError } from './error' import { getJanDataFolderPath } from '@janhq/core' +import { engineVariant } from './utils' interface ModelList { data: Model[] @@ -276,11 +277,7 @@ export default class JSONEngineManagementExtension extends EngineManagementExten error instanceof EngineError ) { const systemInfo = await systemInformation() - const variant = await executeOnMain( - NODE, - 'engineVariant', - systemInfo.gpuSetting - ) + const variant = await engineVariant(systemInfo.gpuSetting) await this.setDefaultEngineVariant(InferenceEngine.cortex_llamacpp, { variant: variant, version: `${CORTEX_ENGINE_VERSION}`, diff --git a/extensions/engine-management-extension/src/node/cpuInfo.ts b/extensions/engine-management-extension/src/node/cpuInfo.ts deleted file mode 100644 index 4366a995b..000000000 --- a/extensions/engine-management-extension/src/node/cpuInfo.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { cpuInfo } from 'cpu-instructions' - -// Check the CPU info and determine the supported instruction set -const info = cpuInfo.cpuInfo().some((e) => e.toUpperCase() === 'AVX512') - ? 'avx512' - : cpuInfo.cpuInfo().some((e) => e.toUpperCase() === 'AVX2') - ? 'avx2' - : cpuInfo.cpuInfo().some((e) => e.toUpperCase() === 'AVX') - ? 'avx' - : 'noavx' - -// Send the result and wait for confirmation before exiting -new Promise((resolve, reject) => { - // @ts-ignore - process.send(info, (error: Error | null) => { - if (error) { - reject(error) - } else { - resolve() - } - }) -}) - .then(() => process.exit(0)) - .catch((error) => { - console.error('Failed to send info:', error) - process.exit(1) - }) diff --git a/extensions/engine-management-extension/src/node/index.test.ts b/extensions/engine-management-extension/src/node/index.test.ts index c73feb9c6..aa2ac8be8 100644 --- a/extensions/engine-management-extension/src/node/index.test.ts +++ b/extensions/engine-management-extension/src/node/index.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from '@jest/globals' import engine from './index' -import { GpuSetting } from '@janhq/core/node' -import { cpuInfo } from 'cpu-instructions' +import { GpuSetting } from '@janhq/core' import { fork } from 'child_process' let testSettings: GpuSetting = { @@ -23,22 +22,12 @@ let testSettings: GpuSetting = { } const originalPlatform = process.platform -jest.mock('cpu-instructions', () => ({ - cpuInfo: { - cpuInfo: jest.fn(), - }, -})) -let mockCpuInfo = cpuInfo.cpuInfo as jest.Mock -mockCpuInfo.mockReturnValue([]) -jest.mock('@janhq/core/node', () => ({ + +jest.mock('@janhq/core', () => ({ appResourcePath: () => '.', log: jest.fn(), })) -jest.mock('child_process', () => ({ - fork: jest.fn(), -})) -const mockFork = fork as jest.Mock describe('test executable cortex file', () => { afterAll(function () { @@ -48,14 +37,7 @@ describe('test executable cortex file', () => { }) it('executes on MacOS', () => { - const mockProcess = { - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('noavx') - } - }), - send: jest.fn(), - } + Object.defineProperty(process, 'platform', { value: 'darwin', }) @@ -63,7 +45,7 @@ describe('test executable cortex file', () => { value: 'arm64', }) - mockFork.mockReturnValue(mockProcess) + expect(engine.engineVariant(testSettings)).resolves.toEqual('mac-arm64') }) @@ -83,7 +65,7 @@ describe('test executable cortex file', () => { }), send: jest.fn(), } - mockFork.mockReturnValue(mockProcess) + Object.defineProperty(process, 'arch', { value: 'x64', }) @@ -107,7 +89,6 @@ describe('test executable cortex file', () => { }), send: jest.fn(), } - mockFork.mockReturnValue(mockProcess) expect(engine.engineVariant()).resolves.toEqual('windows-amd64-avx') }) @@ -145,7 +126,6 @@ describe('test executable cortex file', () => { }), send: jest.fn(), } - mockFork.mockReturnValue(mockProcess) expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-avx2-cuda-11-7' @@ -176,26 +156,11 @@ describe('test executable cortex file', () => { }, ], } - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('noavx') - } - }), - send: jest.fn(), - }) expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-noavx-cuda-12-0' ) - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('avx512') - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-avx2-cuda-12-0' ) @@ -209,14 +174,6 @@ describe('test executable cortex file', () => { ...testSettings, run_mode: 'cpu', } - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('noavx') - } - }), - send: jest.fn(), - }) expect(engine.engineVariant()).resolves.toEqual('linux-amd64-noavx') }) @@ -245,16 +202,6 @@ describe('test executable cortex file', () => { }, ], } - - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('avx512') - } - }), - send: jest.fn(), - }) - expect(engine.engineVariant(settings)).resolves.toBe( 'linux-amd64-avx2-cuda-11-7' ) @@ -284,14 +231,7 @@ describe('test executable cortex file', () => { }, ], } - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('avx2') - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( 'linux-amd64-avx2-cuda-12-0' @@ -310,15 +250,6 @@ describe('test executable cortex file', () => { const cpuInstructions = ['avx512', 'avx2', 'avx', 'noavx'] cpuInstructions.forEach((instruction) => { - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback(instruction) - } - }), - send: jest.fn(), - }) - expect(engine.engineVariant(settings)).resolves.toEqual( `linux-amd64-${instruction}` ) @@ -335,14 +266,7 @@ describe('test executable cortex file', () => { } const cpuInstructions = ['avx512', 'avx2', 'avx', 'noavx'] cpuInstructions.forEach((instruction) => { - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback(instruction) - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( `windows-amd64-${instruction}` ) @@ -376,14 +300,7 @@ describe('test executable cortex file', () => { } const cpuInstructions = ['avx512', 'avx2', 'avx', 'noavx'] cpuInstructions.forEach((instruction) => { - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback(instruction) - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( `windows-amd64-${instruction === 'avx512' || instruction === 'avx2' ? 'avx2' : 'noavx'}-cuda-12-0` ) @@ -417,14 +334,7 @@ describe('test executable cortex file', () => { ], } cpuInstructions.forEach((instruction) => { - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback(instruction) - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( `linux-amd64-${instruction === 'avx512' || instruction === 'avx2' ? 'avx2' : 'noavx'}-cuda-12-0` ) @@ -459,14 +369,7 @@ describe('test executable cortex file', () => { ], } cpuInstructions.forEach((instruction) => { - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback(instruction) - } - }), - send: jest.fn(), - }) + expect(engine.engineVariant(settings)).resolves.toEqual( `linux-amd64-vulkan` ) diff --git a/extensions/engine-management-extension/src/node/index.ts b/extensions/engine-management-extension/src/node/index.ts index 31ad90ed2..4c1daf998 100644 --- a/extensions/engine-management-extension/src/node/index.ts +++ b/extensions/engine-management-extension/src/node/index.ts @@ -2,111 +2,10 @@ import * as path from 'path' import { appResourcePath, getJanDataFolderPath, - GpuSetting, log, } from '@janhq/core/node' -import { fork } from 'child_process' import { mkdir, readdir, symlink } from 'fs/promises' -/** - * The GPU runMode that will be set - either 'vulkan', 'cuda', or empty for cpu. - * @param settings - * @returns - */ -const gpuRunMode = (settings?: GpuSetting): string => { - if (process.platform === 'darwin') - // MacOS now has universal binaries - return '' - - if (!settings) return '' - - return settings.vulkan === true || settings.run_mode === 'cpu' ? '' : 'cuda' -} - -/** - * The OS & architecture that the current process is running on. - * @returns win, mac-x64, mac-arm64, or linux - */ -const os = (): string => { - return process.platform === 'win32' - ? 'windows-amd64' - : process.platform === 'darwin' - ? process.arch === 'arm64' - ? 'mac-arm64' - : 'mac-amd64' - : 'linux-amd64' -} - -/** - * The CUDA version that will be set - either '11-7' or '12-0'. - * @param settings - * @returns - */ -const cudaVersion = (settings?: GpuSetting): '11-7' | '12-0' | undefined => { - const isUsingCuda = - settings?.vulkan !== true && - settings?.run_mode === 'gpu' && - !os().includes('mac') - - if (!isUsingCuda) return undefined - return settings?.cuda?.version === '11' ? '11-7' : '12-0' -} - -/** - * The CPU instructions that will be set - either 'avx512', 'avx2', 'avx', or 'noavx'. - * @returns - */ -const cpuInstructions = async (): Promise => { - if (process.platform === 'darwin') return '' - - const child = fork(path.join(__dirname, './cpuInfo.js')) // Path to the child process file - - return new Promise((resolve, reject) => { - child.on('message', (cpuInfo?: string) => { - resolve(cpuInfo ?? 'noavx') - child.kill() // Kill the child process after receiving the result - }) - - child.on('error', (err) => { - resolve('noavx') - child.kill() - }) - - child.on('exit', (code) => { - if (code !== 0) { - resolve('noavx') - child.kill() - } - }) - }) -} - -/** - * Find which variant to run based on the current platform. - */ -const engineVariant = async (gpuSetting?: GpuSetting): Promise => { - const cpuInstruction = await cpuInstructions() - log(`[CORTEX]: CPU instruction: ${cpuInstruction}`) - let engineVariant = [ - os(), - gpuSetting?.vulkan - ? 'vulkan' - : gpuRunMode(gpuSetting) !== 'cuda' - ? // CPU mode - support all variants - cpuInstruction - : // GPU mode - packaged CUDA variants of avx2 and noavx - cpuInstruction === 'avx2' || cpuInstruction === 'avx512' - ? 'avx2' - : 'noavx', - gpuRunMode(gpuSetting), - cudaVersion(gpuSetting), - ] - .filter((e) => !!e) - .join('-') - - log(`[CORTEX]: Engine variant: ${engineVariant}`) - return engineVariant -} /** * Create symlink to each variant for the default bundled version @@ -148,6 +47,5 @@ const symlinkEngines = async () => { } export default { - engineVariant, symlinkEngines, } diff --git a/extensions/engine-management-extension/src/utils.ts b/extensions/engine-management-extension/src/utils.ts new file mode 100644 index 000000000..588fd938c --- /dev/null +++ b/extensions/engine-management-extension/src/utils.ts @@ -0,0 +1,81 @@ + +import { + GpuSetting, + log, +} from '@janhq/core' + +/** + * The GPU runMode that will be set - either 'vulkan', 'cuda', or empty for cpu. + * @param settings + * @returns + */ + + const gpuRunMode = (settings?: GpuSetting): string => { + + if (!settings) return '' + + return settings.vulkan === true || + settings.gpus?.some((gpu) => gpu.activated !== true) + ? '' + : 'cuda' +} + +/** + * The OS & architecture that the current process is running on. + * @returns win, mac-x64, mac-arm64, or linux + */ +const os = (settings?: GpuSetting): string => { + return PLATFORM === 'win32' + ? 'windows-amd64' + : PLATFORM === 'darwin' + ? settings?.cpu?.arch === 'arm64' + ? 'mac-arm64' + : 'mac-amd64' + : 'linux-amd64' +} + +/** + * The CUDA version that will be set - either '11-7' or '12-0'. + * @param settings + * @returns + */ +const cudaVersion = (settings?: GpuSetting): '12-0' | '11-7' | undefined => { + const isUsingCuda = + settings?.vulkan !== true && + settings?.gpus?.some((gpu) => (gpu.activated === true ? 'gpu' : 'cpu')) && + !os().includes('mac') + + if (!isUsingCuda) return undefined + // return settings?.cuda?.version === '11' ? '11-7' : '12-0' + return settings.gpus?.some((gpu) => gpu.version.includes('12')) + ? '12-0' + : '11-7' +} + +/** + * The CPU instructions that will be set - either 'avx512', 'avx2', 'avx', or 'noavx'. + * @returns + */ + +/** + * Find which variant to run based on the current platform. + */ +export const engineVariant = async (gpuSetting?: GpuSetting): Promise => { + let engineVariant = [ + os(gpuSetting), + gpuSetting?.vulkan + ? 'vulkan' + : (gpuRunMode(gpuSetting) === 'cuda' && // GPU mode - packaged CUDA variants of avx2 and noavx + gpuSetting.cpu.instructions.some((inst) => inst === 'avx2')) || + gpuSetting.cpu.instructions.some((inst) => inst === 'avx512') + ? 'avx2' + : 'noavx', + gpuRunMode(gpuSetting), + cudaVersion(gpuSetting), + ] + .filter((e) => !!e) + .join('-') + + log(`[CORTEX]: Engine variant: ${engineVariant}`) + return engineVariant +} diff --git a/extensions/hardware-management-extension/jest.config.js b/extensions/hardware-management-extension/jest.config.js new file mode 100644 index 000000000..8bb37208d --- /dev/null +++ b/extensions/hardware-management-extension/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +} diff --git a/extensions/hardware-management-extension/package.json b/extensions/hardware-management-extension/package.json new file mode 100644 index 000000000..ec98c7440 --- /dev/null +++ b/extensions/hardware-management-extension/package.json @@ -0,0 +1,48 @@ +{ + "name": "@janhq/hardware-management-extension", + "productName": "Hardware Management", + "version": "1.0.0", + "description": "Manages Better Hardware settings.", + "main": "dist/index.js", + "node": "dist/node/index.cjs.js", + "author": "Jan ", + "license": "MIT", + "scripts": { + "test": "jest", + "build": "rolldown -c rolldown.config.mjs", + "codesign:darwin": "../../.github/scripts/auto-sign.sh", + "codesign:win32:linux": "echo 'No codesigning required'", + "codesign": "run-script-os", + "build:publish": "rimraf *.tgz --glob || true && yarn build && yarn codesign && npm pack && cpx *.tgz ../../pre-install" + }, + "exports": { + ".": "./dist/index.js", + "./main": "./dist/module.js" + }, + "devDependencies": { + "cpx": "^1.5.0", + "rimraf": "^3.0.2", + "rolldown": "^1.0.0-beta.1", + "run-script-os": "^1.1.6", + "ts-loader": "^9.5.0", + "typescript": "^5.3.3" + }, + "dependencies": { + "@janhq/core": "../../core/package.tgz", + "cpu-instructions": "^0.0.13", + "ky": "^1.7.2", + "p-queue": "^8.0.1" + }, + "bundledDependencies": [ + "cpu-instructions", + "@janhq/core" + ], + "hardwares": { + "node": ">=18.0.0" + }, + "files": [ + "dist/*", + "package.json", + "README.md" + ] +} diff --git a/extensions/hardware-management-extension/rolldown.config.mjs b/extensions/hardware-management-extension/rolldown.config.mjs new file mode 100644 index 000000000..7982ca555 --- /dev/null +++ b/extensions/hardware-management-extension/rolldown.config.mjs @@ -0,0 +1,17 @@ +import { defineConfig } from 'rolldown' +import pkgJson from './package.json' with { type: 'json' } + +export default defineConfig([ + { + input: 'src/index.ts', + output: { + format: 'esm', + file: 'dist/index.js', + }, + define: { + NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), + API_URL: JSON.stringify('http://127.0.0.1:39291'), + SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), + }, + }, +]) diff --git a/extensions/hardware-management-extension/src/@types/global.d.ts b/extensions/hardware-management-extension/src/@types/global.d.ts new file mode 100644 index 000000000..6639b9cbb --- /dev/null +++ b/extensions/hardware-management-extension/src/@types/global.d.ts @@ -0,0 +1,12 @@ +declare const API_URL: string +declare const SOCKET_URL: string +declare const NODE: string + +interface Core { + api: APIFunctions + events: EventEmitter +} +interface Window { + core?: Core | undefined + electronAPI?: any | undefined +} diff --git a/extensions/hardware-management-extension/src/index.ts b/extensions/hardware-management-extension/src/index.ts new file mode 100644 index 000000000..c2edc6159 --- /dev/null +++ b/extensions/hardware-management-extension/src/index.ts @@ -0,0 +1,67 @@ +import { + executeOnMain, + HardwareManagementExtension, + HardwareInformation, +} from '@janhq/core' +import ky from 'ky' +import PQueue from 'p-queue' + +/** + * JSONHardwareManagementExtension is a HardwareManagementExtension implementation that provides + * functionality for managing engines. + */ +export default class JSONHardwareManagementExtension extends HardwareManagementExtension { + queue = new PQueue({ concurrency: 1 }) + + /** + * Called when the extension is loaded. + */ + async onLoad() { + // Run Healthcheck + this.queue.add(() => this.healthz()) + } + + /** + * Called when the extension is unloaded. + */ + onUnload() {} + + /** + * Do health check on cortex.cpp + * @returns + */ + async healthz(): Promise { + return ky + .get(`${API_URL}/healthz`, { + retry: { limit: 20, delay: () => 500, methods: ['get'] }, + }) + .then(() => {}) + } + + /** + * @returns A Promise that resolves to an object of hardware. + */ + async getHardware(): Promise { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/hardware`) + .json() + .then((e) => e) + ) as Promise + } + + /** + * @returns A Promise that resolves to an object of set gpu activate. + */ + async setAvtiveGpu(data: { gpus: number[] }): Promise<{ + message: string + activated_gpus: number[] + }> { + return this.queue.add(() => + ky.post(`${API_URL}/v1/hardware/activate`, { json: data }).then((e) => e) + ) as Promise<{ + message: string + activated_gpus: number[] + }> + } +} diff --git a/extensions/monitoring-extension/tsconfig.json b/extensions/hardware-management-extension/tsconfig.json similarity index 65% rename from extensions/monitoring-extension/tsconfig.json rename to extensions/hardware-management-extension/tsconfig.json index 2477d58ce..72e1e1895 100644 --- a/extensions/monitoring-extension/tsconfig.json +++ b/extensions/hardware-management-extension/tsconfig.json @@ -8,7 +8,9 @@ "forceConsistentCasingInFileNames": true, "strict": false, "skipLibCheck": true, - "rootDir": "./src" + "rootDir": "./src", + "resolveJsonModule": true }, - "include": ["./src"] + "include": ["./src"], + "exclude": ["src/**/*.test.ts", "rolldown.config.mjs"] } diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index 84cc49b94..05efaf735 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -112,8 +112,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { if (!Number.isNaN(threads_number)) this.cpu_threads = threads_number // Run the process watchdog - const systemInfo = await systemInformation() - this.queue.add(() => executeOnMain(NODE, 'run', systemInfo)) + // const systemInfo = await systemInformation() + this.queue.add(() => executeOnMain(NODE, 'run')) this.queue.add(() => this.healthz()) this.subscribeToEvents() diff --git a/extensions/inference-cortex-extension/src/node/index.ts b/extensions/inference-cortex-extension/src/node/index.ts index 420c84b6e..1b82b7760 100644 --- a/extensions/inference-cortex-extension/src/node/index.ts +++ b/extensions/inference-cortex-extension/src/node/index.ts @@ -16,12 +16,14 @@ let watchdog: ProcessWatchdog | undefined = undefined * Spawns a Nitro subprocess. * @returns A promise that resolves when the Nitro subprocess is started. */ -function run(systemInfo?: SystemInformation): Promise { +function run(): Promise { log(`[CORTEX]:: Spawning cortex subprocess...`) return new Promise(async (resolve, reject) => { - let gpuVisibleDevices = systemInfo?.gpuSetting?.gpus_in_use.join(',') ?? '' - let binaryName = `cortex-server${process.platform === 'win32' ? '.exe' : ''}` + // let gpuVisibleDevices = systemInfo?.gpuSetting?.gpus_in_use.join(',') ?? '' + let binaryName = `cortex-server${ + process.platform === 'win32' ? '.exe' : '' + }` const binPath = path.join(__dirname, '..', 'bin') const executablePath = path.join(binPath, binaryName) @@ -48,11 +50,11 @@ function run(systemInfo?: SystemInformation): Promise { { env: { ...process.env, - CUDA_VISIBLE_DEVICES: gpuVisibleDevices, - // Vulkan - Support 1 device at a time for now - ...(gpuVisibleDevices?.length > 0 && { - GGML_VK_VISIBLE_DEVICES: gpuVisibleDevices, - }), + // CUDA_VISIBLE_DEVICES: gpuVisibleDevices, + // // Vulkan - Support 1 device at a time for now + // ...(gpuVisibleDevices?.length > 0 && { + // GGML_VK_VISIBLE_DEVICES: gpuVisibleDevices, + // }), }, cwd: sharedPath, } diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index 832f279f9..41081d99a 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -14,8 +14,6 @@ import { } from '@janhq/core' import { CortexAPI } from './cortex' import { scanModelsFolder } from './legacy/model-json' -import { downloadModel } from './legacy/download' -import { systemInformation } from '@janhq/core' import { deleteModelFiles } from './legacy/delete' export enum Settings { @@ -70,18 +68,6 @@ export default class JanModelExtension extends ModelExtension { * @returns A Promise that resolves when the model is downloaded. */ async pullModel(model: string, id?: string, name?: string): Promise { - if (id) { - const model: Model = ModelManager.instance().get(id) - // Clip vision model - should not be handled by cortex.cpp - // TensorRT model - should not be handled by cortex.cpp - if ( - model && - (model.engine === InferenceEngine.nitro_tensorrt_llm || - model.settings.vision_model) - ) { - return downloadModel(model, (await systemInformation()).gpuSetting) - } - } /** * Sending POST to /models/pull/{id} endpoint to pull the model */ diff --git a/extensions/model-extension/src/legacy/download.ts b/extensions/model-extension/src/legacy/download.ts index d4d6c62d9..570d0cd13 100644 --- a/extensions/model-extension/src/legacy/download.ts +++ b/extensions/model-extension/src/legacy/download.ts @@ -2,15 +2,12 @@ import { downloadFile, DownloadRequest, fs, - GpuSetting, - InferenceEngine, joinPath, Model, } from '@janhq/core' export const downloadModel = async ( model: Model, - gpuSettings?: GpuSetting, network?: { ignoreSSL?: boolean; proxy?: string } ): Promise => { const homedir = 'file://models' @@ -27,41 +24,6 @@ export const downloadModel = async ( JSON.stringify(model, null, 2) ) - if (model.engine === InferenceEngine.nitro_tensorrt_llm) { - if (!gpuSettings || gpuSettings.gpus.length === 0) { - console.error('No GPU found. Please check your GPU setting.') - return - } - const firstGpu = gpuSettings.gpus[0] - if (!firstGpu.name.toLowerCase().includes('nvidia')) { - console.error('No Nvidia GPU found. Please check your GPU setting.') - return - } - const gpuArch = firstGpu.arch - if (gpuArch === undefined) { - console.error('No GPU architecture found. Please check your GPU setting.') - return - } - - if (!supportedGpuArch.includes(gpuArch)) { - console.debug( - `Your GPU: ${JSON.stringify(firstGpu)} is not supported. Only 30xx, 40xx series are supported.` - ) - return - } - - const os = 'windows' // TODO: remove this hard coded value - - const newSources = model.sources.map((source) => { - const newSource = { ...source } - newSource.url = newSource.url - .replace(//g, os) - .replace(//g, gpuArch) - return newSource - }) - model.sources = newSources - } - console.debug(`Download sources: ${JSON.stringify(model.sources)}`) if (model.sources.length > 1) { diff --git a/extensions/monitoring-extension/README.md b/extensions/monitoring-extension/README.md deleted file mode 100644 index f9690da09..000000000 --- a/extensions/monitoring-extension/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# 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/monitoring-extension/bin/.gitkeep b/extensions/monitoring-extension/bin/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/extensions/monitoring-extension/download.bat b/extensions/monitoring-extension/download.bat deleted file mode 100644 index 14e0aadd9..000000000 --- a/extensions/monitoring-extension/download.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -.\node_modules\.bin\download https://catalog.jan.ai/vulkaninfoSDK.exe -o ./bin \ No newline at end of file diff --git a/extensions/monitoring-extension/package.json b/extensions/monitoring-extension/package.json deleted file mode 100644 index 2f827b41b..000000000 --- a/extensions/monitoring-extension/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@janhq/monitoring-extension", - "productName": "System Monitoring", - "version": "1.0.10", - "description": "Provides system health and OS level data.", - "main": "dist/index.js", - "node": "dist/node/index.cjs.js", - "author": "Jan ", - "license": "AGPL-3.0", - "scripts": { - "build": "rolldown -c rolldown.config.mjs && yarn download-artifacts", - "download-artifacts": "run-script-os && cpx \"bin/**\" \"dist/bin\"", - "download-artifacts:darwin": "echo 'No artifacts to download for darwin'", - "download-artifacts:win32": "download.bat", - "download-artifacts:linux": "download https://catalog.jan.ai/vulkaninfo -o ./bin && chmod +x ./bin/vulkaninfo", - "build:publish": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install" - }, - "exports": { - ".": "./dist/index.js", - "./main": "./dist/node/index.cjs.js" - }, - "devDependencies": { - "@types/node": "^20.11.4", - "@types/node-os-utils": "^1.3.4", - "cpx": "^1.5.0", - "download-cli": "^1.1.1", - "rimraf": "^3.0.2", - "rolldown": "1.0.0-beta.1", - "run-script-os": "^1.1.6", - "typescript": "^5.3.3" - }, - "dependencies": { - "@janhq/core": "../../core/package.tgz", - "node-os-utils": "^1.3.7" - }, - "files": [ - "dist/*", - "package.json", - "README.md" - ], - "bundleDependencies": [ - "node-os-utils", - "@janhq/core" - ], - "installConfig": { - "hoistingLimits": "workspaces" - }, - "packageManager": "yarn@4.5.3" -} diff --git a/extensions/monitoring-extension/resources/settings.json b/extensions/monitoring-extension/resources/settings.json deleted file mode 100644 index 40b0b97f9..000000000 --- a/extensions/monitoring-extension/resources/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "key": "log-enabled", - "title": "Enable App Logs", - "description": "Saves app logs locally on your computer. This enables you to send us crash reports.", - "controllerType": "checkbox", - "controllerProps": { - "value": true - } - }, - { - "key": "log-cleaning-interval", - "title": "Log Cleaning Interval", - "description": "Automatically delete local logs after a certain time interval (in milliseconds).", - "controllerType": "input", - "controllerProps": { - "value": "120000", - "placeholder": "Interval in milliseconds. E.g. 120000", - "textAlign": "right" - } - } -] \ No newline at end of file diff --git a/extensions/monitoring-extension/rolldown.config.mjs b/extensions/monitoring-extension/rolldown.config.mjs deleted file mode 100644 index 3533e052b..000000000 --- a/extensions/monitoring-extension/rolldown.config.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { defineConfig } from 'rolldown' -import packageJson from './package.json' with { type: 'json' } -import settingJson from './resources/settings.json' with { type: 'json' } - -export default defineConfig([ - { - input: 'src/index.ts', - output: { - format: 'esm', - file: 'dist/index.js', - }, - platform: 'browser', - define: { - NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`), - SETTINGS: JSON.stringify(settingJson), - }, - }, - { - input: 'src/node/index.ts', - external: ['@janhq/core/node'], - output: { - format: 'cjs', - file: 'dist/node/index.cjs.js', - sourcemap: false, - inlineDynamicImports: true, - }, - resolve: { - extensions: ['.js', '.ts', '.json'], - }, - platform: 'node', - }, -]) diff --git a/extensions/monitoring-extension/src/@types/global.d.ts b/extensions/monitoring-extension/src/@types/global.d.ts deleted file mode 100644 index 7536fabd8..000000000 --- a/extensions/monitoring-extension/src/@types/global.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -declare const NODE: string -declare const SETTINGS: SettingComponentProps[] - -type CpuGpuInfo = { - cpu: { - usage: number - } - gpu: GpuInfo[] -} - -type GpuInfo = { - id: string - name: string - temperature: string - utilization: string - memoryTotal: string - memoryFree: string - memoryUtilization: string -} diff --git a/extensions/monitoring-extension/src/index.ts b/extensions/monitoring-extension/src/index.ts deleted file mode 100644 index 5616c70a8..000000000 --- a/extensions/monitoring-extension/src/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - AppConfigurationEventName, - GpuSetting, - MonitoringExtension, - OperatingSystemInfo, - events, - executeOnMain, -} from '@janhq/core' - -enum Settings { - logEnabled = 'log-enabled', - logCleaningInterval = 'log-cleaning-interval', -} -/** - * JanMonitoringExtension is a extension that provides system monitoring functionality. - * It implements the MonitoringExtension interface from the @janhq/core package. - */ -export default class JanMonitoringExtension extends MonitoringExtension { - /** - * Called when the extension is loaded. - */ - async onLoad() { - // Register extension settings - this.registerSettings(SETTINGS) - - const logEnabled = await this.getSetting(Settings.logEnabled, true) - const logCleaningInterval = parseInt( - await this.getSetting(Settings.logCleaningInterval, '120000') - ) - // Register File Logger provided by this extension - await executeOnMain(NODE, 'registerLogger', { - logEnabled, - logCleaningInterval: isNaN(logCleaningInterval) - ? 120000 - : logCleaningInterval, - }) - - // Attempt to fetch nvidia info - await executeOnMain(NODE, 'updateNvidiaInfo') - events.emit(AppConfigurationEventName.OnConfigurationUpdate, {}) - } - - onSettingUpdate(key: string, value: T): void { - if (key === Settings.logEnabled) { - executeOnMain(NODE, 'updateLogger', { logEnabled: value }) - } else if (key === Settings.logCleaningInterval) { - executeOnMain(NODE, 'updateLogger', { logCleaningInterval: value }) - } - } - - /** - * Called when the extension is unloaded. - */ - onUnload(): void { - // Register File Logger provided by this extension - executeOnMain(NODE, 'unregisterLogger') - } - - /** - * Returns the GPU configuration. - * @returns A Promise that resolves to an object containing the GPU configuration. - */ - async getGpuSetting(): Promise { - return executeOnMain(NODE, 'getGpuConfig') - } - - /** - * Returns information about the system resources. - * @returns A Promise that resolves to an object containing information about the system resources. - */ - getResourcesInfo(): Promise { - return executeOnMain(NODE, 'getResourcesInfo') - } - - /** - * Returns information about the current system load. - * @returns A Promise that resolves to an object containing information about the current system load. - */ - getCurrentLoad(): Promise { - return executeOnMain(NODE, 'getCurrentLoad') - } - - /** - * Returns information about the OS - * @returns - */ - getOsInfo(): Promise { - return executeOnMain(NODE, 'getOsInfo') - } -} diff --git a/extensions/monitoring-extension/src/node/index.ts b/extensions/monitoring-extension/src/node/index.ts deleted file mode 100644 index e32f85082..000000000 --- a/extensions/monitoring-extension/src/node/index.ts +++ /dev/null @@ -1,389 +0,0 @@ -import { - GpuSetting, - GpuSettingInfo, - LoggerManager, - OperatingSystemInfo, - ResourceInfo, - SupportedPlatforms, - getJanDataFolderPath, - log, -} from '@janhq/core/node' -import { mem, cpu } from 'node-os-utils' -import { exec } from 'child_process' -import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs' -import path from 'path' -import os from 'os' -import { FileLogger } from './logger' - -/** - * Path to the settings directory - **/ -export const SETTINGS_DIR = path.join(getJanDataFolderPath(), 'settings') -/** - * Path to the settings file - **/ -export const GPU_INFO_FILE = path.join(SETTINGS_DIR, 'settings.json') - -/** - * Default GPU settings - * TODO: This needs to be refactored to support multiple accelerators - **/ -const DEFAULT_SETTINGS: GpuSetting = { - 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, -} - -export const getGpuConfig = async (): Promise => { - if (process.platform === 'darwin') return undefined - if (existsSync(GPU_INFO_FILE)) - return JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8')) - return DEFAULT_SETTINGS -} - -export const getResourcesInfo = async (): Promise => { - const ramUsedInfo = await mem.used() - const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024 - const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024 - - const resourceInfo: ResourceInfo = { - mem: { - totalMemory, - usedMemory, - }, - } - - return resourceInfo -} - -export const getCurrentLoad = () => - new Promise(async (resolve, reject) => { - const cpuPercentage = await cpu.usage() - let data = { - run_mode: 'cpu', - gpus_in_use: [], - } - - if (process.platform !== 'darwin') { - data = JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8')) - } - - if (data.run_mode === 'gpu' && data.gpus_in_use.length > 0) { - const gpuIds = data.gpus_in_use.join(',') - if (gpuIds !== '' && data['vulkan'] !== true) { - exec( - `nvidia-smi --query-gpu=index,name,temperature.gpu,utilization.gpu,memory.total,memory.free,utilization.memory --format=csv,noheader,nounits --id=${gpuIds}`, - (error, stdout, _) => { - if (error) { - console.error(`exec error: ${error}`) - throw new Error(error.message) - } - const gpuInfo: GpuInfo[] = stdout - .trim() - .split('\n') - .map((line) => { - const [ - id, - name, - temperature, - utilization, - memoryTotal, - memoryFree, - memoryUtilization, - ] = line.split(', ').map((item) => item.replace(/\r/g, '')) - return { - id, - name, - temperature, - utilization, - memoryTotal, - memoryFree, - memoryUtilization, - } - }) - - resolve({ - cpu: { usage: cpuPercentage }, - gpu: gpuInfo, - }) - } - ) - } else { - // Handle the case where gpuIds is empty - resolve({ - cpu: { usage: cpuPercentage }, - gpu: [], - }) - } - } else { - // Handle the case where run_mode is not 'gpu' or no GPUs are in use - resolve({ - cpu: { usage: cpuPercentage }, - gpu: [], - }) - } - }) - -/** - * This will retrieve GPU information and persist settings.json - * Will be called when the extension is loaded to turn on GPU acceleration if supported - */ -export const updateNvidiaInfo = async () => { - // ignore if macos - if (process.platform === 'darwin') return - - try { - JSON.parse(readFileSync(GPU_INFO_FILE, 'utf-8')) - } catch (error) { - if (!existsSync(SETTINGS_DIR)) { - mkdirSync(SETTINGS_DIR, { - recursive: true, - }) - } - writeFileSync(GPU_INFO_FILE, JSON.stringify(DEFAULT_SETTINGS, null, 2)) - } - - await updateNvidiaDriverInfo() - await updateGpuInfo() -} - -const updateNvidiaDriverInfo = async () => - new Promise((resolve, reject) => { - exec( - 'nvidia-smi --query-gpu=driver_version --format=csv,noheader', - (error, stdout) => { - const data: GpuSetting = 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)) - resolve({}) - } - ) - }) - -const getGpuArch = (gpuName: string): string => { - if (!gpuName.toLowerCase().includes('nvidia')) return 'unknown' - - if (gpuName.includes('30')) return 'ampere' - else if (gpuName.includes('40')) return 'ada' - else return 'unknown' -} - -const updateGpuInfo = async () => - new Promise((resolve, reject) => { - let data: GpuSetting = 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`, - async (error, stdout) => { - if (!error) { - const output = stdout.toString() - - log(output) - const gpuRegex = /GPU(\d+):(?:[\s\S]*?)deviceName\s*=\s*(.*)/g - - const gpus: GpuSettingInfo[] = [] - let match - while ((match = gpuRegex.exec(output)) !== null) { - const id = match[1] - const name = match[2] - const arch = getGpuArch(name) - gpus.push({ id, vram: '0', name, arch }) - } - 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 = await updateCudaExistence(data) - writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2)) - log(`[APP]::${JSON.stringify(data)}`) - resolve({}) - } else { - reject(error) - } - } - ) - } else { - exec( - 'nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits', - async (error, stdout) => { - if (!error) { - log(`[SPECS]::${stdout}`) - // Get GPU info and gpu has higher memory first - let highestVram = 0 - let highestVramId = '0' - const gpus: GpuSettingInfo[] = stdout - .trim() - .split('\n') - .map((line) => { - let [id, vram, name] = line.split(', ') - const arch = getGpuArch(name) - vram = vram.replace(/\r/g, '') - if (parseFloat(vram) > highestVram) { - highestVram = parseFloat(vram) - highestVramId = id - } - return { id, vram, name, arch } - }) - - data.gpus = gpus - data.gpu_highest_vram = highestVramId - } else { - data.gpus = [] - data.gpu_highest_vram = undefined - } - - if (!data.gpus_in_use || data.gpus_in_use.length === 0) { - data.gpus_in_use = data.gpu_highest_vram ? [data.gpu_highest_vram].filter(e => !!e) : [] - } - - data = await updateCudaExistence(data) - console.log('[MONITORING]::Cuda info: ', data) - writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2)) - log(`[APP]::${JSON.stringify(data)}`) - resolve({}) - } - ) - } - }) - -/** - * Check if file exists in paths - */ -const checkFileExistenceInPaths = (file: string, paths: string[]): boolean => { - return paths.some((p) => existsSync(path.join(p, file))) -} - -/** - * Validate cuda for linux and windows - */ -const updateCudaExistence = async ( - data: GpuSetting = DEFAULT_SETTINGS -): Promise => { - 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_110.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 - - // Attempt to query CUDA using NVIDIA SMI - if (!cudaExists) { - await new Promise((resolve) => { - exec('nvidia-smi', (error, stdout) => { - if (!error) { - const regex = /CUDA\s*Version:\s*(\d+\.\d+)/g - const match = regex.exec(stdout) - if (match && match[1]) { - data.cuda.version = match[1] - } - } - console.log('[MONITORING]::Finalized cuda info update: ', data) - resolve() - }) - }) - } - return data -} - -export const getOsInfo = (): OperatingSystemInfo => { - const platform = - SupportedPlatforms.find((p) => p === process.platform) || 'unknown' - - const osInfo: OperatingSystemInfo = { - platform: platform, - arch: process.arch, - release: os.release(), - machine: os.machine(), - version: os.version(), - totalMem: os.totalmem(), - freeMem: os.freemem(), - } - - return osInfo -} - -export const registerLogger = ({ logEnabled, logCleaningInterval }) => { - const logger = new FileLogger(logEnabled, logCleaningInterval) - LoggerManager.instance().register(logger) - logger.cleanLogs() -} - -export const unregisterLogger = () => { - LoggerManager.instance().unregister('file') -} - -export const updateLogger = ({ logEnabled, logCleaningInterval }) => { - const logger = LoggerManager.instance().loggers.get('file') as FileLogger - if (logger && logEnabled !== undefined) logger.logEnabled = logEnabled - if (logger && logCleaningInterval) - logger.logCleaningInterval = logCleaningInterval - // Rerun - logger && logger.cleanLogs() -} diff --git a/extensions/monitoring-extension/src/node/logger.ts b/extensions/monitoring-extension/src/node/logger.ts deleted file mode 100644 index ca64ea2d9..000000000 --- a/extensions/monitoring-extension/src/node/logger.ts +++ /dev/null @@ -1,142 +0,0 @@ -import fs from 'fs' -import util from 'util' -import { - getAppConfigurations, - getJanDataFolderPath, - Logger, -} from '@janhq/core/node' -import path, { join } from 'path' - -export class FileLogger extends Logger { - name = 'file' - logCleaningInterval: number = 120000 - timeout: NodeJS.Timeout | null = null - appLogPath: string = './' - logEnabled: boolean = true - - constructor( - logEnabled: boolean = true, - logCleaningInterval: number = 120000 - ) { - super() - this.logEnabled = logEnabled - if (logCleaningInterval) this.logCleaningInterval = logCleaningInterval - - const appConfigurations = getAppConfigurations() - const logFolderPath = join(appConfigurations.data_folder, 'logs') - if (!fs.existsSync(logFolderPath)) { - fs.mkdirSync(logFolderPath, { recursive: true }) - } - - this.appLogPath = join(logFolderPath, 'app.log') - } - - log(args: any) { - if (!this.logEnabled) return - let message = args[0] - const scope = args[1] - if (!message) return - const path = this.appLogPath - if (!scope && !message.startsWith('[')) { - message = `[APP]::${message}` - } else if (scope) { - message = `${scope}::${message}` - } - - message = `${new Date().toISOString()} ${message}` - - writeLog(message, path) - } - - cleanLogs( - maxFileSizeBytes?: number | undefined, - daysToKeep?: number | undefined - ): void { - // clear existing timeout - // in case we rerun it with different values - if (this.timeout) clearTimeout(this.timeout) - this.timeout = undefined - - if (!this.logEnabled) return - - console.log( - 'Validating app logs. Next attempt in ', - this.logCleaningInterval - ) - - const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB - const days = daysToKeep ?? 7 // 7 days - const logDirectory = path.join(getJanDataFolderPath(), 'logs') - // Perform log cleaning - const currentDate = new Date() - if (fs.existsSync(logDirectory)) - fs.readdir(logDirectory, (err, files) => { - if (err) { - console.error('Error reading log directory:', err) - return - } - - files.forEach((file) => { - const filePath = path.join(logDirectory, file) - fs.stat(filePath, (err, stats) => { - if (err) { - console.error('Error getting file stats:', err) - return - } - - // Check size - if (stats.size > size) { - fs.unlink(filePath, (err) => { - if (err) { - console.error('Error deleting log file:', err) - return - } - console.debug( - `Deleted log file due to exceeding size limit: ${filePath}` - ) - }) - } else { - // Check age - const creationDate = new Date(stats.ctime) - const daysDifference = Math.floor( - (currentDate.getTime() - creationDate.getTime()) / - (1000 * 3600 * 24) - ) - if (daysDifference > days) { - fs.unlink(filePath, (err) => { - if (err) { - console.error('Error deleting log file:', err) - return - } - console.debug(`Deleted old log file: ${filePath}`) - }) - } - } - }) - }) - }) - - // Schedule the next execution with doubled delays - this.timeout = setTimeout( - () => this.cleanLogs(maxFileSizeBytes, daysToKeep), - this.logCleaningInterval - ) - } -} - -const writeLog = (message: string, logPath: string) => { - if (!fs.existsSync(logPath)) { - const logDirectory = path.join(getJanDataFolderPath(), 'logs') - if (!fs.existsSync(logDirectory)) { - fs.mkdirSync(logDirectory) - } - fs.writeFileSync(logPath, message) - } else { - const logFile = fs.createWriteStream(logPath, { - flags: 'a', - }) - logFile.write(util.format(message) + '\n') - logFile.close() - console.debug(message) - } -} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index aecb2a2b7..b3dd81233 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -509,67 +509,83 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + languageName: node + linkType: hard + +"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension": + version: 0.1.10 + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" + dependencies: + rxjs: "npm:^7.8.1" + ulidx: "npm:^2.3.0" + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 - languageName: node - linkType: hard - -"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": - version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=c5ae26&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" - dependencies: - rxjs: "npm:^7.8.1" - ulidx: "npm:^2.3.0" - checksum: 10c0/8d0da05e8a691b55c4de0578f80b134695789a01e2b8e197846318467afa391ae68675fdc9bf2aa3f173563f0a01efb2cf1008564b1df8631355f0f4ae9c2772 + checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 languageName: node linkType: hard "@janhq/engine-management-extension@workspace:engine-management-extension": version: 0.0.0-use.local resolution: "@janhq/engine-management-extension@workspace:engine-management-extension" + dependencies: + "@janhq/core": ../../core/package.tgz + cpx: "npm:^1.5.0" + ky: "npm:^1.7.2" + p-queue: "npm:^8.0.1" + rimraf: "npm:^3.0.2" + rolldown: "npm:^1.0.0-beta.1" + run-script-os: "npm:^1.1.6" + ts-loader: "npm:^9.5.0" + typescript: "npm:^5.3.3" + languageName: unknown + linkType: soft + +"@janhq/hardware-management-extension@workspace:hardware-management-extension": + version: 0.0.0-use.local + resolution: "@janhq/hardware-management-extension@workspace:hardware-management-extension" dependencies: "@janhq/core": ../../core/package.tgz cpu-instructions: "npm:^0.0.13" @@ -630,23 +646,6 @@ __metadata: languageName: unknown linkType: soft -"@janhq/monitoring-extension@workspace:monitoring-extension": - version: 0.0.0-use.local - resolution: "@janhq/monitoring-extension@workspace:monitoring-extension" - dependencies: - "@janhq/core": ../../core/package.tgz - "@types/node": "npm:^20.11.4" - "@types/node-os-utils": "npm:^1.3.4" - cpx: "npm:^1.5.0" - download-cli: "npm:^1.1.1" - node-os-utils: "npm:^1.3.7" - rimraf: "npm:^3.0.2" - rolldown: "npm:1.0.0-beta.1" - run-script-os: "npm:^1.1.6" - typescript: "npm:^5.3.3" - languageName: unknown - linkType: soft - "@jest/console@npm:^29.7.0": version: 29.7.0 resolution: "@jest/console@npm:29.7.0" @@ -1877,13 +1876,6 @@ __metadata: languageName: node linkType: hard -"@types/node-os-utils@npm:^1.3.4": - version: 1.3.4 - resolution: "@types/node-os-utils@npm:1.3.4" - checksum: 10c0/d57bfa84862ee388f538e2bf38b5a6e6a555aebf6e50573ad5700f5858f657ee72388833aa7ed6c9d0b68ce0a6763802366326617b0d5f4d56cc3fe61dd617e1 - languageName: node - linkType: hard - "@types/node@npm:*": version: 22.10.2 resolution: "@types/node@npm:22.10.2" @@ -5999,13 +5991,6 @@ __metadata: languageName: node linkType: hard -"node-os-utils@npm:^1.3.7": - version: 1.3.7 - resolution: "node-os-utils@npm:1.3.7" - checksum: 10c0/88b8a4c7ed99ca0ca8f077f4f4672026e732605d5afb125e856de9ba1880b842facefa4c38f732f5cce20a34f9f471ce18a20c677dcdb702b4b68c17bacf9584 - languageName: node - linkType: hard - "node-releases@npm:^2.0.19": version: 2.0.19 resolution: "node-releases@npm:2.0.19" diff --git a/web/containers/Layout/BottomPanel/SystemMonitor/SystemMonitor.test.tsx b/web/containers/Layout/BottomPanel/SystemMonitor/SystemMonitor.test.tsx index 2eba5edbb..ca336b0e5 100644 --- a/web/containers/Layout/BottomPanel/SystemMonitor/SystemMonitor.test.tsx +++ b/web/containers/Layout/BottomPanel/SystemMonitor/SystemMonitor.test.tsx @@ -87,7 +87,7 @@ describe('SystemMonitor', () => { expect(screen.getByText('Running Models')).toBeInTheDocument() expect(screen.getByText('App Log')).toBeInTheDocument() - expect(screen.getByText('7.45/14.90 GB')).toBeInTheDocument() + expect(screen.getByText('7.45GB / 14.90GB')).toBeInTheDocument() expect(screen.getByText('30%')).toBeInTheDocument() }) diff --git a/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx b/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx index 3dfdff2f9..bbced52f7 100644 --- a/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx +++ b/web/containers/Layout/BottomPanel/SystemMonitor/index.tsx @@ -134,8 +134,8 @@ const SystemMonitor = () => {
Memory
- {toGibibytes(usedRam, { hideUnit: true })}/ - {toGibibytes(totalRam, { hideUnit: true })} GB + {toGibibytes(usedRam, { hideUnit: true })}GB /{' '} + {toGibibytes(totalRam, { hideUnit: true })}GB
@@ -149,41 +149,43 @@ const SystemMonitor = () => {
{gpus.length > 0 && (
- {gpus.map((gpu, index) => { - const gpuUtilization = utilizedMemory( - gpu.memoryFree, - gpu.memoryTotal - ) - return ( -
-
- - {gpu.name} - -
-
- - {gpu.memoryTotal - gpu.memoryFree}/ - {gpu.memoryTotal} - - MB + {gpus + .filter((gpu) => gpu.activated === true) + .map((gpu, index) => { + const gpuUtilization = utilizedMemory( + gpu.free_vram, + gpu.total_vram + ) + return ( +
+
+ + {gpu.name} + +
+
+ + {gpu.total_vram - gpu.free_vram}/ + {gpu.total_vram} + + MB +
-
-
- - - {gpuUtilization}% - +
+ + + {gpuUtilization}% + +
-
- ) - })} + ) + })}
)}
diff --git a/web/containers/ModelLabel/index.tsx b/web/containers/ModelLabel/index.tsx index a6237ada6..2beb4a7ec 100644 --- a/web/containers/ModelLabel/index.tsx +++ b/web/containers/ModelLabel/index.tsx @@ -39,14 +39,13 @@ const ModelLabel = ({ metadata, compact }: Props) => { const getLabel = (size: number) => { const minimumRamModel = size * 1.25 - const availableRam = - settings?.run_mode === 'gpu' - ? availableVram * 1000000 // MB to bytes - : totalRam - usedRam + (activeModel?.metadata?.size ?? 0) + const availableRam = settings?.gpus?.some((gpu) => gpu.activated) + ? availableVram * 1000000 // MB to bytes + : totalRam - usedRam + (activeModel?.metadata?.size ?? 0) if (minimumRamModel > totalRam) { return ( gpu.activated) ? 'VRAM' : 'RAM'} compact={compact} /> ) diff --git a/web/helpers/atoms/App.atom.ts b/web/helpers/atoms/App.atom.ts index 7321fd9b4..9688c71f9 100644 --- a/web/helpers/atoms/App.atom.ts +++ b/web/helpers/atoms/App.atom.ts @@ -8,6 +8,8 @@ export const mainViewStateAtom = atom(MainViewState.Thread) export const defaultJanDataFolderAtom = atom('') +export const LocalEngineDefaultVariantAtom = atom('') + const SHOW_RIGHT_PANEL = 'showRightPanel' // Store panel atom diff --git a/web/hooks/useGetSystemResources.test.ts b/web/hooks/useGetSystemResources.test.ts index 10e539e07..78392b612 100644 --- a/web/hooks/useGetSystemResources.test.ts +++ b/web/hooks/useGetSystemResources.test.ts @@ -21,7 +21,7 @@ jest.mock('jotai', () => ({ describe('useGetSystemResources', () => { const mockMonitoringExtension = { - getResourcesInfo: jest.fn(), + getHardware: jest.fn(), getCurrentLoad: jest.fn(), } @@ -38,17 +38,17 @@ describe('useGetSystemResources', () => { }) it('should fetch system resources on initial render', async () => { - mockMonitoringExtension.getResourcesInfo.mockResolvedValue({ - mem: { usedMemory: 4000, totalMemory: 8000 }, + mockMonitoringExtension.getHardware.mockResolvedValue({ + cpu: { usage: 50 }, + ram: { available: 4000, total: 8000 }, }) mockMonitoringExtension.getCurrentLoad.mockResolvedValue({ - cpu: { usage: 50 }, gpu: [], }) const { result } = renderHook(() => useGetSystemResources()) - expect(mockMonitoringExtension.getResourcesInfo).toHaveBeenCalledTimes(1) + expect(mockMonitoringExtension.getHardware).toHaveBeenCalledTimes(1) }) it('should start watching system resources when watch is called', () => { @@ -58,14 +58,14 @@ describe('useGetSystemResources', () => { result.current.watch() }) - expect(mockMonitoringExtension.getResourcesInfo).toHaveBeenCalled() + expect(mockMonitoringExtension.getHardware).toHaveBeenCalled() // Fast-forward time by 2 seconds act(() => { jest.advanceTimersByTime(2000) }) - expect(mockMonitoringExtension.getResourcesInfo).toHaveBeenCalled() + expect(mockMonitoringExtension.getHardware).toHaveBeenCalled() }) it('should stop watching when stopWatching is called', () => { @@ -85,7 +85,7 @@ describe('useGetSystemResources', () => { }) // Expect no additional calls after stopping - expect(mockMonitoringExtension.getResourcesInfo).toHaveBeenCalled() + expect(mockMonitoringExtension.getHardware).toHaveBeenCalled() }) it('should not fetch resources if monitoring extension is not available', async () => { @@ -97,7 +97,7 @@ describe('useGetSystemResources', () => { result.current.getSystemResources() }) - expect(mockMonitoringExtension.getResourcesInfo).not.toHaveBeenCalled() + expect(mockMonitoringExtension.getHardware).not.toHaveBeenCalled() expect(mockMonitoringExtension.getCurrentLoad).not.toHaveBeenCalled() }) }) diff --git a/web/hooks/useGetSystemResources.ts b/web/hooks/useGetSystemResources.ts index a05a6a710..e40100a55 100644 --- a/web/hooks/useGetSystemResources.ts +++ b/web/hooks/useGetSystemResources.ts @@ -1,6 +1,7 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback, useEffect, useState } from 'react' -import { ExtensionTypeEnum, MonitoringExtension } from '@janhq/core' +import { ExtensionTypeEnum, HardwareManagementExtension } from '@janhq/core' import { useSetAtom } from 'jotai' @@ -20,58 +21,62 @@ export default function useGetSystemResources() { NodeJS.Timeout | number | undefined >(undefined) - const setTotalRam = useSetAtom(totalRamAtom) const setGpus = useSetAtom(gpusAtom) - const setUsedRam = useSetAtom(usedRamAtom) const setCpuUsage = useSetAtom(cpuUsageAtom) const setTotalNvidiaVram = useSetAtom(nvidiaTotalVramAtom) const setAvailableVram = useSetAtom(availableVramAtom) + const setUsedRam = useSetAtom(usedRamAtom) + const setTotalRam = useSetAtom(totalRamAtom) const setRamUtilitized = useSetAtom(ramUtilitizedAtom) const getSystemResources = useCallback(async () => { if ( - !extensionManager.get( - ExtensionTypeEnum.SystemMonitoring + !extensionManager.get( + ExtensionTypeEnum.Hardware ) ) { return } - const monitoring = extensionManager.get( - ExtensionTypeEnum.SystemMonitoring - ) - const resourceInfor = await monitoring?.getResourcesInfo() - const currentLoadInfor = await monitoring?.getCurrentLoad() - if (resourceInfor?.mem?.usedMemory) setUsedRam(resourceInfor.mem.usedMemory) - if (resourceInfor?.mem?.totalMemory) - setTotalRam(resourceInfor.mem.totalMemory) + const hardwareExtension = extensionManager.get( + ExtensionTypeEnum.Hardware + ) + + const hardwareInfo = await hardwareExtension?.getHardware() + + const usedMemory = + Number(hardwareInfo?.ram.total) - Number(hardwareInfo?.ram.available) + + if (hardwareInfo?.ram?.total && hardwareInfo?.ram?.available) + setUsedRam(Number(usedMemory)) + + if (hardwareInfo?.ram?.total) setTotalRam(hardwareInfo.ram.total) const ramUtilitized = - ((resourceInfor?.mem?.usedMemory ?? 0) / - (resourceInfor?.mem?.totalMemory ?? 1)) * - 100 + ((Number(usedMemory) ?? 0) / (hardwareInfo?.ram.total ?? 1)) * 100 + setRamUtilitized(Math.round(ramUtilitized)) - setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0)) + setCpuUsage(Math.round(hardwareInfo?.cpu.usage ?? 0)) - const gpus = currentLoadInfor?.gpu ?? [] - setGpus(gpus) + const gpus = hardwareInfo?.gpus ?? [] + setGpus(gpus as any) let totalNvidiaVram = 0 if (gpus.length > 0) { totalNvidiaVram = gpus.reduce( - (total: number, gpu: { memoryTotal: string }) => - total + Number(gpu.memoryTotal), + (total: number, gpu: { total_vram: number }) => + total + Number(gpu.total_vram), 0 ) } + setTotalNvidiaVram(totalNvidiaVram) + setAvailableVram( - gpus.reduce( - (total: number, gpu: { memoryFree: string }) => - total + Number(gpu.memoryFree), - 0 - ) + gpus.reduce((total, gpu) => { + return total + Number(gpu.free_vram || 0) + }, 0) ) }, [ setUsedRam, diff --git a/web/hooks/useGpuSetting.test.ts b/web/hooks/useGpuSetting.test.ts deleted file mode 100644 index f52f07af8..000000000 --- a/web/hooks/useGpuSetting.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -// useGpuSetting.test.ts - -import { renderHook, act } from '@testing-library/react' -import { ExtensionTypeEnum, MonitoringExtension } from '@janhq/core' - -// Mock dependencies -jest.mock('@/extension') - -import useGpuSetting from './useGpuSetting' -import { extensionManager } from '@/extension' - -describe('useGpuSetting', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should return GPU settings when available', async () => { - const mockGpuSettings = { - gpuCount: 2, - gpuNames: ['NVIDIA GeForce RTX 3080', 'NVIDIA GeForce RTX 3070'], - totalMemory: 20000, - freeMemory: 15000, - } - - const mockMonitoringExtension: Partial = { - getGpuSetting: jest.fn().mockResolvedValue(mockGpuSettings), - } - - jest - .spyOn(extensionManager, 'get') - .mockReturnValue(mockMonitoringExtension as MonitoringExtension) - - const { result } = renderHook(() => useGpuSetting()) - - let gpuSettings - await act(async () => { - gpuSettings = await result.current.getGpuSettings() - }) - - expect(gpuSettings).toEqual(mockGpuSettings) - expect(extensionManager.get).toHaveBeenCalledWith( - ExtensionTypeEnum.SystemMonitoring - ) - expect(mockMonitoringExtension.getGpuSetting).toHaveBeenCalled() - }) - - it('should return undefined when no GPU settings are found', async () => { - const mockMonitoringExtension: Partial = { - getGpuSetting: jest.fn().mockResolvedValue(undefined), - } - - jest - .spyOn(extensionManager, 'get') - .mockReturnValue(mockMonitoringExtension as MonitoringExtension) - - const { result } = renderHook(() => useGpuSetting()) - - let gpuSettings - await act(async () => { - gpuSettings = await result.current.getGpuSettings() - }) - - expect(gpuSettings).toBeUndefined() - expect(extensionManager.get).toHaveBeenCalledWith( - ExtensionTypeEnum.SystemMonitoring - ) - expect(mockMonitoringExtension.getGpuSetting).toHaveBeenCalled() - }) - - it('should handle missing MonitoringExtension', async () => { - jest.spyOn(extensionManager, 'get').mockReturnValue(undefined) - jest.spyOn(console, 'debug').mockImplementation(() => {}) - - const { result } = renderHook(() => useGpuSetting()) - - let gpuSettings - await act(async () => { - gpuSettings = await result.current.getGpuSettings() - }) - - expect(gpuSettings).toBeUndefined() - expect(extensionManager.get).toHaveBeenCalledWith( - ExtensionTypeEnum.SystemMonitoring - ) - expect(console.debug).toHaveBeenCalledWith('No GPU setting found') - }) -}) diff --git a/web/hooks/useGpuSetting.ts b/web/hooks/useGpuSetting.ts deleted file mode 100644 index 36f51ed57..000000000 --- a/web/hooks/useGpuSetting.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useCallback } from 'react' - -import { ExtensionTypeEnum, MonitoringExtension } from '@janhq/core' - -import { extensionManager } from '@/extension' - -export default function useGpuSetting() { - const getGpuSettings = useCallback(async () => { - const gpuSetting = await extensionManager - ?.get(ExtensionTypeEnum.SystemMonitoring) - ?.getGpuSetting() - - if (!gpuSetting) { - console.debug('No GPU setting found') - return undefined - } - return gpuSetting - }, []) - - return { getGpuSettings } -} diff --git a/web/hooks/useHardwareManagement.ts b/web/hooks/useHardwareManagement.ts new file mode 100644 index 000000000..d39b3c1fc --- /dev/null +++ b/web/hooks/useHardwareManagement.ts @@ -0,0 +1,99 @@ +import { useMemo } from 'react' + +import { ExtensionTypeEnum, HardwareManagementExtension } from '@janhq/core' + +import { useSetAtom } from 'jotai' +import useSWR from 'swr' + +import { extensionManager } from '@/extension/ExtensionManager' +import { + cpuUsageAtom, + ramUtilitizedAtom, + totalRamAtom, + usedRamAtom, +} from '@/helpers/atoms/SystemBar.atom' + +// fetcher function +async function fetchExtensionData( + extension: HardwareManagementExtension | null, + method: (extension: HardwareManagementExtension) => Promise +): Promise { + if (!extension) { + throw new Error('Extension not found') + } + return method(extension) +} + +const getExtension = () => + extensionManager.get( + ExtensionTypeEnum.Hardware + ) ?? null + +/** + * @returns A Promise that resolves to an object of list engines. + */ +export function useGetHardwareInfo() { + const setCpuUsage = useSetAtom(cpuUsageAtom) + const setUsedRam = useSetAtom(usedRamAtom) + const setTotalRam = useSetAtom(totalRamAtom) + const setRamUtilitized = useSetAtom(ramUtilitizedAtom) + + const extension = useMemo( + () => + extensionManager.get( + ExtensionTypeEnum.Hardware + ) ?? null, + [] + ) + + const { + data: hardware, + error, + mutate, + } = useSWR( + extension ? 'hardware' : null, + () => fetchExtensionData(extension, (ext) => ext.getHardware()), + { + revalidateOnFocus: false, + revalidateOnReconnect: false, + refreshInterval: 2000, + } + ) + + const usedMemory = + Number(hardware?.ram.total) - Number(hardware?.ram.available) + + if (hardware?.ram?.total && hardware?.ram?.available) + setUsedRam(Number(usedMemory)) + + if (hardware?.ram?.total) setTotalRam(hardware.ram.total) + + const ramUtilitized = + ((Number(usedMemory) ?? 0) / (hardware?.ram.total ?? 1)) * 100 + + setRamUtilitized(Math.round(ramUtilitized)) + + setCpuUsage(Math.round(hardware?.cpu.usage ?? 0)) + + return { hardware, error, mutate } +} + +/** + * set gpus activate + * @returns A Promise that resolves set gpus activate. + */ +export const setActiveGpus = async (data: { gpus: number[] }) => { + const extension = getExtension() + + if (!extension) { + throw new Error('Extension is not available') + } + + try { + const response = await extension.setAvtiveGpu(data) + return response + } catch (error) { + console.error('Failed to install engine variant:', error) + throw error + } +} diff --git a/web/hooks/useSettings.ts b/web/hooks/useSettings.ts index 0f02d41af..a9635aa93 100644 --- a/web/hooks/useSettings.ts +++ b/web/hooks/useSettings.ts @@ -1,20 +1,10 @@ import { useCallback, useEffect, useState } from 'react' -import { fs, joinPath } from '@janhq/core' - -type NvidiaDriver = { - exist: boolean - version: string -} +import { fs, GpuSettingInfo, joinPath } from '@janhq/core' export type AppSettings = { - run_mode: 'cpu' | 'gpu' | undefined - notify: boolean - gpus_in_use: string[] vulkan: boolean - gpus: string[] - nvidia_driver: NvidiaDriver - cuda: NvidiaDriver + gpus: GpuSettingInfo[] } export const useSettings = () => { @@ -38,29 +28,16 @@ export const useSettings = () => { return {} }, []) - const saveSettings = async ({ - runMode, - notify, - gpusInUse, - vulkan, - }: { - runMode?: string | undefined - notify?: boolean | undefined - gpusInUse?: string[] | undefined - vulkan?: boolean | undefined - }) => { + const saveSettings = async ({ vulkan }: { vulkan?: boolean | undefined }) => { const settingsFile = await joinPath(['file://settings', 'settings.json']) const settings = await readSettings() - if (runMode != null) settings.run_mode = runMode - if (notify != null) settings.notify = notify - if (gpusInUse != null) settings.gpus_in_use = gpusInUse.filter((e) => !!e) if (vulkan != null) { settings.vulkan = vulkan // GPU enabled, set run_mode to 'gpu' if (settings.vulkan === true) { - settings.run_mode = 'gpu' - } else { - settings.run_mode = 'cpu' + settings?.gpus?.some((gpu: { activated: boolean }) => + gpu.activated === true ? 'gpu' : 'cpu' + ) } } await fs.writeFileSync(settingsFile, JSON.stringify(settings)) diff --git a/web/package.json b/web/package.json index 0f66b3f00..da1c47c78 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "test": "jest" }, "dependencies": { + "@hello-pangea/dnd": "17.0.0", "@hookform/resolvers": "^3.9.1", "@janhq/core": "link:../core", "@janhq/joi": "link:../joi", diff --git a/web/screens/Hub/ModelList/ModelHeader/index.tsx b/web/screens/Hub/ModelList/ModelHeader/index.tsx index 9a939aa44..254ce0682 100644 --- a/web/screens/Hub/ModelList/ModelHeader/index.tsx +++ b/web/screens/Hub/ModelList/ModelHeader/index.tsx @@ -57,7 +57,7 @@ const ModelItemHeader = ({ model, onClick, open }: Props) => { // Default nvidia returns vram in MB, need to convert to bytes to match the unit of totalRamW let ram = nvidiaTotalVram * 1024 * 1024 - if (ram === 0 || settings?.run_mode === 'cpu') { + if (ram === 0 || settings?.gpus?.some((gpu) => gpu.activated !== true)) { ram = totalRam } const serverEnabled = useAtomValue(serverEnabledAtom) diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index 66240c028..1eb6e220a 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -123,7 +123,7 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => { }) stopModel() setVulkanEnabled(e) - await saveSettings({ vulkan: e, gpusInUse: [] }) + await saveSettings({ vulkan: e }) // Relaunch to apply settings if (relaunch) window.location.reload() } @@ -155,7 +155,11 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => { useEffect(() => { const setUseGpuIfPossible = async () => { const settings = await readSettings() - setGpuEnabled(settings.run_mode === 'gpu' && settings.gpus?.length > 0) + setGpuEnabled( + settings.gpus?.some( + (gpu: { activated: boolean }) => gpu.activated === true + ) === 'gpu' && settings.gpus?.length > 0 + ) setGpusInUse(settings.gpus_in_use || []) setVulkanEnabled(settings.vulkan || false) if (settings.gpus) { @@ -194,7 +198,6 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => { if (gpuId && gpuId.trim()) updatedGpusInUse.push(gpuId) } setGpusInUse(updatedGpusInUse) - await saveSettings({ gpusInUse: updatedGpusInUse.filter((e) => !!e) }) // Reload window to apply changes // This will trigger engine servers to restart window.location.reload() @@ -280,7 +283,6 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => { checked={gpuEnabled} onChange={(e) => { if (e.target.checked === true) { - saveSettings({ runMode: 'gpu' }) setGpuEnabled(true) snackbar({ description: @@ -288,7 +290,6 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => { type: 'success', }) } else { - saveSettings({ runMode: 'cpu' }) setGpuEnabled(false) snackbar({ description: diff --git a/web/screens/Settings/Engines/LocalEngineSettings.tsx b/web/screens/Settings/Engines/LocalEngineSettings.tsx index 47a5a7ffc..e19accffd 100644 --- a/web/screens/Settings/Engines/LocalEngineSettings.tsx +++ b/web/screens/Settings/Engines/LocalEngineSettings.tsx @@ -9,6 +9,7 @@ import { } from '@janhq/core' import { Button, ScrollArea, Badge, Select, Progress } from '@janhq/joi' +import { useAtom } from 'jotai' import { twMerge } from 'tailwind-merge' import { useActiveModel } from '@/hooks/useActiveModel' @@ -27,6 +28,8 @@ import { formatDownloadPercentage } from '@/utils/converter' import ExtensionSetting from '../ExtensionSetting' import DeleteEngineVariant from './DeleteEngineVariant' + +import { LocalEngineDefaultVariantAtom } from '@/helpers/atoms/App.atom' const os = () => { switch (PLATFORM) { case 'win32': @@ -86,8 +89,8 @@ const LocalEngineSettings = ({ engine }: { engine: InferenceEngine }) => { (x: any) => x.version === defaultEngineVariant?.version ) - const [selectedVariants, setSelectedVariants] = useState( - defaultEngineVariant?.variant + const [selectedVariants, setSelectedVariants] = useAtom( + LocalEngineDefaultVariantAtom ) const selectedVariant = useMemo( @@ -102,7 +105,7 @@ const LocalEngineSettings = ({ engine }: { engine: InferenceEngine }) => { if (defaultEngineVariant?.variant) { setSelectedVariants(defaultEngineVariant.variant || '') } - }, [defaultEngineVariant]) + }, [defaultEngineVariant, setSelectedVariants]) const handleEngineUpdate = useCallback( async (event: { id: string; type: DownloadEvent; percent: number }) => { diff --git a/web/screens/Settings/Hardware/index.tsx b/web/screens/Settings/Hardware/index.tsx new file mode 100644 index 000000000..7a13dbec9 --- /dev/null +++ b/web/screens/Settings/Hardware/index.tsx @@ -0,0 +1,342 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as React from 'react' + +import { useState } from 'react' + +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' +import { Gpu } from '@janhq/core' +import { Progress, ScrollArea, Switch } from '@janhq/joi' +import { useAtom, useAtomValue } from 'jotai' +import { atomWithStorage } from 'jotai/utils' + +import { ChevronDownIcon, GripVerticalIcon } from 'lucide-react' + +import { twMerge } from 'tailwind-merge' + +import { + useGetHardwareInfo, + setActiveGpus, +} from '@/hooks/useHardwareManagement' + +import { toGibibytes } from '@/utils/converter' + +import { + cpuUsageAtom, + ramUtilitizedAtom, + totalRamAtom, + usedRamAtom, +} from '@/helpers/atoms/SystemBar.atom' + +const gpusAtom = atomWithStorage('gpus', [], undefined, { + getOnInit: true, +}) + +const Hardware = () => { + const { hardware } = useGetHardwareInfo() + const [openPanels, setOpenPanels] = useState>({}) + + const cpuUsage = useAtomValue(cpuUsageAtom) + const totalRam = useAtomValue(totalRamAtom) + const usedRam = useAtomValue(usedRamAtom) + const ramUtilitized = useAtomValue(ramUtilitizedAtom) + + const [gpus, setGpus] = useAtom(gpusAtom) + + const togglePanel = (index: number) => { + setOpenPanels((prev) => ({ + ...prev, + [index]: !prev[index], // Toggle the specific panel + })) + } + + // Handle switch toggle for GPU activation + const handleSwitchChange = async (index: number, isActive: boolean) => { + const updatedGpus = gpus.map((gpu, i) => + i === index ? { ...gpu, activated: isActive } : gpu + ) + setGpus(updatedGpus) + // Call the API to update the active GPUs + try { + const activeGpuIds = updatedGpus + .filter((gpu) => gpu.activated) + .map((gpu) => Number(gpu.id)) + await setActiveGpus({ gpus: activeGpuIds }) + } catch (error) { + console.error('Failed to update active GPUs:', error) + } + } + + const handleDragEnd = (result: any) => { + if (!result.destination) return + const reorderedGpus = Array.from(gpus) + const [movedGpu] = reorderedGpus.splice(result.source.index, 1) + reorderedGpus.splice(result.destination.index, 0, movedGpu) + setGpus(reorderedGpus) // Update the atom, which persists to localStorage + } + + React.useEffect(() => { + if (hardware?.gpus) { + setGpus((prevGpus) => { + // Create a map of existing GPUs by UUID for quick lookup + const gpuMap = new Map(prevGpus.map((gpu) => [gpu.uuid, gpu])) + + // Update existing GPUs or add new ones + const updatedGpus = hardware.gpus.map((newGpu) => { + const existingGpu = gpuMap.get(newGpu.uuid) + + if (existingGpu) { + // Update the GPU properties while keeping the original order + return { + ...existingGpu, + free_vram: newGpu.free_vram, + total_vram: newGpu.total_vram, + } + } + + // Return the new GPU if not already in the state + return newGpu + }) + + // Append GPUs from the previous state that are not in the hardware.gpus + // This preserves user-reordered GPUs that aren't present in the new data + const remainingGpus = prevGpus.filter( + (prevGpu) => !hardware.gpus?.some((gpu) => gpu.uuid === prevGpu.uuid) + ) + + return [...updatedGpus, ...remainingGpus] + }) + } + }, [hardware?.gpus, setGpus]) + + return ( + +
+ {/* CPU */} +
+
+
+
CPU
+
+
+
+
+
+ {hardware?.cpu.model} + | + Cores: {hardware?.cpu.cores} + | + Architecture: {hardware?.cpu.arch} +
+
+ + {cpuUsage}% +
+
+
+
+ {/* RAM */} +
+
+
+
RAM
+
+
+
+
+
+ + {toGibibytes(usedRam, { hideUnit: true })}GB /{' '} + {toGibibytes(totalRam, { hideUnit: true })}GB + + {hardware?.ram.type && ( + <> + | + Type: {hardware?.ram.type} + + )} +
+
+ + {ramUtilitized}% +
+
+
+
+ {/* OS */} +
+
+
+
OS
+
+
+
+
+
+ {hardware?.os.name} + | + {hardware?.os.version} +
+
+
+
+ {/* GPUs */} + {!isMac && gpus.length > 0 && ( +
+
+
+
GPUs
+
+

+ {`Enhance model performance by utilizing your device's GPU for + acceleration.`} +

+ + + {(provided) => ( +
+ {gpus.map((item, i) => ( + + {(provided, snapshot) => ( +
1 && 'last:rounded-t-none', + snapshot.isDragging + ? 'border-b' + : 'border-b-0 last:border-b' + )} + onClick={() => togglePanel(i)} + > +
+
+
+ +
+
{item.name}
+
+
+ {item.activated && ( +
+ + + {Math.round( + ((Number(item.total_vram) - + Number(item.free_vram)) / + Number(item.total_vram)) * + 100 + ).toFixed()} + % + +
+ )} + +
+ {item.activated && ( + + {( + (Number(item.total_vram) - + Number(item.free_vram)) / + 1024 + ).toFixed(2)} + GB /{' '} + + )} + + {( + Number(item.total_vram) / 1024 + ).toFixed(2)} + GB + +
+ + + handleSwitchChange(i, e.target.checked) + } + /> + + +
+
+
+ + {openPanels[i] && ( +
+
+
+ Driver Version +
+ + { + item.additional_information + ?.driver_version + } + +
+
+
+ Compute Capability +
+ + {item.additional_information.compute_cap} + +
+
+ )} +
+ )} + + ))} + {provided.placeholder} +
+ )} + + +
+
+ )} +
+ + ) +} + +export default Hardware diff --git a/web/screens/Settings/SettingDetail/index.tsx b/web/screens/Settings/SettingDetail/index.tsx index 0d85ccbf4..8ceb600e6 100644 --- a/web/screens/Settings/SettingDetail/index.tsx +++ b/web/screens/Settings/SettingDetail/index.tsx @@ -13,6 +13,7 @@ import Engines from '@/screens/Settings/Engines' import LocalEngineSettings from '@/screens/Settings/Engines/LocalEngineSettings' import RemoteEngineSettings from '@/screens/Settings/Engines/RemoteEngineSettings' import ExtensionSetting from '@/screens/Settings/ExtensionSetting' +import Hardware from '@/screens/Settings/Hardware' import Hotkeys from '@/screens/Settings/Hotkeys' import MyModels from '@/screens/Settings/MyModels' import Privacy from '@/screens/Settings/Privacy' @@ -39,6 +40,9 @@ const SettingDetail = () => { case 'Keyboard Shortcuts': return + case 'Hardware': + return + case 'Privacy': return diff --git a/web/screens/Settings/index.tsx b/web/screens/Settings/index.tsx index 66e11d07e..d126f0d0e 100644 --- a/web/screens/Settings/index.tsx +++ b/web/screens/Settings/index.tsx @@ -15,6 +15,7 @@ export const SettingScreenList = [ 'My Models', 'Preferences', 'Keyboard Shortcuts', + 'Hardware', 'Privacy', 'Advanced Settings', 'Engines', diff --git a/web/services/appService.test.ts b/web/services/appService.test.ts index 5172ea6ed..52e8ed63b 100644 --- a/web/services/appService.test.ts +++ b/web/services/appService.test.ts @@ -1,23 +1,34 @@ import { extensionManager } from '@/extension' import { appService } from './appService' -test('should return correct system information when monitoring extension is found', async () => { - const mockGpuSetting = { name: 'NVIDIA GeForce GTX 1080', memory: 8192 } - const mockOsInfo = { platform: 'win32', release: '10.0.19041' } - const mockMonitoringExtension = { - getGpuSetting: jest.fn().mockResolvedValue(mockGpuSetting), - getOsInfo: jest.fn().mockResolvedValue(mockOsInfo), +test('should return correct system information when hardware extension is found', async () => { + + (global as any).isMac = false; + (global as any).PLATFORM = "win32"; + + const mock = { cpu: { arch: 'arc' }, ram: { available: 4000, total: 8000 }, gpus: [{name: 'NVIDIA GeForce GTX 1080', total_vram: 8192}] } + + const mockHardwareExtension = { + getHardware: jest.fn().mockResolvedValue(mock), } - extensionManager.get = jest.fn().mockReturnValue(mockMonitoringExtension) + extensionManager.get = jest.fn().mockReturnValue(mockHardwareExtension) const result = await appService.systemInformation() - expect(mockMonitoringExtension.getGpuSetting).toHaveBeenCalled() - expect(mockMonitoringExtension.getOsInfo).toHaveBeenCalled() - expect(result).toEqual({ gpuSetting: mockGpuSetting, osInfo: mockOsInfo }) + expect(mockHardwareExtension.getHardware).toHaveBeenCalled() + + expect(result).toEqual({ + gpuSetting: {gpus: mock.gpus, vulkan: false, cpu: {arch: mock.cpu.arch},}, + osInfo: { + platform: 'win32', + arch: mock.cpu.arch, + freeMem: mock.ram.available, + totalMem: mock.ram.total, + }, + }) }) -test('should log a warning when monitoring extension is not found', async () => { +test('should log a warning when hardware extension is not found', async () => { const consoleWarnMock = jest .spyOn(console, 'warn') .mockImplementation(() => {}) @@ -26,7 +37,7 @@ test('should log a warning when monitoring extension is not found', async () => await appService.systemInformation() expect(consoleWarnMock).toHaveBeenCalledWith( - 'System monitoring extension not found' + 'Hardware extension not found' ) consoleWarnMock.mockRestore() }) diff --git a/web/services/appService.ts b/web/services/appService.ts index 16060e2d9..642c4c8d4 100644 --- a/web/services/appService.ts +++ b/web/services/appService.ts @@ -1,29 +1,54 @@ import { ExtensionTypeEnum, - MonitoringExtension, + HardwareManagementExtension, + SupportedPlatform, SystemInformation, + GpuSetting, + GpuSettingInfo, } from '@janhq/core' +import { getDefaultStore } from 'jotai' + import { toaster } from '@/containers/Toast' import { extensionManager } from '@/extension' +import { LocalEngineDefaultVariantAtom } from '@/helpers/atoms/App.atom' + export const appService = { systemInformation: async (): Promise => { - const monitorExtension = extensionManager?.get( - ExtensionTypeEnum.SystemMonitoring + const selectedVariants = getDefaultStore().get( + LocalEngineDefaultVariantAtom ) - if (!monitorExtension) { - console.warn('System monitoring extension not found') + + const hardwareExtension = + extensionManager?.get( + ExtensionTypeEnum.Hardware + ) + + if (!hardwareExtension) { + console.warn('Hardware extension not found') return undefined } - const gpuSetting = await monitorExtension.getGpuSetting() - const osInfo = await monitorExtension.getOsInfo() + const hardwareInfo = await hardwareExtension?.getHardware() + + const gpuSettingInfo: GpuSetting | undefined = { + gpus: hardwareInfo.gpus as GpuSettingInfo[], + vulkan: isMac ? false : selectedVariants.includes('vulkan'), + cpu: hardwareInfo.cpu, + } + + const updateOsInfo = { + platform: PLATFORM as SupportedPlatform, + arch: hardwareInfo.cpu.arch, + freeMem: hardwareInfo.ram.available, + totalMem: hardwareInfo.ram.total, + } return { - gpuSetting, - osInfo, + gpuSetting: gpuSettingInfo, + osInfo: updateOsInfo, } }, diff --git a/yarn.lock b/yarn.lock index 5367fc4f6..b18b088a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -369,7 +369,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.12.5": +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.25.6": version: 7.26.0 resolution: "@babel/runtime@npm:7.26.0" dependencies: @@ -844,6 +844,24 @@ __metadata: languageName: node linkType: hard +"@hello-pangea/dnd@npm:17.0.0": + version: 17.0.0 + resolution: "@hello-pangea/dnd@npm:17.0.0" + dependencies: + "@babel/runtime": "npm:^7.25.6" + css-box-model: "npm:^1.2.1" + memoize-one: "npm:^6.0.0" + raf-schd: "npm:^4.0.3" + react-redux: "npm:^9.1.2" + redux: "npm:^5.0.1" + use-memo-one: "npm:^1.1.3" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10c0/93417c055267f6f12a37a1cdb08d9db85ab021b102315e1e5a70a79d7de6c2ffaeff211e3ec40441c110f39e60688cfcea85ab86c21820041d974415c1ca715e + languageName: node + linkType: hard + "@hookform/resolvers@npm:^3.9.1": version: 3.9.1 resolution: "@hookform/resolvers@npm:3.9.1" @@ -1067,6 +1085,7 @@ __metadata: version: 0.0.0-use.local resolution: "@janhq/web@workspace:web" dependencies: + "@hello-pangea/dnd": "npm:17.0.0" "@hookform/resolvers": "npm:^3.9.1" "@janhq/core": "link:../core" "@janhq/joi": "link:../joi" @@ -4408,6 +4427,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/use-sync-external-store@npm:0.0.6" + checksum: 10c0/77c045a98f57488201f678b181cccd042279aff3da34540ad242f893acc52b358bd0a8207a321b8ac09adbcef36e3236944390e2df4fcedb556ce7bb2a88f2a8 + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.6": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -6681,6 +6707,15 @@ __metadata: languageName: node linkType: hard +"css-box-model@npm:^1.2.1": + version: 1.2.1 + resolution: "css-box-model@npm:1.2.1" + dependencies: + tiny-invariant: "npm:^1.0.6" + checksum: 10c0/611e56d76b16e4e21956ed9fa53f1936fbbfaccd378659587e9c929f342037fc6c062f8af9447226e11fe7c95e31e6c007a37e592f9bff4c2d40e6915553104a + languageName: node + linkType: hard + "css-declaration-sorter@npm:^6.3.1": version: 6.4.1 resolution: "css-declaration-sorter@npm:6.4.1" @@ -12482,6 +12517,13 @@ __metadata: languageName: node linkType: hard +"memoize-one@npm:^6.0.0": + version: 6.0.0 + resolution: "memoize-one@npm:6.0.0" + checksum: 10c0/45c88e064fd715166619af72e8cf8a7a17224d6edf61f7a8633d740ed8c8c0558a4373876c9b8ffc5518c2b65a960266adf403cc215cb1e90f7e262b58991f54 + languageName: node + linkType: hard + "merge-stream@npm:^2.0.0": version: 2.0.0 resolution: "merge-stream@npm:2.0.0" @@ -15331,6 +15373,13 @@ __metadata: languageName: node linkType: hard +"raf-schd@npm:^4.0.3": + version: 4.0.3 + resolution: "raf-schd@npm:4.0.3" + checksum: 10c0/ecabf0957c05fad059779bddcd992f1a9d3a35dfea439a6f0935c382fcf4f7f7fa60489e467b4c2db357a3665167d2a379782586b59712bb36c766e02824709b + languageName: node + linkType: hard + "randomatic@npm:^3.0.0": version: 3.1.1 resolution: "randomatic@npm:3.1.1" @@ -15477,6 +15526,25 @@ __metadata: languageName: node linkType: hard +"react-redux@npm:^9.1.2": + version: 9.2.0 + resolution: "react-redux@npm:9.2.0" + dependencies: + "@types/use-sync-external-store": "npm:^0.0.6" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + "@types/react": ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + redux: + optional: true + checksum: 10c0/00d485f9d9219ca1507b4d30dde5f6ff8fb68ba642458f742e0ec83af052f89e65cd668249b99299e1053cc6ad3d2d8ac6cb89e2f70d2ac5585ae0d7fa0ef259 + languageName: node + linkType: hard + "react-remove-scroll-bar@npm:^2.3.7": version: 2.3.8 resolution: "react-remove-scroll-bar@npm:2.3.8" @@ -15684,6 +15752,13 @@ __metadata: languageName: node linkType: hard +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10c0/b10c28357194f38e7d53b760ed5e64faa317cc63de1fb95bc5d9e127fab956392344368c357b8e7a9bedb0c35b111e7efa522210cfdc3b3c75e5074718e9069c + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.8, reflect.getprototypeof@npm:^1.0.9": version: 1.0.9 resolution: "reflect.getprototypeof@npm:1.0.9" @@ -17742,6 +17817,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.0.6": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a + languageName: node + linkType: hard + "tiny-typed-emitter@npm:^2.1.0": version: 2.1.0 resolution: "tiny-typed-emitter@npm:2.1.0" @@ -18465,6 +18547,15 @@ __metadata: languageName: node linkType: hard +"use-memo-one@npm:^1.1.3": + version: 1.1.3 + resolution: "use-memo-one@npm:1.1.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/3d596e65a6b47b2f1818061599738e00daad1f9a9bb4e5ce1f014b20a35b297e50fe4bf1d8c1699ab43ea97f01f84649a736c15ceff96de83bfa696925f6cc6b + languageName: node + linkType: hard + "use-sidecar@npm:^1.1.2": version: 1.1.3 resolution: "use-sidecar@npm:1.1.3" From f8557c78d55eef0e7f0221fa92d1d5e709af8f16 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 4 Feb 2025 14:54:21 +0700 Subject: [PATCH 10/59] fix: think content render as markdown (#4564) * fix: think content render as markdown * chore: fix linter issue failed on CI --- .../ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx | 2 +- .../Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx index f0a23e416..a112d7614 100644 --- a/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx @@ -19,7 +19,7 @@ import { useClipboard } from '@/hooks/useClipboard' import { getLanguageFromExtension } from '@/utils/codeLanguageExtension' export const MarkdownTextMessage = memo( - ({ text, isUser }: { id: string; text: string; isUser: boolean }) => { + ({ text, isUser }: { id?: string; text: string; isUser?: boolean }) => { const clipboard = useClipboard({ timeout: 1000 }) // Escapes headings diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx index 59ff720c8..bda541b68 100644 --- a/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/ThinkingBlock.tsx @@ -3,6 +3,8 @@ import React from 'react' import { atom, useAtom } from 'jotai' import { ChevronDown, ChevronUp, Loader } from 'lucide-react' +import { MarkdownTextMessage } from './MarkdownTextMessage' + interface Props { text: string status: string @@ -48,7 +50,9 @@ const ThinkingBlock = ({ id, text, status }: Props) => { {isExpanded && (
- {text.replace(/<\/?think>/g, '').trim()} + /g, '').trim()} + />
)}
From 0cc6f29f7f20293ece39873702ec0e6c06604f8a Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Tue, 4 Feb 2025 19:09:20 +0700 Subject: [PATCH 11/59] fix: download progress import from hgf (#4567) --- extensions/yarn.lock | 24 +++++++++---------- .../ModelDownloadRow/index.tsx | 4 +++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/extensions/yarn.lock b/extensions/yarn.lock index b3dd81233..8bbe4eb93 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -509,61 +509,61 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=8a4445&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=28a570&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/74cb4d1126dd504b81b31a3a1da89b1f332e327e980a99d645a9088cba0ccd5fafdb94e1c31d40991cbfc7e18615cf6644f4882ff8df29293ea6adc6a2977d65 + checksum: 10c0/db6db06721a2eff01d4400d87645529f5447b31274132931cb0848a9bb397b32bc3805105be2f31ca1843be926f4102dbf9fb529502f5f48afb73ae726891232 languageName: node linkType: hard diff --git a/web/screens/Settings/HuggingFaceRepoDetailModal/ModelDownloadRow/index.tsx b/web/screens/Settings/HuggingFaceRepoDetailModal/ModelDownloadRow/index.tsx index 96d8ac04e..66302c1d9 100644 --- a/web/screens/Settings/HuggingFaceRepoDetailModal/ModelDownloadRow/index.tsx +++ b/web/screens/Settings/HuggingFaceRepoDetailModal/ModelDownloadRow/index.tsx @@ -51,7 +51,9 @@ const ModelDownloadRow: React.FC = ({ const setMainViewState = useSetAtom(mainViewStateAtom) const assistants = useAtomValue(assistantsAtom) const downloadedModel = downloadedModels.find((md) => md.id === fileName) - const isDownloading = downloadingModels.some((md) => md === fileName) + const isDownloading = downloadingModels.some( + (md) => md === fileName || fileName.includes(md) + ) const setHfImportingStage = useSetAtom(importHuggingFaceModelStageAtom) From 7f4155c443398322a90062665219fbd61a4107a2 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Wed, 5 Feb 2025 12:26:15 +0700 Subject: [PATCH 12/59] enhancement: error ui in threads (#4578) --- web/containers/ErrorMessage/index.tsx | 11 +++++++---- web/tailwind.config.js | 5 ++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/web/containers/ErrorMessage/index.tsx b/web/containers/ErrorMessage/index.tsx index 7db5d83b3..0c0d1c97c 100644 --- a/web/containers/ErrorMessage/index.tsx +++ b/web/containers/ErrorMessage/index.tsx @@ -103,9 +103,12 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => { key={message.id} >
-
Error
+
+ + Error +
-
+
setModalTroubleShooting(true)} @@ -116,7 +119,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
{copied ? ( @@ -138,7 +141,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
{getErrorTitle()} diff --git a/web/tailwind.config.js b/web/tailwind.config.js index e7ad20019..361d686e6 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -55,9 +55,8 @@ module.exports = { }, extend: { fontFamily: { - fontFamily: { - sans: ['Inter var', ...defaultTheme.fontFamily.sans], - }, + sans: ['Inter', ...defaultTheme.fontFamily.sans], + serif: ['Roboto Mono', ...defaultTheme.fontFamily.mono], }, }, }, From 095a00441cdaae5a790163725a8d6d5f3e994d1e Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Wed, 5 Feb 2025 15:19:44 +0700 Subject: [PATCH 13/59] chore: delete custom engine (#4581) * chore: delete custom engine * chore: fix linter --- .../Engines/ModalDeleteCustomEngine.tsx | 60 +++++++++++++++++++ .../Settings/Engines/RemoteEngineItem.tsx | 7 ++- .../Settings/Engines/RemoteEngineSettings.tsx | 32 ++++++---- 3 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 web/screens/Settings/Engines/ModalDeleteCustomEngine.tsx diff --git a/web/screens/Settings/Engines/ModalDeleteCustomEngine.tsx b/web/screens/Settings/Engines/ModalDeleteCustomEngine.tsx new file mode 100644 index 000000000..b6e378581 --- /dev/null +++ b/web/screens/Settings/Engines/ModalDeleteCustomEngine.tsx @@ -0,0 +1,60 @@ +import { memo, useState } from 'react' + +import { InferenceEngine } from '@janhq/core' +import { Button, Modal, ModalClose } from '@janhq/joi' + +import { Trash2Icon } from 'lucide-react' + +import { uninstallEngine } from '@/hooks/useEngineManagement' + +const ModalDeleteCustomEngine = ({ engine }: { engine: InferenceEngine }) => { + const [open, setOpen] = useState(false) + + return ( + Delete Engine} + open={open} + onOpenChange={() => setOpen(!open)} + trigger={ + + } + content={ +
+

+ Are you sure you want to delete {engine}? This action cannot be + undone. +

+
+ { + setOpen(!open) + e.stopPropagation() + }} + > + + + + + +
+
+ } + /> + ) +} + +export default memo(ModalDeleteCustomEngine) diff --git a/web/screens/Settings/Engines/RemoteEngineItem.tsx b/web/screens/Settings/Engines/RemoteEngineItem.tsx index 4538be1fe..449cf9966 100644 --- a/web/screens/Settings/Engines/RemoteEngineItem.tsx +++ b/web/screens/Settings/Engines/RemoteEngineItem.tsx @@ -8,6 +8,10 @@ import { SettingsIcon } from 'lucide-react' import { getTitleByEngine } from '@/utils/modelEngine' +import { getLogoEngine } from '@/utils/modelEngine' + +import ModalDeleteCustomEngine from './ModalDeleteCustomEngine' + import { showSettingActiveRemoteEngineAtom } from '@/helpers/atoms/Extension.atom' import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' @@ -18,7 +22,7 @@ const RemoteEngineItems = ({ values: EngineConfig[] }) => { const setSelectedSetting = useSetAtom(selectedSettingAtom) - + const customEngineLogo = getLogoEngine(engine) const [showSettingActiveRemoteEngine, setShowSettingActiveRemoteEngineAtom] = useAtom(showSettingActiveRemoteEngineAtom) @@ -60,6 +64,7 @@ const RemoteEngineItems = ({ checked={!showSettingActiveRemoteEngine.includes(engine)} onChange={() => onSwitchChange(engine)} /> + {!customEngineLogo && }
- {coreActiveExtensions.length > 0 && ( -
-
- Core Extension -
-
- )} {coreActiveExtensions .filter((x) => x.name.includes(searchText.toLowerCase().trim())) .sort((a, b) => a.name.localeCompare(b.name)) diff --git a/web/screens/Settings/Engines/ModalAddRemoteEngine.tsx b/web/screens/Settings/Engines/ModalAddRemoteEngine.tsx index d500616ae..d123789a1 100644 --- a/web/screens/Settings/Engines/ModalAddRemoteEngine.tsx +++ b/web/screens/Settings/Engines/ModalAddRemoteEngine.tsx @@ -90,7 +90,7 @@ const ModalAddRemoteEngine = () => {

Install Remote Engine

- Only OpenAI API-compatible engines are supported + Only OpenAI API-compatible engines are supported.

} @@ -123,10 +123,14 @@ const ModalAddRemoteEngine = () => {
{errors.chatCmpletionsUrl && ( @@ -141,7 +145,7 @@ const ModalAddRemoteEngine = () => { {renderLabel( 'Model List URL', false, - `URL for fetching available models` + `The endpoint URL to fetch available models.` )} { {renderLabel( 'API Key', false, - `Your authentication key from the provider` + `Your authentication key to activate this engine.` )} { {renderLabel( 'Request Headers Template', false, - `Template for request headers format.` + `HTTP headers template required for API authentication and version specification.` )}