From fc43f1499665664dfae04a3c3ef60d4ac7472698 Mon Sep 17 00:00:00 2001 From: Daniel <101145494+dan-jan@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:30:17 +0800 Subject: [PATCH 01/35] Reorganize Documentation --- docs/docs/about/about.md | 4 +- .../03_assistants.md => specs/assistants.md} | 0 .../{modules/01_chats.md => specs/chats.md} | 0 .../{modules/06_files.md => specs/files.md} | 0 .../05_messages.md => specs/messages.md} | 0 .../{modules/02_models.md => specs/models.md} | 0 .../04_threads.md => specs/threads.md} | 0 .../{guides => getting-started}/img/chat.png | Bin .../img/downloading.png | Bin .../img/explore-model.png | Bin .../img/jan-download.png | Bin .../img/model-catelog.png | Bin .../img/model-version.png | Bin .../img/set-up.png | Bin .../img/start-model.png | Bin .../img/window-defender.png | Bin .../install/linux.md | 0 .../install/mac.md | 0 .../install/windows.md | 0 .../troubleshooting.md | 0 docs/docs/guides/overview.md | 9 --- docs/docusaurus.config.js | 8 +-- docs/sidebars.js | 59 +++++++++++------- 23 files changed, 39 insertions(+), 41 deletions(-) rename docs/docs/docs/{modules/03_assistants.md => specs/assistants.md} (100%) rename docs/docs/docs/{modules/01_chats.md => specs/chats.md} (100%) rename docs/docs/docs/{modules/06_files.md => specs/files.md} (100%) rename docs/docs/docs/{modules/05_messages.md => specs/messages.md} (100%) rename docs/docs/docs/{modules/02_models.md => specs/models.md} (100%) rename docs/docs/docs/{modules/04_threads.md => specs/threads.md} (100%) rename docs/docs/{guides => getting-started}/img/chat.png (100%) rename docs/docs/{guides => getting-started}/img/downloading.png (100%) rename docs/docs/{guides => getting-started}/img/explore-model.png (100%) rename docs/docs/{guides => getting-started}/img/jan-download.png (100%) rename docs/docs/{guides => getting-started}/img/model-catelog.png (100%) rename docs/docs/{guides => getting-started}/img/model-version.png (100%) rename docs/docs/{guides => getting-started}/img/set-up.png (100%) rename docs/docs/{guides => getting-started}/img/start-model.png (100%) rename docs/docs/{guides => getting-started}/img/window-defender.png (100%) rename docs/docs/{guides => getting-started}/install/linux.md (100%) rename docs/docs/{guides => getting-started}/install/mac.md (100%) rename docs/docs/{guides => getting-started}/install/windows.md (100%) rename docs/docs/{guides => getting-started}/troubleshooting.md (100%) delete mode 100644 docs/docs/guides/overview.md diff --git a/docs/docs/about/about.md b/docs/docs/about/about.md index 7931c030f..3ed5750c4 100644 --- a/docs/docs/about/about.md +++ b/docs/docs/about/about.md @@ -2,11 +2,11 @@ title: About Jan --- -Jan is a free, open source alternative to OpenAI's platform that runs on your personal computer. +Jan is a free, open source alternative to OpenAI's platform that runs on a local folder of open-format files. We believe in the need for an open source AI ecosystem, and are building the infra and tooling to allow open source AIs to be as usable and comprehensive as proprietary ones. -Jan's long-term vision is to build a cognitive framework for future robots. We build towards a future where humans and businesses are augmented by practical, useful assistants in everyday life. +Jan's long-term vision is to build a cognitive framework for future robots, who are practical, useful assistants for humans and businesses in everyday life. ## Why does Jan Exist? diff --git a/docs/docs/docs/modules/03_assistants.md b/docs/docs/docs/specs/assistants.md similarity index 100% rename from docs/docs/docs/modules/03_assistants.md rename to docs/docs/docs/specs/assistants.md diff --git a/docs/docs/docs/modules/01_chats.md b/docs/docs/docs/specs/chats.md similarity index 100% rename from docs/docs/docs/modules/01_chats.md rename to docs/docs/docs/specs/chats.md diff --git a/docs/docs/docs/modules/06_files.md b/docs/docs/docs/specs/files.md similarity index 100% rename from docs/docs/docs/modules/06_files.md rename to docs/docs/docs/specs/files.md diff --git a/docs/docs/docs/modules/05_messages.md b/docs/docs/docs/specs/messages.md similarity index 100% rename from docs/docs/docs/modules/05_messages.md rename to docs/docs/docs/specs/messages.md diff --git a/docs/docs/docs/modules/02_models.md b/docs/docs/docs/specs/models.md similarity index 100% rename from docs/docs/docs/modules/02_models.md rename to docs/docs/docs/specs/models.md diff --git a/docs/docs/docs/modules/04_threads.md b/docs/docs/docs/specs/threads.md similarity index 100% rename from docs/docs/docs/modules/04_threads.md rename to docs/docs/docs/specs/threads.md diff --git a/docs/docs/guides/img/chat.png b/docs/docs/getting-started/img/chat.png similarity index 100% rename from docs/docs/guides/img/chat.png rename to docs/docs/getting-started/img/chat.png diff --git a/docs/docs/guides/img/downloading.png b/docs/docs/getting-started/img/downloading.png similarity index 100% rename from docs/docs/guides/img/downloading.png rename to docs/docs/getting-started/img/downloading.png diff --git a/docs/docs/guides/img/explore-model.png b/docs/docs/getting-started/img/explore-model.png similarity index 100% rename from docs/docs/guides/img/explore-model.png rename to docs/docs/getting-started/img/explore-model.png diff --git a/docs/docs/guides/img/jan-download.png b/docs/docs/getting-started/img/jan-download.png similarity index 100% rename from docs/docs/guides/img/jan-download.png rename to docs/docs/getting-started/img/jan-download.png diff --git a/docs/docs/guides/img/model-catelog.png b/docs/docs/getting-started/img/model-catelog.png similarity index 100% rename from docs/docs/guides/img/model-catelog.png rename to docs/docs/getting-started/img/model-catelog.png diff --git a/docs/docs/guides/img/model-version.png b/docs/docs/getting-started/img/model-version.png similarity index 100% rename from docs/docs/guides/img/model-version.png rename to docs/docs/getting-started/img/model-version.png diff --git a/docs/docs/guides/img/set-up.png b/docs/docs/getting-started/img/set-up.png similarity index 100% rename from docs/docs/guides/img/set-up.png rename to docs/docs/getting-started/img/set-up.png diff --git a/docs/docs/guides/img/start-model.png b/docs/docs/getting-started/img/start-model.png similarity index 100% rename from docs/docs/guides/img/start-model.png rename to docs/docs/getting-started/img/start-model.png diff --git a/docs/docs/guides/img/window-defender.png b/docs/docs/getting-started/img/window-defender.png similarity index 100% rename from docs/docs/guides/img/window-defender.png rename to docs/docs/getting-started/img/window-defender.png diff --git a/docs/docs/guides/install/linux.md b/docs/docs/getting-started/install/linux.md similarity index 100% rename from docs/docs/guides/install/linux.md rename to docs/docs/getting-started/install/linux.md diff --git a/docs/docs/guides/install/mac.md b/docs/docs/getting-started/install/mac.md similarity index 100% rename from docs/docs/guides/install/mac.md rename to docs/docs/getting-started/install/mac.md diff --git a/docs/docs/guides/install/windows.md b/docs/docs/getting-started/install/windows.md similarity index 100% rename from docs/docs/guides/install/windows.md rename to docs/docs/getting-started/install/windows.md diff --git a/docs/docs/guides/troubleshooting.md b/docs/docs/getting-started/troubleshooting.md similarity index 100% rename from docs/docs/guides/troubleshooting.md rename to docs/docs/getting-started/troubleshooting.md diff --git a/docs/docs/guides/overview.md b/docs/docs/guides/overview.md deleted file mode 100644 index bf44ca685..000000000 --- a/docs/docs/guides/overview.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Overview -slug: /guides ---- -- Jan Platform: Desktop app/ Cloud native SaaS that can run on Linux, Windows, Mac, or even a Server that comes with extensibilities, toolbox, and state-of-the-art but optimized models for next-gen Apps. -- Jan App: Next-gen App built on Jan Plaform as `portable intelligence` that can be run everywhere. -- Models: - - Large Language Models - - Stable Diffusion models diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index df522ffb0..f669a70a1 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -134,13 +134,7 @@ const config = { src: "img/logo.svg", }, items: [ - // Navbar left - { - type: "docSidebar", - sidebarId: "guidesSidebar", - position: "left", - label: "User Guide", - }, + // Navbar Left { type: "docSidebar", sidebarId: "docsSidebar", diff --git a/docs/sidebars.js b/docs/sidebars.js index ea6a22a71..f3accdb77 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -29,39 +29,52 @@ const sidebars = { }, ], - guidesSidebar: [ - "guides/overview", - { - type: "category", - label: "Installation", - collapsible: true, - collapsed: false, - items: [ - { - type: "autogenerated", - dirName: "guides/install", - }, - ], - }, - "guides/troubleshooting", - ], - docsSidebar: [ - "docs/introduction", - "docs/quickstart", { type: "category", - label: "Modules", + label: "Getting Started", collapsible: true, collapsed: false, items: [ + "docs/introduction", { - type: "autogenerated", - dirName: "docs/modules", + type: "category", + label: "Installation", + collapsible: true, + collapsed: true, + items: [ + { + type: "autogenerated", + dirName: "getting-started/install", + }, + ], + }, + "docs/quickstart", + ], + }, + { + type: "category", + label: "Building Jan", + collapsible: false, + collapsed: false, + items: [ + "docs/user-interface", + { + type: "category", + label: "Specifications", + collapsible: true, + collapsed: true, + items: [ + "docs/specs/chats", + "docs/specs/models", + "docs/specs/threads", + "docs/specs/messages", + "docs/specs/assistants", + "docs/specs/files", + ], }, ], }, - "docs/user-interface", ], apiSidebar: [ From 9ce5ed3991b407db2b58b4429fc737519c6c5a03 Mon Sep 17 00:00:00 2001 From: Daniel <101145494+dan-jan@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:34:17 +0800 Subject: [PATCH 02/35] Disable collapsible on sidebar --- docs/sidebars.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index f3accdb77..7736b60eb 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -33,7 +33,7 @@ const sidebars = { { type: "category", label: "Getting Started", - collapsible: true, + collapsible: false, collapsed: false, items: [ "docs/introduction", From 1fad9030c516fbe5154fb0a85a470dbf581de570 Mon Sep 17 00:00:00 2001 From: Ashley <89722390+imtuyethan@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:21:23 +0700 Subject: [PATCH 03/35] Update README.md - Update banner - Update tagline --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5464ff1c1..2b29bd84a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# Jan - Personal AI +# Jan - Own Your AI + +![README](https://github.com/janhq/jan/assets/89722390/85a3c0fd-8453-46d0-857b-fb893a286222) -![Project Cover](https://github.com/janhq/jan/assets/89722390/be4f07ef-13df-4621-8f25-b861f1d5b7b3)

@@ -21,7 +22,7 @@ > ⚠️ **Jan is currently in Development**: Expect breaking changes and bugs! -Jan is a free, open-source alternative to OpenAI that runs on your personal computer. +Jan is a free, open-source alternative to OpenAI's platform that runs on a local folder of open-format files. **Jan runs on any hardware.** From PCs to multi-GPU clusters, Jan supports universal architectures: From 8820e187413d94d498635ae89758e991528b8d75 Mon Sep 17 00:00:00 2001 From: Ashley <89722390+imtuyethan@users.noreply.github.com> Date: Wed, 15 Nov 2023 18:36:32 +0700 Subject: [PATCH 04/35] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b29bd84a..c28a9530d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Jan - Own Your AI -![README](https://github.com/janhq/jan/assets/89722390/85a3c0fd-8453-46d0-857b-fb893a286222) - +![README](https://github.com/janhq/jan/assets/89722390/67300882-067c-47d8-a6ec-107b8e1eae0f)

From 9c5c03b6bca2c2e3a1dd4f5c59c36d5dbe57ac1c Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Wed, 15 Nov 2023 21:46:27 +0700 Subject: [PATCH 05/35] Add run-script-os (#620) --- .github/scripts/auto-sign.sh | 11 ++++++--- .github/workflows/jan-electron-build.yml | 12 +++++----- .../jan-electron-linter-and-test.yml | 8 +++---- electron/package.json | 23 ++++++++++--------- package.json | 21 ++++------------- plugins/inference-plugin/package.json | 21 ++++++++--------- 6 files changed, 44 insertions(+), 52 deletions(-) diff --git a/.github/scripts/auto-sign.sh b/.github/scripts/auto-sign.sh index e1c735860..5e6ef9750 100755 --- a/.github/scripts/auto-sign.sh +++ b/.github/scripts/auto-sign.sh @@ -1,5 +1,10 @@ #!/bin/bash -APP_PATH=${APP_PATH} -DEVELOPER_ID=${DEVELOPER_ID} -find $APP_PATH \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \; \ No newline at end of file +# Check if both APP_PATH and DEVELOPER_ID environment variables are set +if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then + echo "Either APP_PATH or DEVELOPER_ID is not set. Skipping script execution." + exit 0 +fi + +# If both variables are set, execute the following commands +find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \; diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml index f1c0f5c1e..34005e068 100644 --- a/.github/workflows/jan-electron-build.yml +++ b/.github/workflows/jan-electron-build.yml @@ -60,14 +60,14 @@ jobs: run: | yarn build:core yarn install - yarn build:plugins-darwin + yarn build:plugins env: APP_PATH: "." DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }} - name: Build and publish app run: | - yarn build:publish-darwin + yarn build:publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} CSC_LINK: "/tmp/codesign.p12" @@ -122,11 +122,11 @@ jobs: yarn build:core yarn install $env:NITRO_VERSION = Get-Content .\plugins\inference-plugin\nitro\version.txt; echo $env:NITRO_VERSION - yarn build:plugins-win32 + yarn build:plugins - name: Build and publish app run: | - yarn build:publish-win32 + yarn build:publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -178,11 +178,11 @@ jobs: yarn config set network-timeout 300000 yarn build:core yarn install - yarn build:plugins-linux + yarn build:plugins - name: Build and publish app run: | - yarn build:publish-linux + yarn build:publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 1938a40f5..92f29c368 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -95,8 +95,8 @@ jobs: yarn build:core yarn install $env:NITRO_VERSION = Get-Content .\plugins\inference-plugin\nitro\version.txt; echo $env:NITRO_VERSION - yarn build:plugins-win32 - yarn build:test-win32 + yarn build:plugins + yarn build:test $env:CI="e2e" yarn test @@ -131,6 +131,6 @@ jobs: yarn config set network-timeout 300000 yarn build:core yarn install - yarn build:plugins-linux - yarn build:test-linux + yarn build:plugins + yarn build:test yarn test diff --git a/electron/package.json b/electron/package.json index 107264805..0eed33b2e 100644 --- a/electron/package.json +++ b/electron/package.json @@ -52,18 +52,18 @@ "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", "test:e2e": "playwright test --workers=1", "dev": "tsc -p . && electron .", - "build": "tsc -p . && electron-builder -p never -m", - "build:test": "tsc -p . && electron-builder --dir -p never -m", - "build:test-darwin": "tsc -p . && electron-builder -p never -m --x64 --arm64 --dir", - "build:test-win32": "tsc -p . && electron-builder -p never -w --dir", - "build:test-linux": "tsc -p . && electron-builder -p never -l --dir", - "build:darwin": "tsc -p . && electron-builder -p never -m --x64 --arm64", + "build": "run-script-os", + "build:test": "run-script-os", + "build:test:darwin": "tsc -p . && electron-builder -p never -m --dir", + "build:test:win32": "tsc -p . && electron-builder -p never -w --dir", + "build:test:linux": "tsc -p . && electron-builder -p never -l --dir", + "build:darwin": "tsc -p . && electron-builder -p never -m", "build:win32": "tsc -p . && electron-builder -p never -w", "build:linux": "tsc -p . && electron-builder -p never --linux deb", - "build:publish": "tsc -p . && electron-builder -p onTagOrDraft -m", - "build:publish-darwin": "tsc -p . && electron-builder -p onTagOrDraft -m --x64 --arm64", - "build:publish-win32": "tsc -p . && electron-builder -p onTagOrDraft -w", - "build:publish-linux": "tsc -p . && electron-builder -p onTagOrDraft --linux deb " + "build:publish": "run-script-os", + "build:publish:darwin": "tsc -p . && electron-builder -p onTagOrDraft -m --x64 --arm64", + "build:publish:win32": "tsc -p . && electron-builder -p onTagOrDraft -w", + "build:publish:linux": "tsc -p . && electron-builder -p onTagOrDraft -l deb" }, "dependencies": { "@npmcli/arborist": "^7.1.0", @@ -86,7 +86,8 @@ "electron": "26.2.1", "electron-builder": "^24.6.4", "electron-playwright-helpers": "^1.6.0", - "eslint-plugin-react": "^7.33.2" + "eslint-plugin-react": "^7.33.2", + "run-script-os": "^1.1.6" }, "installConfig": { "hoistingLimits": "workspaces" diff --git a/package.json b/package.json index 32506ea80..036542c38 100644 --- a/package.json +++ b/package.json @@ -36,27 +36,16 @@ "build:electron": "yarn workspace jan build", "build:electron:test": "yarn workspace jan build:test", "build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-json && npm install && npm run build:publish\" \"cd ./plugins/inference-plugin && npm install && npm run build:publish\" \"cd ./plugins/model-plugin && npm install && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm install && npm run build:publish\"", - "build:plugins-win32": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-json && npm install && npm run build:publish\" \"cd ./plugins/inference-plugin && npm install && npm run build:publish-win32\" \"cd ./plugins/model-plugin && npm install && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm install && npm run build:publish\"", - "build:plugins-linux": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-json && npm install && npm run build:publish\" \"cd ./plugins/inference-plugin && npm install && npm run build:publish-linux\" \"cd ./plugins/model-plugin && npm install && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm install && npm run build:publish\"", - "build:plugins-darwin": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-json && npm install && npm run build:publish\" \"cd ./plugins/inference-plugin && npm install && npm run build:publish-darwin\" \"cd ./plugins/model-plugin && npm install && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm install && npm run build:publish\"", - "build:test": "yarn build:web && yarn build:electron:test", - "build:test-darwin": "yarn build:web && yarn workspace jan build:test-darwin", - "build:test-win32": "yarn build:web && yarn workspace jan build:test-win32", - "build:test-linux": "yarn build:web && yarn workspace jan build:test-linux", - "build": "yarn build:web && yarn build:electron", - "build:darwin": "yarn build:web && yarn workspace jan build:darwin", - "build:win32": "yarn build:web && yarn workspace jan build:win32", - "build:linux": "yarn build:web && yarn workspace jan build:linux", - "build:publish": "yarn build:web && yarn workspace jan build:publish", - "build:publish-darwin": "yarn build:web && yarn workspace jan build:publish-darwin", - "build:publish-win32": "yarn build:web && yarn workspace jan build:publish-win32", - "build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux" + "build:test": "yarn build:web && yarn workspace jan build:test", + "build": "yarn build:web && yarn workspace jan build", + "build:publish": "yarn build:web && yarn workspace jan build:publish" }, "devDependencies": { "concurrently": "^8.2.1", "cpx": "^1.5.0", "rimraf": "^3.0.2", - "wait-on": "^7.0.1" + "wait-on": "^7.0.1", + "run-script-os": "^1.1.6" }, "version": "0.0.0" } diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 5c52d13d0..3b7159c3c 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -13,18 +13,14 @@ ], "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "downloadnitro:linux-cpu": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.zip -e --strip 1 -o ./nitro/linux-cpu && chmod +x ./nitro/linux-cpu/nitro && chmod +x ./nitro/linux-start.sh ", - "downloadnitro:linux-cuda": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda.zip -e --strip 1 -o ./nitro/linux-cuda && chmod +x ./nitro/linux-cuda/nitro && chmod +x ./nitro/linux-start.sh", - "downloadnitro:darwin-arm64": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.zip -e --strip 1 -o ./nitro/mac-arm64 && chmod +x ./nitro/mac-arm64/nitro", - "downloadnitro:darwin-x64": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.zip -e --strip 1 -o ./nitro/mac-x64 && chmod +x ./nitro/mac-x64/nitro", - "downloadnitro:win32-cpu": "download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.zip -e --strip 1 -o ./nitro/win-cpu", - "downloadnitro:win32-cuda": "download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda.zip -e --strip 1 -o ./nitro/win-cuda", - "downloadnitro:all": "npm run downloadnitro:darwin-arm64 && npm run downloadnitro:darwin-x64 && downloadnitro:win32-cpu && npm run downloadnitro:win32-cuda && npm run downloadnitro:linux-cpu && npm run downloadnitro:linux-cuda", - "build:publish": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:darwin-arm64 && npm run downloadnitro:darwin-x64 && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", - "build:publish-darwin": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:darwin-arm64 && npm run downloadnitro:darwin-x64 && ../../.github/scripts/auto-sign.sh && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", - "build:publish-win32": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:win32-cpu && npm run downloadnitro:win32-cuda && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", - "build:publish-linux": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:linux-cpu && npm run downloadnitro:linux-cuda && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", - "build:publish-all": "rimraf *.tgz --glob && npm run build && npm run downloadnitro:all && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install" + "downloadnitro:linux": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.zip -e --strip 1 -o ./nitro/linux-cpu && chmod +x ./nitro/linux-cpu/nitro && chmod +x ./nitro/linux-start.sh && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda.zip -e --strip 1 -o ./nitro/linux-cuda && chmod +x ./nitro/linux-cuda/nitro && chmod +x ./nitro/linux-start.sh", + "downloadnitro:darwin": "NITRO_VERSION=$(cat ./nitro/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.zip -e --strip 1 -o ./nitro/mac-arm64 && chmod +x ./nitro/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.zip -e --strip 1 -o ./nitro/mac-x64 && chmod +x ./nitro/mac-x64/nitro", + "downloadnitro:win32": "download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.zip -e --strip 1 -o ./nitro/win-cpu && download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda.zip -e --strip 1 -o ./nitro/win-cuda", + "downloadnitro": "run-script-os", + "build:publish:darwin": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && ../../.github/scripts/auto-sign.sh && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", + "build:publish:win32": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", + "build:publish:linux": "rimraf *.tgz --glob && npm run build && npm run downloadnitro && cpx \"nitro/**\" \"dist/nitro\" && npm pack && cpx *.tgz ../../electron/core/pre-install", + "build:publish": "run-script-os" }, "exports": { ".": "./dist/index.js", @@ -33,6 +29,7 @@ "devDependencies": { "cpx": "^1.5.0", "rimraf": "^3.0.2", + "run-script-os": "^1.1.6", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" }, From 52d56a8ae1da3fa99554eb2ec8ae9e711477a693 Mon Sep 17 00:00:00 2001 From: NamH Date: Thu, 16 Nov 2023 12:09:09 +0700 Subject: [PATCH 06/35] refactor: move file to jan root (#598) * feat: move necessary files to jan root Signed-off-by: James * chore: check model dir --------- Signed-off-by: James Co-authored-by: James Co-authored-by: Louis --- core/src/events.ts | 4 +- core/src/fs.ts | 17 ++ core/src/index.ts | 9 +- core/src/types/index.ts | 59 ++++++- electron/.prettierrc | 8 + electron/handlers/download.ts | 96 +++++------ electron/handlers/fs.ts | 151 ++++++++++-------- electron/handlers/plugin.ts | 4 +- electron/handlers/update.ts | 2 +- electron/main.ts | 78 ++++----- electron/managers/plugin.ts | 6 +- electron/preload.ts | 74 +++++---- plugins/conversational-json/package.json | 1 + plugins/conversational-json/src/index.ts | 29 +++- plugins/conversational-json/webpack.config.js | 3 + plugins/conversational-plugin/src/index.ts | 16 +- plugins/inference-plugin/src/index.ts | 12 +- plugins/inference-plugin/src/module.ts | 2 +- plugins/model-plugin/.prettierrc | 8 + plugins/model-plugin/package.json | 1 + .../model-plugin/src/helpers/cloudNative.ts | 48 ------ .../model-plugin/src/helpers/modelParser.ts | 20 +-- plugins/model-plugin/src/index.ts | 96 ++++++----- plugins/model-plugin/webpack.config.js | 27 ++-- web/containers/Layout/BottomBar/index.tsx | 2 +- .../CommandListDownloadedModel/index.tsx | 6 +- web/containers/ModalCancelDownload/index.tsx | 4 +- web/containers/Providers/EventHandler.tsx | 58 ++++--- web/containers/Providers/EventListener.tsx | 17 +- web/helpers/atoms/ChatMessage.atom.ts | 3 +- web/helpers/atoms/Conversation.atom.ts | 12 +- web/hooks/useActiveModel.ts | 11 +- web/hooks/useCreateConversation.ts | 14 +- web/hooks/useDeleteConversation.ts | 4 +- web/hooks/useDeleteModel.ts | 10 +- web/hooks/useDownloadModel.ts | 8 +- web/hooks/useGetInputState.ts | 10 +- web/hooks/useGetUserConversations.ts | 12 +- web/hooks/useSendChatMessage.ts | 25 +-- web/models/ChatMessage.ts | 84 ---------- web/screens/Chat/ChatItem/index.tsx | 3 +- web/screens/Chat/HistoryList/index.tsx | 12 +- web/screens/Chat/SimpleTextMessage/index.tsx | 3 +- web/screens/Chat/index.tsx | 6 +- .../ExploreModels/ExploreModelItem/index.tsx | 2 +- .../ExploreModelItemHeader/index.tsx | 6 +- .../ExploreModels/ExploreModelList/index.tsx | 18 +-- .../ExploreModels/ModelVersionItem/index.tsx | 13 +- .../ExploreModels/ModelVersionList/index.tsx | 4 +- web/screens/MyModels/index.tsx | 8 +- web/services/coreService.ts | 4 +- web/types/chatMessage.d.ts | 63 -------- web/utils/dummy.ts | 6 +- web/utils/message.ts | 67 +++++--- 54 files changed, 608 insertions(+), 658 deletions(-) create mode 100644 electron/.prettierrc create mode 100644 plugins/model-plugin/.prettierrc delete mode 100644 plugins/model-plugin/src/helpers/cloudNative.ts delete mode 100644 web/models/ChatMessage.ts delete mode 100644 web/types/chatMessage.d.ts diff --git a/core/src/events.ts b/core/src/events.ts index b39a38408..e962dead4 100644 --- a/core/src/events.ts +++ b/core/src/events.ts @@ -20,7 +20,7 @@ export type MessageHistory = { * The `NewMessageRequest` type defines the shape of a new message request object. */ export type NewMessageRequest = { - _id?: string; + id?: string; conversationId?: string; user?: string; avatar?: string; @@ -34,7 +34,7 @@ export type NewMessageRequest = { * The `NewMessageRequest` type defines the shape of a new message request object. */ export type NewMessageResponse = { - _id?: string; + id?: string; conversationId?: string; user?: string; avatar?: string; diff --git a/core/src/fs.ts b/core/src/fs.ts index 04046705a..73ac31636 100644 --- a/core/src/fs.ts +++ b/core/src/fs.ts @@ -8,6 +8,21 @@ const writeFile: (path: string, data: string) => Promise = (path, data) => window.coreAPI?.writeFile(path, data) ?? window.electronAPI?.writeFile(path, data); +/** + * Gets the user space path. + * @returns {Promise} A Promise that resolves with the user space path. + */ +const getUserSpace = (): Promise => + window.coreAPI?.getUserSpace() ?? window.electronAPI?.getUserSpace(); + +/** + * Checks whether the path is a directory. + * @param path - The path to check. + * @returns {boolean} A boolean indicating whether the path is a directory. + */ +const isDirectory = (path: string): Promise => + window.coreAPI?.isDirectory(path) ?? window.electronAPI?.isDirectory(path); + /** * Reads the contents of a file at the specified path. * @param {string} path - The path of the file to read. @@ -48,6 +63,8 @@ const deleteFile: (path: string) => Promise = (path) => window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path); export const fs = { + isDirectory, + getUserSpace, writeFile, readFile, listFiles, diff --git a/core/src/index.ts b/core/src/index.ts index 39a69d702..5c741c863 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -20,12 +20,9 @@ export { events } from "./events"; * Events types exports. * @module */ -export { - EventName, - NewMessageRequest, - NewMessageResponse, - MessageHistory, -} from "./events"; +export * from "./events"; + +export * from "./types/index"; /** * Filesystem module exports. diff --git a/core/src/types/index.ts b/core/src/types/index.ts index c2062b5d1..296bc1a7e 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -1,5 +1,5 @@ export interface Conversation { - _id: string; + id: string; modelId?: string; botId?: string; name: string; @@ -8,11 +8,23 @@ export interface Conversation { createdAt?: string; updatedAt?: string; messages: Message[]; + lastMessage?: string; } + export interface Message { + id: string; message?: string; user?: string; - _id: string; + createdAt?: string; + updatedAt?: string; +} + +export interface RawMessage { + id?: string; + conversationId?: string; + user?: string; + avatar?: string; + message?: string; createdAt?: string; updatedAt?: string; } @@ -22,7 +34,7 @@ export interface Model { * Combination of owner and model name. * Being used as file name. MUST be unique. */ - _id: string; + id: string; name: string; quantMethod: string; bits: number; @@ -51,7 +63,7 @@ export interface Model { tags: string[]; } export interface ModelCatalog { - _id: string; + id: string; name: string; shortDescription: string; avatarUrl: string; @@ -74,7 +86,7 @@ export type ModelVersion = { * Combination of owner and model name. * Being used as file name. Should be unique. */ - _id: string; + id: string; name: string; quantMethod: string; bits: number; @@ -89,3 +101,40 @@ export type ModelVersion = { startDownloadAt?: number; finishDownloadAt?: number; }; + +export interface ChatMessage { + id: string; + conversationId: string; + messageType: MessageType; + messageSenderType: MessageSenderType; + senderUid: string; + senderName: string; + senderAvatarUrl: string; + text: string | undefined; + imageUrls?: string[] | undefined; + createdAt: number; + status: MessageStatus; +} + +export enum MessageType { + Text = "Text", + Image = "Image", + ImageWithText = "ImageWithText", + Error = "Error", +} + +export enum MessageSenderType { + Ai = "assistant", + User = "user", +} + +export enum MessageStatus { + Ready = "ready", + Pending = "pending", +} + +export type ConversationState = { + hasMore: boolean; + waitingForResponse: boolean; + error?: Error; +}; diff --git a/electron/.prettierrc b/electron/.prettierrc new file mode 100644 index 000000000..46f1abcb0 --- /dev/null +++ b/electron/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "quoteProps": "consistent", + "trailingComma": "es5", + "endOfLine": "auto", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/electron/handlers/download.ts b/electron/handlers/download.ts index ab672f25c..3a1fc36d1 100644 --- a/electron/handlers/download.ts +++ b/electron/handlers/download.ts @@ -1,10 +1,10 @@ -import { app, ipcMain } from "electron"; -import { DownloadManager } from "../managers/download"; -import { resolve, join } from "path"; -import { WindowManager } from "../managers/window"; -import request from "request"; -import { createWriteStream, unlink } from "fs"; -const progress = require("request-progress"); +import { app, ipcMain } from 'electron' +import { DownloadManager } from '../managers/download' +import { resolve, join } from 'path' +import { WindowManager } from '../managers/window' +import request from 'request' +import { createWriteStream, unlink } from 'fs' +const progress = require('request-progress') export function handleDownloaderIPCs() { /** @@ -12,18 +12,18 @@ export function handleDownloaderIPCs() { * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle("pauseDownload", async (_event, fileName) => { - DownloadManager.instance.networkRequests[fileName]?.pause(); - }); + ipcMain.handle('pauseDownload', async (_event, fileName) => { + DownloadManager.instance.networkRequests[fileName]?.pause() + }) /** * Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName. * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle("resumeDownload", async (_event, fileName) => { - DownloadManager.instance.networkRequests[fileName]?.resume(); - }); + ipcMain.handle('resumeDownload', async (_event, fileName) => { + DownloadManager.instance.networkRequests[fileName]?.resume() + }) /** * Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName. @@ -31,24 +31,26 @@ export function handleDownloaderIPCs() { * @param _event - The IPC event object. * @param fileName - The name of the file being downloaded. */ - ipcMain.handle("abortDownload", async (_event, fileName) => { - const rq = DownloadManager.instance.networkRequests[fileName]; - DownloadManager.instance.networkRequests[fileName] = undefined; - const userDataPath = app.getPath("userData"); - const fullPath = join(userDataPath, fileName); - rq?.abort(); - let result = "NULL"; + ipcMain.handle('abortDownload', async (_event, fileName) => { + const rq = DownloadManager.instance.networkRequests[fileName] + DownloadManager.instance.networkRequests[fileName] = undefined + const userDataPath = app.getPath('userData') + const fullPath = join(userDataPath, fileName) + rq?.abort() + let result = 'NULL' unlink(fullPath, function (err) { - if (err && err.code == "ENOENT") { - result = `File not exist: ${err}`; + if (err && err.code == 'ENOENT') { + result = `File not exist: ${err}` } else if (err) { - result = `File delete error: ${err}`; + result = `File delete error: ${err}` } else { - result = "File deleted successfully"; + result = 'File deleted successfully' } - console.log(`Delete file ${fileName} from ${fullPath} result: ${result}`); - }); - }); + console.debug( + `Delete file ${fileName} from ${fullPath} result: ${result}` + ) + }) + }) /** * Downloads a file from a given URL. @@ -56,51 +58,51 @@ export function handleDownloaderIPCs() { * @param url - The URL to download the file from. * @param fileName - The name to give the downloaded file. */ - ipcMain.handle("downloadFile", async (_event, url, fileName) => { - const userDataPath = app.getPath("userData"); - const destination = resolve(userDataPath, fileName); - const rq = request(url); + ipcMain.handle('downloadFile', async (_event, url, fileName) => { + const userDataPath = join(app.getPath('home'), 'jan') + const destination = resolve(userDataPath, fileName) + const rq = request(url) progress(rq, {}) - .on("progress", function (state: any) { + .on('progress', function (state: any) { WindowManager?.instance.currentWindow?.webContents.send( - "FILE_DOWNLOAD_UPDATE", + 'FILE_DOWNLOAD_UPDATE', { ...state, fileName, } - ); + ) }) - .on("error", function (err: Error) { + .on('error', function (err: Error) { WindowManager?.instance.currentWindow?.webContents.send( - "FILE_DOWNLOAD_ERROR", + 'FILE_DOWNLOAD_ERROR', { fileName, err, } - ); + ) }) - .on("end", function () { + .on('end', function () { if (DownloadManager.instance.networkRequests[fileName]) { WindowManager?.instance.currentWindow?.webContents.send( - "FILE_DOWNLOAD_COMPLETE", + 'FILE_DOWNLOAD_COMPLETE', { fileName, } - ); - DownloadManager.instance.setRequest(fileName, undefined); + ) + DownloadManager.instance.setRequest(fileName, undefined) } else { WindowManager?.instance.currentWindow?.webContents.send( - "FILE_DOWNLOAD_ERROR", + 'FILE_DOWNLOAD_ERROR', { fileName, - err: "Download cancelled", + err: 'Download cancelled', } - ); + ) } }) - .pipe(createWriteStream(destination)); + .pipe(createWriteStream(destination)) - DownloadManager.instance.setRequest(fileName, rq); - }); + DownloadManager.instance.setRequest(fileName, rq) + }) } diff --git a/electron/handlers/fs.ts b/electron/handlers/fs.ts index af77e3002..c1e8a85e4 100644 --- a/electron/handlers/fs.ts +++ b/electron/handlers/fs.ts @@ -1,28 +1,53 @@ -import { app, ipcMain } from "electron"; -import * as fs from "fs"; -import { join } from "path"; +import { app, ipcMain } from 'electron' +import * as fs from 'fs' +import { join } from 'path' /** * Handles file system operations. */ export function handleFsIPCs() { + const userSpacePath = join(app.getPath('home'), 'jan') + + /** + * Gets the path to the user data directory. + * @param event - The event object. + * @returns A promise that resolves with the path to the user data directory. + */ + ipcMain.handle( + 'getUserSpace', + (): Promise => Promise.resolve(userSpacePath) + ) + + /** + * Checks whether the path is a directory. + * @param event - The event object. + * @param path - The path to check. + * @returns A promise that resolves with a boolean indicating whether the path is a directory. + */ + ipcMain.handle('isDirectory', (_event, path: string): Promise => { + const fullPath = join(userSpacePath, path) + return Promise.resolve( + fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() + ) + }) + /** * Reads a file from the user data directory. * @param event - The event object. * @param path - The path of the file to read. * @returns A promise that resolves with the contents of the file. */ - ipcMain.handle("readFile", async (event, path: string): Promise => { + ipcMain.handle('readFile', async (event, path: string): Promise => { return new Promise((resolve, reject) => { - fs.readFile(join(app.getPath("userData"), path), "utf8", (err, data) => { + fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => { if (err) { - reject(err); + reject(err) } else { - resolve(data); + resolve(data) } - }); - }); - }); + }) + }) + }) /** * Writes data to a file in the user data directory. @@ -32,24 +57,19 @@ export function handleFsIPCs() { * @returns A promise that resolves when the file has been written. */ ipcMain.handle( - "writeFile", + 'writeFile', async (event, path: string, data: string): Promise => { return new Promise((resolve, reject) => { - fs.writeFile( - join(app.getPath("userData"), path), - data, - "utf8", - (err) => { - if (err) { - reject(err); - } else { - resolve(); - } + fs.writeFile(join(userSpacePath, path), data, 'utf8', (err) => { + if (err) { + reject(err) + } else { + resolve() } - ); - }); + }) + }) } - ); + ) /** * Creates a directory in the user data directory. @@ -57,21 +77,17 @@ export function handleFsIPCs() { * @param path - The path of the directory to create. * @returns A promise that resolves when the directory has been created. */ - ipcMain.handle("mkdir", async (event, path: string): Promise => { + ipcMain.handle('mkdir', async (event, path: string): Promise => { return new Promise((resolve, reject) => { - fs.mkdir( - join(app.getPath("userData"), path), - { recursive: true }, - (err) => { - if (err) { - reject(err); - } else { - resolve(); - } + fs.mkdir(join(userSpacePath, path), { recursive: true }, (err) => { + if (err) { + reject(err) + } else { + resolve() } - ); - }); - }); + }) + }) + }) /** * Removes a directory in the user data directory. @@ -79,21 +95,17 @@ export function handleFsIPCs() { * @param path - The path of the directory to remove. * @returns A promise that resolves when the directory is removed successfully. */ - ipcMain.handle("rmdir", async (event, path: string): Promise => { + ipcMain.handle('rmdir', async (event, path: string): Promise => { return new Promise((resolve, reject) => { - fs.rmdir( - join(app.getPath("userData"), path), - { recursive: true }, - (err) => { - if (err) { - reject(err); - } else { - resolve(); - } + fs.rmdir(join(userSpacePath, path), { recursive: true }, (err) => { + if (err) { + reject(err) + } else { + resolve() } - ); - }); - }); + }) + }) + }) /** * Lists the files in a directory in the user data directory. @@ -102,19 +114,19 @@ export function handleFsIPCs() { * @returns A promise that resolves with an array of file names. */ ipcMain.handle( - "listFiles", + 'listFiles', async (event, path: string): Promise => { return new Promise((resolve, reject) => { - fs.readdir(join(app.getPath("userData"), path), (err, files) => { + fs.readdir(join(userSpacePath, path), (err, files) => { if (err) { - reject(err); + reject(err) } else { - resolve(files); + resolve(files) } - }); - }); + }) + }) } - ); + ) /** * Deletes a file from the user data folder. @@ -122,22 +134,23 @@ export function handleFsIPCs() { * @param filePath - The path to the file to delete. * @returns A string indicating the result of the operation. */ - ipcMain.handle("deleteFile", async (_event, filePath) => { - const userDataPath = app.getPath("userData"); - const fullPath = join(userDataPath, filePath); + ipcMain.handle('deleteFile', async (_event, filePath) => { + const fullPath = join(userSpacePath, filePath) - let result = "NULL"; + let result = 'NULL' fs.unlink(fullPath, function (err) { - if (err && err.code == "ENOENT") { - result = `File not exist: ${err}`; + if (err && err.code == 'ENOENT') { + result = `File not exist: ${err}` } else if (err) { - result = `File delete error: ${err}`; + result = `File delete error: ${err}` } else { - result = "File deleted successfully"; + result = 'File deleted successfully' } - console.log(`Delete file ${filePath} from ${fullPath} result: ${result}`); - }); + console.debug( + `Delete file ${filePath} from ${fullPath} result: ${result}` + ) + }) - return result; - }); + return result + }) } diff --git a/electron/handlers/plugin.ts b/electron/handlers/plugin.ts index 26eb3c583..22bf253e6 100644 --- a/electron/handlers/plugin.ts +++ b/electron/handlers/plugin.ts @@ -30,7 +30,7 @@ export function handlePluginIPCs() { if (typeof module[method] === "function") { return module[method](...args); } else { - console.log(module[method]); + console.debug(module[method]); console.error(`Function "${method}" does not exist in the module.`); } } @@ -75,7 +75,7 @@ export function handlePluginIPCs() { const fullPath = join(userDataPath, "plugins"); rmdir(fullPath, { recursive: true }, function (err) { - if (err) console.log(err); + if (err) console.error(err); ModuleManager.instance.clearImportedModules(); // just relaunch if packaged, should launch manually in development mode diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts index 096d09bac..340db54b9 100644 --- a/electron/handlers/update.ts +++ b/electron/handlers/update.ts @@ -42,7 +42,7 @@ export function handleAppUpdates() { /* App Update Progress */ autoUpdater.on("download-progress", (progress: any) => { - console.log("app update progress: ", progress.percent); + console.debug("app update progress: ", progress.percent); WindowManager.instance.currentWindow?.webContents.send( "APP_UPDATE_PROGRESS", { diff --git a/electron/main.ts b/electron/main.ts index 741a75867..5f1d6b086 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,23 +1,23 @@ -import { app, BrowserWindow } from "electron"; -import { join } from "path"; -import { setupMenu } from "./utils/menu"; -import { handleFsIPCs } from "./handlers/fs"; +import { app, BrowserWindow } from 'electron' +import { join } from 'path' +import { setupMenu } from './utils/menu' +import { handleFsIPCs } from './handlers/fs' /** * Managers **/ -import { WindowManager } from "./managers/window"; -import { ModuleManager } from "./managers/module"; -import { PluginManager } from "./managers/plugin"; +import { WindowManager } from './managers/window' +import { ModuleManager } from './managers/module' +import { PluginManager } from './managers/plugin' /** * IPC Handlers **/ -import { handleDownloaderIPCs } from "./handlers/download"; -import { handleThemesIPCs } from "./handlers/theme"; -import { handlePluginIPCs } from "./handlers/plugin"; -import { handleAppIPCs } from "./handlers/app"; -import { handleAppUpdates } from "./handlers/update"; +import { handleDownloaderIPCs } from './handlers/download' +import { handleThemesIPCs } from './handlers/theme' +import { handlePluginIPCs } from './handlers/plugin' +import { handleAppIPCs } from './handlers/app' +import { handleAppUpdates } from './handlers/update' app .whenReady() @@ -28,56 +28,56 @@ app .then(handleAppUpdates) .then(createMainWindow) .then(() => { - app.on("activate", () => { + app.on('activate', () => { if (!BrowserWindow.getAllWindows().length) { - createMainWindow(); + createMainWindow() } - }); - }); + }) + }) -app.on("window-all-closed", () => { - ModuleManager.instance.clearImportedModules(); - app.quit(); -}); +app.on('window-all-closed', () => { + ModuleManager.instance.clearImportedModules() + app.quit() +}) -app.on("quit", () => { - ModuleManager.instance.clearImportedModules(); - app.quit(); -}); +app.on('quit', () => { + ModuleManager.instance.clearImportedModules() + app.quit() +}) function createMainWindow() { /* Create main window */ const mainWindow = WindowManager.instance.createWindow({ webPreferences: { nodeIntegration: true, - preload: join(__dirname, "preload.js"), + preload: join(__dirname, 'preload.js'), webSecurity: false, }, - }); + }) const startURL = app.isPackaged - ? `file://${join(__dirname, "../renderer/index.html")}` - : "http://localhost:3000"; + ? `file://${join(__dirname, '../renderer/index.html')}` + : 'http://localhost:3000' /* Load frontend app to the window */ - mainWindow.loadURL(startURL); + mainWindow.loadURL(startURL) - mainWindow.once("ready-to-show", () => mainWindow?.show()); - mainWindow.on("closed", () => { - if (process.platform !== "darwin") app.quit(); - }); + mainWindow.once('ready-to-show', () => mainWindow?.show()) + mainWindow.on('closed', () => { + if (process.platform !== 'darwin') app.quit() + }) /* Enable dev tools for development */ - if (!app.isPackaged) mainWindow.webContents.openDevTools(); + if (!app.isPackaged) mainWindow.webContents.openDevTools() } /** * Handles various IPC messages from the renderer process. */ function handleIPCs() { - handleFsIPCs(); - handleDownloaderIPCs(); - handleThemesIPCs(); - handlePluginIPCs(); - handleAppIPCs(); + handleFsIPCs() + handleDownloaderIPCs() + handleThemesIPCs() + handlePluginIPCs() + handleAppIPCs() } diff --git a/electron/managers/plugin.ts b/electron/managers/plugin.ts index 889425ec7..227eab34e 100644 --- a/electron/managers/plugin.ts +++ b/electron/managers/plugin.ts @@ -42,14 +42,14 @@ export class PluginManager { return new Promise((resolve) => { const store = new Store(); if (store.get("migrated_version") !== app.getVersion()) { - console.log("start migration:", store.get("migrated_version")); + console.debug("start migration:", store.get("migrated_version")); const userDataPath = app.getPath("userData"); const fullPath = join(userDataPath, "plugins"); rmdir(fullPath, { recursive: true }, function (err) { - if (err) console.log(err); + if (err) console.error(err); store.set("migrated_version", app.getVersion()); - console.log("migrate plugins done"); + console.debug("migrate plugins done"); resolve(undefined); }); } else { diff --git a/electron/preload.ts b/electron/preload.ts index 398913da2..dfba13bd1 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -33,6 +33,8 @@ * @property {Function} relaunch - Relaunches the app. * @property {Function} openAppDirectory - Opens the app directory. * @property {Function} deleteFile - Deletes the file at the given path. + * @property {Function} isDirectory - Returns true if the file at the given path is a directory. + * @property {Function} getUserSpace - Returns the user space. * @property {Function} readFile - Reads the file at the given path. * @property {Function} writeFile - Writes the given data to the file at the given path. * @property {Function} listFiles - Lists the files in the directory at the given path. @@ -52,81 +54,85 @@ */ // Make Pluggable Electron's facade available to the renderer on window.plugins -import { useFacade } from "./core/plugin/facade"; +import { useFacade } from './core/plugin/facade' -useFacade(); +useFacade() -const { contextBridge, ipcRenderer } = require("electron"); +const { contextBridge, ipcRenderer } = require('electron') -contextBridge.exposeInMainWorld("electronAPI", { +contextBridge.exposeInMainWorld('electronAPI', { invokePluginFunc: (plugin: any, method: any, ...args: any[]) => - ipcRenderer.invoke("invokePluginFunc", plugin, method, ...args), + ipcRenderer.invoke('invokePluginFunc', plugin, method, ...args), - setNativeThemeLight: () => ipcRenderer.invoke("setNativeThemeLight"), + setNativeThemeLight: () => ipcRenderer.invoke('setNativeThemeLight'), - setNativeThemeDark: () => ipcRenderer.invoke("setNativeThemeDark"), + setNativeThemeDark: () => ipcRenderer.invoke('setNativeThemeDark'), - setNativeThemeSystem: () => ipcRenderer.invoke("setNativeThemeSystem"), + setNativeThemeSystem: () => ipcRenderer.invoke('setNativeThemeSystem'), - basePlugins: () => ipcRenderer.invoke("basePlugins"), + basePlugins: () => ipcRenderer.invoke('basePlugins'), - pluginPath: () => ipcRenderer.invoke("pluginPath"), + pluginPath: () => ipcRenderer.invoke('pluginPath'), - appDataPath: () => ipcRenderer.invoke("appDataPath"), + appDataPath: () => ipcRenderer.invoke('appDataPath'), - reloadPlugins: () => ipcRenderer.invoke("reloadPlugins"), + reloadPlugins: () => ipcRenderer.invoke('reloadPlugins'), - appVersion: () => ipcRenderer.invoke("appVersion"), + appVersion: () => ipcRenderer.invoke('appVersion'), - openExternalUrl: (url: string) => ipcRenderer.invoke("openExternalUrl", url), + openExternalUrl: (url: string) => ipcRenderer.invoke('openExternalUrl', url), - relaunch: () => ipcRenderer.invoke("relaunch"), + relaunch: () => ipcRenderer.invoke('relaunch'), - openAppDirectory: () => ipcRenderer.invoke("openAppDirectory"), + openAppDirectory: () => ipcRenderer.invoke('openAppDirectory'), - deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath), + deleteFile: (filePath: string) => ipcRenderer.invoke('deleteFile', filePath), - readFile: (path: string) => ipcRenderer.invoke("readFile", path), + isDirectory: (filePath: string) => ipcRenderer.invoke('isDirectory', filePath), + + getUserSpace: () => ipcRenderer.invoke('getUserSpace'), + + readFile: (path: string) => ipcRenderer.invoke('readFile', path), writeFile: (path: string, data: string) => - ipcRenderer.invoke("writeFile", path, data), + ipcRenderer.invoke('writeFile', path, data), - listFiles: (path: string) => ipcRenderer.invoke("listFiles", path), + listFiles: (path: string) => ipcRenderer.invoke('listFiles', path), - mkdir: (path: string) => ipcRenderer.invoke("mkdir", path), + mkdir: (path: string) => ipcRenderer.invoke('mkdir', path), - rmdir: (path: string) => ipcRenderer.invoke("rmdir", path), + rmdir: (path: string) => ipcRenderer.invoke('rmdir', path), installRemotePlugin: (pluginName: string) => - ipcRenderer.invoke("installRemotePlugin", pluginName), + ipcRenderer.invoke('installRemotePlugin', pluginName), downloadFile: (url: string, path: string) => - ipcRenderer.invoke("downloadFile", url, path), + ipcRenderer.invoke('downloadFile', url, path), pauseDownload: (fileName: string) => - ipcRenderer.invoke("pauseDownload", fileName), + ipcRenderer.invoke('pauseDownload', fileName), resumeDownload: (fileName: string) => - ipcRenderer.invoke("resumeDownload", fileName), + ipcRenderer.invoke('resumeDownload', fileName), abortDownload: (fileName: string) => - ipcRenderer.invoke("abortDownload", fileName), + ipcRenderer.invoke('abortDownload', fileName), onFileDownloadUpdate: (callback: any) => - ipcRenderer.on("FILE_DOWNLOAD_UPDATE", callback), + ipcRenderer.on('FILE_DOWNLOAD_UPDATE', callback), onFileDownloadError: (callback: any) => - ipcRenderer.on("FILE_DOWNLOAD_ERROR", callback), + ipcRenderer.on('FILE_DOWNLOAD_ERROR', callback), onFileDownloadSuccess: (callback: any) => - ipcRenderer.on("FILE_DOWNLOAD_COMPLETE", callback), + ipcRenderer.on('FILE_DOWNLOAD_COMPLETE', callback), onAppUpdateDownloadUpdate: (callback: any) => - ipcRenderer.on("APP_UPDATE_PROGRESS", callback), + ipcRenderer.on('APP_UPDATE_PROGRESS', callback), onAppUpdateDownloadError: (callback: any) => - ipcRenderer.on("APP_UPDATE_ERROR", callback), + ipcRenderer.on('APP_UPDATE_ERROR', callback), onAppUpdateDownloadSuccess: (callback: any) => - ipcRenderer.on("APP_UPDATE_COMPLETE", callback), -}); + ipcRenderer.on('APP_UPDATE_COMPLETE', callback), +}) diff --git a/plugins/conversational-json/package.json b/plugins/conversational-json/package.json index 198756f82..520970664 100644 --- a/plugins/conversational-json/package.json +++ b/plugins/conversational-json/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@janhq/core": "file:../../core", + "path-browserify": "^1.0.1", "ts-loader": "^9.5.0" }, "engines": { diff --git a/plugins/conversational-json/src/index.ts b/plugins/conversational-json/src/index.ts index b87f52a84..0e8465fd5 100644 --- a/plugins/conversational-json/src/index.ts +++ b/plugins/conversational-json/src/index.ts @@ -1,12 +1,15 @@ import { PluginType, fs } from '@janhq/core' import { ConversationalPlugin } from '@janhq/core/lib/plugins' import { Conversation } from '@janhq/core/lib/types' +import { join } from 'path' /** * JSONConversationalPlugin is a ConversationalPlugin implementation that provides * functionality for managing conversations. */ export default class JSONConversationalPlugin implements ConversationalPlugin { + private static readonly _homeDir = 'threads' + /** * Returns the type of the plugin. */ @@ -18,7 +21,7 @@ export default class JSONConversationalPlugin implements ConversationalPlugin { * Called when the plugin is loaded. */ onLoad() { - fs.mkdir('conversations') + fs.mkdir(JSONConversationalPlugin._homeDir) console.debug('JSONConversationalPlugin loaded') } @@ -65,10 +68,14 @@ export default class JSONConversationalPlugin implements ConversationalPlugin { */ saveConversation(conversation: Conversation): Promise { return fs - .mkdir(`conversations/${conversation._id}`) + .mkdir(`${JSONConversationalPlugin._homeDir}/${conversation.id}`) .then(() => fs.writeFile( - `conversations/${conversation._id}/${conversation._id}.json`, + join( + JSONConversationalPlugin._homeDir, + conversation.id, + `${conversation.id}.json` + ), JSON.stringify(conversation) ) ) @@ -79,7 +86,9 @@ export default class JSONConversationalPlugin implements ConversationalPlugin { * @param conversationId The ID of the conversation to delete. */ deleteConversation(conversationId: string): Promise { - return fs.rmdir(`conversations/${conversationId}`) + return fs.rmdir( + join(JSONConversationalPlugin._homeDir, `${conversationId}`) + ) } /** @@ -88,7 +97,9 @@ export default class JSONConversationalPlugin implements ConversationalPlugin { * @returns data of the conversation */ private async readConvo(convoId: string): Promise { - return fs.readFile(`conversations/${convoId}/${convoId}.json`) + return fs.readFile( + join(JSONConversationalPlugin._homeDir, convoId, `${convoId}.json`) + ) } /** @@ -97,8 +108,10 @@ export default class JSONConversationalPlugin implements ConversationalPlugin { * @private */ private async getConversationDocs(): Promise { - return fs.listFiles(`conversations`).then((files: string[]) => { - return Promise.all(files.filter((file) => file.startsWith('jan-'))) - }) + return fs + .listFiles(JSONConversationalPlugin._homeDir) + .then((files: string[]) => { + return Promise.all(files.filter((file) => file.startsWith('jan-'))) + }) } } diff --git a/plugins/conversational-json/webpack.config.js b/plugins/conversational-json/webpack.config.js index d4b0db2bd..36e338295 100644 --- a/plugins/conversational-json/webpack.config.js +++ b/plugins/conversational-json/webpack.config.js @@ -22,6 +22,9 @@ module.exports = { plugins: [new webpack.DefinePlugin({})], resolve: { extensions: [".ts", ".js"], + fallback: { + path: require.resolve('path-browserify'), + }, }, // Do not minify the output, otherwise it breaks the class registration optimization: { diff --git a/plugins/conversational-plugin/src/index.ts b/plugins/conversational-plugin/src/index.ts index b1c5bd937..01045b6c8 100644 --- a/plugins/conversational-plugin/src/index.ts +++ b/plugins/conversational-plugin/src/index.ts @@ -83,15 +83,15 @@ export default class JanConversationalPlugin implements ConversationalPlugin { */ private parseConversationMarkdown(markdown: string): Conversation { const conversation: Conversation = { - _id: "", + id: "", name: "", messages: [], }; var currentMessage: Message | undefined = undefined; for (const line of markdown.split("\n")) { const trimmedLine = line.trim(); - if (trimmedLine.startsWith("- _id:")) { - conversation._id = trimmedLine.replace("- _id:", "").trim(); + if (trimmedLine.startsWith("- id:")) { + conversation.id = trimmedLine.replace("- id:", "").trim(); } else if (trimmedLine.startsWith("- modelId:")) { conversation.modelId = trimmedLine.replace("- modelId:", "").trim(); } else if (trimmedLine.startsWith("- name:")) { @@ -128,7 +128,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin { if (currentMessage) { conversation.messages.push(currentMessage); } - currentMessage = { _id: messageMatch[1] }; + currentMessage = { id: messageMatch[1] }; } } else if ( currentMessage?.message && @@ -170,7 +170,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin { private generateMarkdown(conversation: Conversation): string { // Generate the Markdown content based on the Conversation object const conversationMetadata = ` - - _id: ${conversation._id} + - id: ${conversation.id} - modelId: ${conversation.modelId} - name: ${conversation.name} - lastMessage: ${conversation.message} @@ -182,7 +182,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin { const messages = conversation.messages.map( (message) => ` - - Message ${message._id}: + - Message ${message.id}: - createdAt: ${message.createdAt} - user: ${message.user} - message: ${message.message?.trim()} @@ -204,10 +204,10 @@ export default class JanConversationalPlugin implements ConversationalPlugin { private async writeMarkdownToFile(conversation: Conversation) { // Generate the Markdown content const markdownContent = this.generateMarkdown(conversation); - await fs.mkdir(`conversations/${conversation._id}`); + await fs.mkdir(`conversations/${conversation.id}`); // Write the content to a Markdown file await fs.writeFile( - `conversations/${conversation._id}/${conversation._id}.md`, + `conversations/${conversation.id}/${conversation.id}.md`, markdownContent ); } diff --git a/plugins/inference-plugin/src/index.ts b/plugins/inference-plugin/src/index.ts index ebd44657f..b02c0f628 100644 --- a/plugins/inference-plugin/src/index.ts +++ b/plugins/inference-plugin/src/index.ts @@ -18,7 +18,7 @@ import { InferencePlugin } from "@janhq/core/lib/plugins"; import { requestInference } from "./helpers/sse"; import { ulid } from "ulid"; import { join } from "path"; -import { appDataPath } from "@janhq/core"; +import { fs } from "@janhq/core"; /** * A class that implements the InferencePlugin interface from the @janhq/core package. @@ -54,8 +54,10 @@ export default class JanInferencePlugin implements InferencePlugin { * @returns {Promise} A promise that resolves when the model is initialized. */ async initModel(modelFileName: string): Promise { - const appPath = await appDataPath(); - return executeOnMain(MODULE, "initModel", join(appPath, modelFileName)); + const userSpacePath = await fs.getUserSpace(); + const modelFullPath = join(userSpacePath, modelFileName); + + return executeOnMain(MODULE, "initModel", modelFullPath); } /** @@ -84,7 +86,7 @@ export default class JanInferencePlugin implements InferencePlugin { content: data.message, }, ]; - const recentMessages = await (data.history ?? prompts); + const recentMessages = data.history ?? prompts; return new Promise(async (resolve, reject) => { requestInference([ @@ -121,7 +123,7 @@ export default class JanInferencePlugin implements InferencePlugin { message: "", user: "assistant", createdAt: new Date().toISOString(), - _id: ulid(), + id: ulid(), }; events.emit(EventName.OnNewMessageResponse, message); diff --git a/plugins/inference-plugin/src/module.ts b/plugins/inference-plugin/src/module.ts index ba6afdf90..613ad9fca 100644 --- a/plugins/inference-plugin/src/module.ts +++ b/plugins/inference-plugin/src/module.ts @@ -124,7 +124,7 @@ function killSubprocess(): Promise { if (subprocess) { subprocess.kill(); subprocess = null; - console.log("Subprocess terminated."); + console.debug("Subprocess terminated."); } else { return kill(PORT, "tcp").then(console.log).catch(console.log); } diff --git a/plugins/model-plugin/.prettierrc b/plugins/model-plugin/.prettierrc new file mode 100644 index 000000000..46f1abcb0 --- /dev/null +++ b/plugins/model-plugin/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "quoteProps": "consistent", + "trailingComma": "es5", + "endOfLine": "auto", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/plugins/model-plugin/package.json b/plugins/model-plugin/package.json index 171f0a4e9..43d1ffa8e 100644 --- a/plugins/model-plugin/package.json +++ b/plugins/model-plugin/package.json @@ -29,6 +29,7 @@ ], "dependencies": { "@janhq/core": "file:../../core", + "path-browserify": "^1.0.1", "ts-loader": "^9.5.0" } } diff --git a/plugins/model-plugin/src/helpers/cloudNative.ts b/plugins/model-plugin/src/helpers/cloudNative.ts deleted file mode 100644 index 90c6d3f1e..000000000 --- a/plugins/model-plugin/src/helpers/cloudNative.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { EventName, events } from "@janhq/core"; - -export async function pollDownloadProgress(fileName: string) { - if ( - typeof window !== "undefined" && - typeof (window as any).electronAPI === "undefined" - ) { - const intervalId = setInterval(() => { - notifyProgress(fileName, intervalId); - }, 3000); - } -} - -export async function notifyProgress( - fileName: string, - intervalId: NodeJS.Timeout -): Promise { - const response = await fetch("/api/v1/downloadProgress", { - method: "POST", - body: JSON.stringify({ fileName: fileName }), - headers: { "Content-Type": "application/json", Authorization: "" }, - }); - - if (!response.ok) { - events.emit(EventName.OnDownloadError, null); - clearInterval(intervalId); - return; - } - const json = await response.json(); - if (isEmptyObject(json)) { - if (!fileName && intervalId) { - clearInterval(intervalId); - } - return Promise.resolve(""); - } - if (json.success === true) { - events.emit(EventName.OnDownloadSuccess, json); - clearInterval(intervalId); - return Promise.resolve(""); - } else { - events.emit(EventName.OnDownloadUpdate, json); - return Promise.resolve(json.fileName); - } -} - -function isEmptyObject(ojb: any): boolean { - return Object.keys(ojb).length === 0; -} diff --git a/plugins/model-plugin/src/helpers/modelParser.ts b/plugins/model-plugin/src/helpers/modelParser.ts index d8b8a81f5..826a2afba 100644 --- a/plugins/model-plugin/src/helpers/modelParser.ts +++ b/plugins/model-plugin/src/helpers/modelParser.ts @@ -1,8 +1,8 @@ export const parseToModel = (model) => { - const modelVersions = []; + const modelVersions = [] model.versions.forEach((v) => { const version = { - _id: `${model.author}-${v.name}`, + id: `${model.author}-${v.name}`, name: v.name, quantMethod: v.quantMethod, bits: v.bits, @@ -11,12 +11,12 @@ export const parseToModel = (model) => { usecase: v.usecase, downloadLink: v.downloadLink, productId: model.id, - }; - modelVersions.push(version); - }); + } + modelVersions.push(version) + }) const product = { - _id: model.id, + id: model.id, name: model.name, shortDescription: model.shortDescription, avatarUrl: model.avatarUrl, @@ -29,9 +29,9 @@ export const parseToModel = (model) => { type: model.type, createdAt: model.createdAt, longDescription: model.longDescription, - status: "Downloadable", + status: 'Downloadable', releaseDate: 0, availableVersions: modelVersions, - }; - return product; -}; + } + return product +} diff --git a/plugins/model-plugin/src/index.ts b/plugins/model-plugin/src/index.ts index ccfed6bfe..2e599c2d4 100644 --- a/plugins/model-plugin/src/index.ts +++ b/plugins/model-plugin/src/index.ts @@ -1,20 +1,21 @@ -import { PluginType, fs, downloadFile } from "@janhq/core"; -import { ModelPlugin } from "@janhq/core/lib/plugins"; -import { Model, ModelCatalog } from "@janhq/core/lib/types"; -import { pollDownloadProgress } from "./helpers/cloudNative"; -import { parseToModel } from "./helpers/modelParser"; +import { PluginType, fs, downloadFile } from '@janhq/core' +import { ModelPlugin } from '@janhq/core/lib/plugins' +import { Model, ModelCatalog } from '@janhq/core/lib/types' +import { parseToModel } from './helpers/modelParser' +import { join } from 'path' /** * A plugin for managing machine learning models. */ export default class JanModelPlugin implements ModelPlugin { + private static readonly _homeDir = 'models' /** * Implements type from JanPlugin. * @override * @returns The type of the plugin. */ type(): PluginType { - return PluginType.Model; + return PluginType.Model } /** @@ -25,6 +26,7 @@ export default class JanModelPlugin implements ModelPlugin { /** Cloud Native * TODO: Fetch all downloading progresses? **/ + fs.mkdir(JanModelPlugin._homeDir) } /** @@ -39,12 +41,13 @@ export default class JanModelPlugin implements ModelPlugin { * @returns A Promise that resolves when the model is downloaded. */ async downloadModel(model: Model): Promise { - await fs.mkdir("models"); - downloadFile(model.downloadLink, `models/${model._id}`); - /** Cloud Native - * MARK: Poll Downloading Progress - **/ - pollDownloadProgress(model._id); + // create corresponding directory + const directoryPath = join(JanModelPlugin._homeDir, model.productName) + await fs.mkdir(directoryPath) + + // path to model binary + const path = join(directoryPath, model.id) + downloadFile(model.downloadLink, path) } /** @@ -52,10 +55,15 @@ export default class JanModelPlugin implements ModelPlugin { * @param filePath - The path to the model file to delete. * @returns A Promise that resolves when the model is deleted. */ - deleteModel(filePath: string): Promise { - return fs - .deleteFile(`models/${filePath}`) - .then(() => fs.deleteFile(`models/m-${filePath}.json`)); + async deleteModel(filePath: string): Promise { + try { + await Promise.allSettled([ + fs.deleteFile(filePath), + fs.deleteFile(`${filePath}.json`), + ]) + } catch (err) { + console.error(err) + } } /** @@ -64,30 +72,46 @@ export default class JanModelPlugin implements ModelPlugin { * @returns A Promise that resolves when the model is saved. */ async saveModel(model: Model): Promise { - await fs.writeFile(`models/m-${model._id}.json`, JSON.stringify(model)); + const directoryPath = join(JanModelPlugin._homeDir, model.productName) + const jsonFilePath = join(directoryPath, `${model.id}.json`) + + try { + await fs.writeFile(jsonFilePath, JSON.stringify(model)) + } catch (err) { + console.error(err) + } } /** * Gets all downloaded models. * @returns A Promise that resolves with an array of all models. */ - getDownloadedModels(): Promise { - return fs - .listFiles("models") - .then((files: string[]) => { - return Promise.all( - files - .filter((file) => /^m-.*\.json$/.test(file)) - .map(async (file) => { - const model: Model = JSON.parse( - await fs.readFile(`models/${file}`) - ); - return model; - }) - ); - }) - .catch((e) => fs.mkdir("models").then(() => [])); + async getDownloadedModels(): Promise { + const results: Model[] = [] + const allDirs: string[] = await fs.listFiles(JanModelPlugin._homeDir) + for (const dir of allDirs) { + const modelDirPath = join(JanModelPlugin._homeDir, dir) + const isModelDir = await fs.isDirectory(modelDirPath) + if (!isModelDir) { + // if not a directory, ignore + continue + } + + const jsonFiles: string[] = (await fs.listFiles(modelDirPath)).filter( + (file: string) => file.endsWith('.json') + ) + + for (const json of jsonFiles) { + const model: Model = JSON.parse( + await fs.readFile(join(modelDirPath, json)) + ) + results.push(model) + } + } + + return results } + /** * Gets all available models. * @returns A Promise that resolves with an array of all models. @@ -96,10 +120,6 @@ export default class JanModelPlugin implements ModelPlugin { // Add a timestamp to the URL to prevent caching return import( /* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}` - ).then((module) => - module.default.map((e) => { - return parseToModel(e); - }) - ); + ).then((module) => module.default.map((e) => parseToModel(e))) } } diff --git a/plugins/model-plugin/webpack.config.js b/plugins/model-plugin/webpack.config.js index 60fa1a9b0..3475516ed 100644 --- a/plugins/model-plugin/webpack.config.js +++ b/plugins/model-plugin/webpack.config.js @@ -1,16 +1,16 @@ -const path = require("path"); -const webpack = require("webpack"); -const packageJson = require("./package.json"); +const path = require('path') +const webpack = require('webpack') +const packageJson = require('./package.json') module.exports = { experiments: { outputModule: true }, - entry: "./src/index.ts", // Adjust the entry point to match your project's main file - mode: "production", + entry: './src/index.ts', // Adjust the entry point to match your project's main file + mode: 'production', module: { rules: [ { test: /\.tsx?$/, - use: "ts-loader", + use: 'ts-loader', exclude: /node_modules/, }, ], @@ -20,20 +20,23 @@ module.exports = { PLUGIN_NAME: JSON.stringify(packageJson.name), MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`), MODEL_CATALOG_URL: JSON.stringify( - "https://cdn.jsdelivr.net/npm/@janhq/models@latest/dist/index.js" + 'https://cdn.jsdelivr.net/npm/@janhq/models@latest/dist/index.js' ), }), ], output: { - filename: "index.js", // Adjust the output file name as needed - path: path.resolve(__dirname, "dist"), - library: { type: "module" }, // Specify ESM output format + filename: 'index.js', // Adjust the output file name as needed + path: path.resolve(__dirname, 'dist'), + library: { type: 'module' }, // Specify ESM output format }, resolve: { - extensions: [".ts", ".js"], + extensions: ['.ts', '.js'], + fallback: { + path: require.resolve('path-browserify'), + }, }, optimization: { minimize: false, }, // Add loaders and other configuration as needed for your project -}; +} diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 9752888fa..9153da2c7 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -46,7 +46,7 @@ const BottomBar = () => { ⌘e to show your model ) } diff --git a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx index 108aa8e82..fee918f3a 100644 --- a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx +++ b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx @@ -24,7 +24,7 @@ export default function CommandListDownloadedModel() { const { activeModel, startModel, stopModel } = useActiveModel() const onModelActionClick = (modelId: string) => { - if (activeModel && activeModel._id === modelId) { + if (activeModel && activeModel.id === modelId) { stopModel(modelId) } else { startModel(modelId) @@ -62,7 +62,7 @@ export default function CommandListDownloadedModel() { { - onModelActionClick(model._id) + onModelActionClick(model.id) setOpen(false) }} > @@ -72,7 +72,7 @@ export default function CommandListDownloadedModel() { />

{model.name} - {activeModel && activeModel._id === model._id && ( + {activeModel && activeModel.id === model.id && ( Active )}
diff --git a/web/containers/ModalCancelDownload/index.tsx b/web/containers/ModalCancelDownload/index.tsx index 2f753d705..e62dda0ca 100644 --- a/web/containers/ModalCancelDownload/index.tsx +++ b/web/containers/ModalCancelDownload/index.tsx @@ -32,9 +32,9 @@ export default function ModalCancelDownload({ const { modelDownloadStateAtom } = useDownloadState() useGetPerformanceTag() const downloadAtom = useMemo( - () => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]), + () => atom((get) => get(modelDownloadStateAtom)[suitableModel.id]), // eslint-disable-next-line react-hooks/exhaustive-deps - [suitableModel._id] + [suitableModel.id] ) const downloadState = useAtomValue(downloadAtom) diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index f1b943427..ff2934282 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -1,15 +1,22 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ReactNode, useEffect, useRef } from 'react' -import { events, EventName, NewMessageResponse, PluginType } from '@janhq/core' - +import { + events, + EventName, + NewMessageResponse, + PluginType, + ChatMessage, +} from '@janhq/core' +import { Conversation, Message, MessageStatus } from '@janhq/core' import { ConversationalPlugin, ModelPlugin } from '@janhq/core/lib/plugins' -import { Message } from '@janhq/core/lib/types' import { useAtomValue, useSetAtom } from 'jotai' import { useDownloadState } from '@/hooks/useDownloadState' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' +import { toChatMessage } from '@/utils/message' + import { addNewMessageAtom, chatMessages, @@ -20,11 +27,8 @@ import { updateConversationWaitingForResponseAtom, userConversationsAtom, } from '@/helpers/atoms/Conversation.atom' - import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' -import { MessageStatus, toChatMessage } from '@/models/ChatMessage' import { pluginManager } from '@/plugin' -import { ChatMessage, Conversation } from '@/types/chatMessage' let currentConversation: Conversation | undefined = undefined @@ -50,9 +54,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { async function handleNewMessageResponse(message: NewMessageResponse) { if (message.conversationId) { - const convo = convoRef.current.find( - (e) => e._id == message.conversationId - ) + const convo = convoRef.current.find((e) => e.id == message.conversationId) if (!convo) return const newResponse = toChatMessage(message) addNewMessage(newResponse) @@ -63,11 +65,11 @@ export default function EventHandler({ children }: { children: ReactNode }) { ) { if ( messageResponse.conversationId && - messageResponse._id && + messageResponse.id && messageResponse.message ) { updateMessage( - messageResponse._id, + messageResponse.id, messageResponse.conversationId, messageResponse.message, MessageStatus.Pending @@ -77,11 +79,11 @@ export default function EventHandler({ children }: { children: ReactNode }) { if (messageResponse.conversationId) { if ( !currentConversation || - currentConversation._id !== messageResponse.conversationId + currentConversation.id !== messageResponse.conversationId ) { if (convoRef.current && messageResponse.conversationId) currentConversation = convoRef.current.find( - (e) => e._id == messageResponse.conversationId + (e) => e.id == messageResponse.conversationId ) } @@ -104,11 +106,11 @@ export default function EventHandler({ children }: { children: ReactNode }) { if ( messageResponse.conversationId && - messageResponse._id && + messageResponse.id && messageResponse.message ) { updateMessage( - messageResponse._id, + messageResponse.id, messageResponse.conversationId, messageResponse.message, MessageStatus.Ready @@ -116,27 +118,23 @@ export default function EventHandler({ children }: { children: ReactNode }) { } const convo = convoRef.current.find( - (e) => e._id == messageResponse.conversationId + (e) => e.id == messageResponse.conversationId ) if (convo) { - const messagesData = (messagesRef.current ?? [])[convo._id].map( - (e: ChatMessage) => { - return { - // eslint-disable-next-line @typescript-eslint/naming-convention - _id: e.id, - message: e.text, - user: e.senderUid, - updatedAt: new Date(e.createdAt).toISOString(), - createdAt: new Date(e.createdAt).toISOString(), - } - } + const messagesData = (messagesRef.current ?? [])[convo.id].map( + (e: ChatMessage) => ({ + id: e.id, + message: e.text, + user: e.senderUid, + updatedAt: new Date(e.createdAt).toISOString(), + createdAt: new Date(e.createdAt).toISOString(), + }) ) pluginManager .get(PluginType.Conversational) ?.saveConversation({ ...convo, - // eslint-disable-next-line @typescript-eslint/naming-convention - _id: convo._id ?? '', + id: convo.id ?? '', name: convo.name ?? '', message: convo.lastMessage ?? '', messages: messagesData, @@ -153,7 +151,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { if (state && state.fileName && state.success === true) { state.fileName = state.fileName.replace('models/', '') setDownloadStateSuccess(state.fileName) - const model = models.find((e) => e._id === state.fileName) + const model = models.find((e) => e.id === state.fileName) if (model) pluginManager .get(PluginType.Model) diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index 528cd254b..2e91a70cf 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -33,14 +33,17 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) { window.electronAPI.onFileDownloadUpdate( (_event: string, state: DownloadState | undefined) => { if (!state) return - setDownloadState(state) + setDownloadState({ + ...state, + fileName: state.fileName.split('/').pop() ?? '', + }) } ) window.electronAPI.onFileDownloadError( (_event: string, callback: any) => { - console.log('Download error', callback) - const fileName = callback.fileName.replace('models/', '') + console.error('Download error', callback) + const fileName = callback.fileName.split('/').pop() ?? '' setDownloadStateFailed(fileName) } ) @@ -48,10 +51,10 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) { window.electronAPI.onFileDownloadSuccess( (_event: string, callback: any) => { if (callback && callback.fileName) { - const fileName = callback.fileName.replace('models/', '') + const fileName = callback.fileName.split('/').pop() ?? '' setDownloadStateSuccess(fileName) - const model = modelsRef.current.find((e) => e._id === fileName) + const model = modelsRef.current.find((e) => e.id === fileName) if (model) pluginManager .get(PluginType.Model) @@ -66,13 +69,13 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) { window.electronAPI.onAppUpdateDownloadUpdate( (_event: string, progress: any) => { setProgress(progress.percent) - console.log('app update progress:', progress.percent) + console.debug('app update progress:', progress.percent) } ) window.electronAPI.onAppUpdateDownloadError( (_event: string, callback: any) => { - console.log('Download error', callback) + console.error('Download error', callback) setProgress(-1) } ) diff --git a/web/helpers/atoms/ChatMessage.atom.ts b/web/helpers/atoms/ChatMessage.atom.ts index c45808288..d343d0f7c 100644 --- a/web/helpers/atoms/ChatMessage.atom.ts +++ b/web/helpers/atoms/ChatMessage.atom.ts @@ -1,9 +1,8 @@ +import { ChatMessage, MessageStatus } from '@janhq/core' import { atom } from 'jotai' import { getActiveConvoIdAtom } from './Conversation.atom' -import { ChatMessage, MessageStatus } from '@/models/ChatMessage' - /** * Stores all chat messages for all conversations */ diff --git a/web/helpers/atoms/Conversation.atom.ts b/web/helpers/atoms/Conversation.atom.ts index 3a661d385..2265c5c2e 100644 --- a/web/helpers/atoms/Conversation.atom.ts +++ b/web/helpers/atoms/Conversation.atom.ts @@ -1,8 +1,6 @@ -import { Conversation, ConversationState } from '@/types/chatMessage' +import { Conversation, ConversationState } from '@janhq/core' import { atom } from 'jotai' -// import { MainViewState, setMainViewStateAtom } from './MainView.atom' - /** * Stores the current active conversation id. */ @@ -78,13 +76,13 @@ export const updateConversationHasMoreAtom = atom( export const updateConversationAtom = atom( null, (get, set, conversation: Conversation) => { - const id = conversation._id + const id = conversation.id if (!id) return - const convo = get(userConversationsAtom).find((c) => c._id === id) + const convo = get(userConversationsAtom).find((c) => c.id === id) if (!convo) return const newConversations: Conversation[] = get(userConversationsAtom).map( - (c) => (c._id === id ? conversation : c) + (c) => (c.id === id ? conversation : c) ) // sort new conversations based on updated at @@ -103,5 +101,5 @@ export const updateConversationAtom = atom( */ export const userConversationsAtom = atom([]) export const currentConversationAtom = atom((get) => - get(userConversationsAtom).find((c) => c._id === get(getActiveConvoIdAtom)) + get(userConversationsAtom).find((c) => c.id === get(getActiveConvoIdAtom)) ) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index a9d517fbf..efe05672e 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -10,6 +10,7 @@ import { toaster } from '@/containers/Toast' import { useGetDownloadedModels } from './useGetDownloadedModels' import { pluginManager } from '@/plugin' +import { join } from 'path' const activeAssistantModelAtom = atom(undefined) @@ -21,16 +22,16 @@ export function useActiveModel() { const { downloadedModels } = useGetDownloadedModels() const startModel = async (modelId: string) => { - if (activeModel && activeModel._id === modelId) { + if (activeModel && activeModel.id === modelId) { console.debug(`Model ${modelId} is already init. Ignore..`) return } setStateModel({ state: 'start', loading: true, model: modelId }) - const model = await downloadedModels.find((e) => e._id === modelId) + const model = downloadedModels.find((e) => e.id === modelId) - if (!modelId) { + if (!model) { alert(`Model ${modelId} not found! Please re-download the model first.`) setStateModel(() => ({ state: 'start', @@ -42,8 +43,8 @@ export function useActiveModel() { const currentTime = Date.now() console.debug('Init model: ', modelId) - - const res = await initModel(`models/${modelId}`) + const path = join('models', model.productName, modelId) + const res = await initModel(path) if (res?.error) { const errorMessage = `${res.error}` alert(errorMessage) diff --git a/web/hooks/useCreateConversation.ts b/web/hooks/useCreateConversation.ts index 755876c0a..e5f2a669f 100644 --- a/web/hooks/useCreateConversation.ts +++ b/web/hooks/useCreateConversation.ts @@ -1,7 +1,6 @@ import { PluginType } from '@janhq/core' +import { Conversation, Model } from '@janhq/core' import { ConversationalPlugin } from '@janhq/core/lib/plugins' -import { Model } from '@janhq/core/lib/types' - import { useAtom, useSetAtom } from 'jotai' import { generateConversationId } from '@/utils/conversation' @@ -12,7 +11,6 @@ import { addNewConversationStateAtom, } from '@/helpers/atoms/Conversation.atom' import { pluginManager } from '@/plugin' -import { Conversation } from '@/types/chatMessage' export const useCreateConversation = () => { const [userConversations, setUserConversations] = useAtom( @@ -24,15 +22,15 @@ export const useCreateConversation = () => { const requestCreateConvo = async (model: Model) => { const conversationName = model.name const mappedConvo: Conversation = { - // eslint-disable-next-line @typescript-eslint/naming-convention - _id: generateConversationId(), - modelId: model._id, + id: generateConversationId(), + modelId: model.id, name: conversationName, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + messages: [], } - addNewConvoState(mappedConvo._id, { + addNewConvoState(mappedConvo.id, { hasMore: true, waitingForResponse: false, }) @@ -45,7 +43,7 @@ export const useCreateConversation = () => { messages: [], }) setUserConversations([mappedConvo, ...userConversations]) - setActiveConvoId(mappedConvo._id) + setActiveConvoId(mappedConvo.id) } return { diff --git a/web/hooks/useDeleteConversation.ts b/web/hooks/useDeleteConversation.ts index 8826aab5b..459b527b5 100644 --- a/web/hooks/useDeleteConversation.ts +++ b/web/hooks/useDeleteConversation.ts @@ -41,7 +41,7 @@ export default function useDeleteConversation() { .get(PluginType.Conversational) ?.deleteConversation(activeConvoId) const currentConversations = userConversations.filter( - (c) => c._id !== activeConvoId + (c) => c.id !== activeConvoId ) setUserConversations(currentConversations) deleteMessages(activeConvoId) @@ -50,7 +50,7 @@ export default function useDeleteConversation() { description: `Delete chat with ${activeModel?.name} has been completed`, }) if (currentConversations.length > 0) { - setActiveConvoId(currentConversations[0]._id) + setActiveConvoId(currentConversations[0].id) } else { setActiveConvoId(undefined) } diff --git a/web/hooks/useDeleteModel.ts b/web/hooks/useDeleteModel.ts index 1980e4514..4d527e8cb 100644 --- a/web/hooks/useDeleteModel.ts +++ b/web/hooks/useDeleteModel.ts @@ -7,20 +7,20 @@ import { toaster } from '@/containers/Toast' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { pluginManager } from '@/plugin/PluginManager' +import { join } from 'path' export default function useDeleteModel() { const { setDownloadedModels, downloadedModels } = useGetDownloadedModels() const deleteModel = async (model: Model) => { - await pluginManager - .get(PluginType.Model) - ?.deleteModel(model._id) + const path = join('models', model.productName, model.id) + await pluginManager.get(PluginType.Model)?.deleteModel(path) // reload models - setDownloadedModels(downloadedModels.filter((e) => e._id !== model._id)) + setDownloadedModels(downloadedModels.filter((e) => e.id !== model.id)) toaster({ title: 'Delete a Model', - description: `Model ${model._id} has been deleted.`, + description: `Model ${model.id} has been deleted.`, }) } diff --git a/web/hooks/useDownloadModel.ts b/web/hooks/useDownloadModel.ts index 29a41333b..3ec1bf330 100644 --- a/web/hooks/useDownloadModel.ts +++ b/web/hooks/useDownloadModel.ts @@ -21,7 +21,7 @@ export default function useDownloadModel() { ): Model => { return { // eslint-disable-next-line @typescript-eslint/naming-convention - _id: modelVersion._id, + id: modelVersion.id, name: modelVersion.name, quantMethod: modelVersion.quantMethod, bits: modelVersion.bits, @@ -31,7 +31,7 @@ export default function useDownloadModel() { downloadLink: modelVersion.downloadLink, startDownloadAt: modelVersion.startDownloadAt, finishDownloadAt: modelVersion.finishDownloadAt, - productId: model._id, + productId: model.id, productName: model.name, shortDescription: model.shortDescription, longDescription: model.longDescription, @@ -53,7 +53,7 @@ export default function useDownloadModel() { ) => { // set an initial download state setDownloadState({ - modelId: modelVersion._id, + modelId: modelVersion.id, time: { elapsed: 0, remaining: 0, @@ -64,7 +64,7 @@ export default function useDownloadModel() { total: 0, transferred: 0, }, - fileName: modelVersion._id, + fileName: modelVersion.id, }) modelVersion.startDownloadAt = Date.now() diff --git a/web/hooks/useGetInputState.ts b/web/hooks/useGetInputState.ts index 26a1c83d7..d1b52f080 100644 --- a/web/hooks/useGetInputState.ts +++ b/web/hooks/useGetInputState.ts @@ -1,13 +1,9 @@ import { useEffect, useState } from 'react' - -import { Model } from '@janhq/core/lib/types' +import { Model, Conversation } from '@janhq/core' import { useAtomValue } from 'jotai' - import { useActiveModel } from './useActiveModel' import { useGetDownloadedModels } from './useGetDownloadedModels' - import { currentConversationAtom } from '@/helpers/atoms/Conversation.atom' -import { Conversation } from '@/types/chatMessage' export default function useGetInputState() { const [inputState, setInputState] = useState('loading') @@ -27,7 +23,7 @@ export default function useGetInputState() { // check if convo model id is in downloaded models const isModelAvailable = downloadedModels.some( - (model) => model._id === convo.modelId + (model) => model.id === convo.modelId ) if (!isModelAvailable) { @@ -36,7 +32,7 @@ export default function useGetInputState() { return } - if (convo.modelId !== currentModel._id) { + if (convo.modelId !== currentModel.id) { // in case convo model and active model is different, // ask user to init the required model setInputState('model-mismatch') diff --git a/web/hooks/useGetUserConversations.ts b/web/hooks/useGetUserConversations.ts index 35e659ee2..5f0ad7435 100644 --- a/web/hooks/useGetUserConversations.ts +++ b/web/hooks/useGetUserConversations.ts @@ -1,16 +1,16 @@ -import { PluginType } from '@janhq/core' +import { PluginType, ChatMessage, ConversationState } from '@janhq/core' import { ConversationalPlugin } from '@janhq/core/lib/plugins' import { Conversation } from '@janhq/core/lib/types' import { useSetAtom } from 'jotai' +import { toChatMessage } from '@/utils/message' + import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { conversationStatesAtom, userConversationsAtom, } from '@/helpers/atoms/Conversation.atom' -import { toChatMessage } from '@/models/ChatMessage' import { pluginManager } from '@/plugin/PluginManager' -import { ChatMessage, ConversationState } from '@/types/chatMessage' const useGetUserConversations = () => { const setConversationStates = useSetAtom(conversationStatesAtom) @@ -24,19 +24,19 @@ const useGetUserConversations = () => { ?.getConversations() const convoStates: Record = {} convos?.forEach((convo) => { - convoStates[convo._id ?? ''] = { + convoStates[convo.id ?? ''] = { hasMore: true, waitingForResponse: false, } setConvoMessages( convo.messages.map((msg) => toChatMessage(msg)), - convo._id ?? '' + convo.id ?? '' ) }) setConversationStates(convoStates) setConversations(convos ?? []) } catch (error) { - console.log(error) + console.error(error) } } diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 256c701ef..5d5e1598c 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -4,13 +4,13 @@ import { NewMessageRequest, PluginType, events, + ChatMessage, + Message, + Conversation, + MessageSenderType, } from '@janhq/core' - import { ConversationalPlugin, InferencePlugin } from '@janhq/core/lib/plugins' - -import { Message } from '@janhq/core/lib/types' import { useAtom, useAtomValue, useSetAtom } from 'jotai' - import { currentPromptAtom } from '@/containers/Providers/Jotai' import { ulid } from 'ulid' import { @@ -22,10 +22,8 @@ import { updateConversationAtom, updateConversationWaitingForResponseAtom, } from '@/helpers/atoms/Conversation.atom' -import { MessageSenderType, toChatMessage } from '@/models/ChatMessage' - import { pluginManager } from '@/plugin/PluginManager' -import { ChatMessage, Conversation } from '@/types/chatMessage' +import { toChatMessage } from '@/utils/message' export default function useSendChatMessage() { const currentConvo = useAtomValue(currentConversationAtom) @@ -59,7 +57,7 @@ export default function useSendChatMessage() { if ( result?.message && result.message.split(' ').length <= 10 && - conv?._id + conv?.id ) { const updatedConv = { ...conv, @@ -73,7 +71,7 @@ export default function useSendChatMessage() { name: updatedConv.name ?? '', message: updatedConv.lastMessage ?? '', messages: currentMessages.map((e: ChatMessage) => ({ - _id: e.id, + id: e.id, message: e.text, user: e.senderUid, updatedAt: new Date(e.createdAt).toISOString(), @@ -87,7 +85,11 @@ export default function useSendChatMessage() { } const sendChatMessage = async () => { - const convoId = currentConvo?._id as string + const convoId = currentConvo?.id + if (!convoId) { + console.error('No conversation id') + return + } setCurrentPrompt('') updateConvWaiting(convoId, true) @@ -106,8 +108,7 @@ export default function useSendChatMessage() { } as MessageHistory, ]) const newMessage: NewMessageRequest = { - // eslint-disable-next-line @typescript-eslint/naming-convention - _id: ulid(), + id: ulid(), conversationId: convoId, message: prompt, user: MessageSenderType.User, diff --git a/web/models/ChatMessage.ts b/web/models/ChatMessage.ts deleted file mode 100644 index b7f18d932..000000000 --- a/web/models/ChatMessage.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { NewMessageResponse } from '@janhq/core' -import { Message } from '@janhq/core/lib/types' - -export enum MessageType { - Text = 'Text', - Image = 'Image', - ImageWithText = 'ImageWithText', - Error = 'Error', -} - -export enum MessageSenderType { - Ai = 'assistant', - User = 'user', -} - -export enum MessageStatus { - Ready = 'ready', - Pending = 'pending', -} - -export interface ChatMessage { - id: string - conversationId: string - messageType: MessageType - messageSenderType: MessageSenderType - senderUid: string - senderName: string - senderAvatarUrl: string - text: string | undefined - imageUrls?: string[] | undefined - createdAt: number - status: MessageStatus -} - -export interface RawMessage { - _id?: string - conversationId?: string - user?: string - avatar?: string - message?: string - createdAt?: string - updatedAt?: string -} - -export const toChatMessage = ( - m: RawMessage | Message | NewMessageResponse, - - conversationId?: string -): ChatMessage => { - const createdAt = new Date(m.createdAt ?? '').getTime() - const imageUrls: string[] = [] - const imageUrl = undefined - if (imageUrl) { - imageUrls.push(imageUrl) - } - - const messageType = MessageType.Text - const messageSenderType = - m.user === 'user' ? MessageSenderType.User : MessageSenderType.Ai - - const content = m.message ?? '' - - const senderName = m.user === 'user' ? 'You' : 'Assistant' - - return { - id: (m._id ?? 0).toString(), - conversationId: ( - (m as RawMessage | NewMessageResponse)?.conversationId ?? - conversationId ?? - 0 - ).toString(), - messageType: messageType, - messageSenderType: messageSenderType, - senderUid: m.user?.toString() || '0', - senderName: senderName, - senderAvatarUrl: - m.user === 'user' ? 'icons/avatar.svg' : 'icons/app_icon.svg', - text: content, - imageUrls: imageUrls, - createdAt: createdAt, - status: MessageStatus.Ready, - } -} diff --git a/web/screens/Chat/ChatItem/index.tsx b/web/screens/Chat/ChatItem/index.tsx index 6b4f0b3f8..86163bbbe 100644 --- a/web/screens/Chat/ChatItem/index.tsx +++ b/web/screens/Chat/ChatItem/index.tsx @@ -1,7 +1,6 @@ import React, { forwardRef } from 'react' - +import { ChatMessage } from '@janhq/core' import SimpleTextMessage from '../SimpleTextMessage' -import { ChatMessage } from '@/types/chatMessage' type Props = { message: ChatMessage diff --git a/web/screens/Chat/HistoryList/index.tsx b/web/screens/Chat/HistoryList/index.tsx index 628023e0c..34587e2ae 100644 --- a/web/screens/Chat/HistoryList/index.tsx +++ b/web/screens/Chat/HistoryList/index.tsx @@ -46,16 +46,16 @@ export default function HistoryList() { console.debug('modelId is undefined') return } - const model = downloadedModels.find((e) => e._id === convo.modelId) + const model = downloadedModels.find((e) => e.id === convo.modelId) if (convo == null) { console.debug('modelId is undefined') return } if (model != null) { - startModel(model._id) + startModel(model.id) } - if (activeConvoId !== convo._id) { - setActiveConvoId(convo._id) + if (activeConvoId !== convo.id) { + setActiveConvoId(convo.id) } } @@ -88,7 +88,7 @@ export default function HistoryList() { key={i} className={twMerge( 'relative flex cursor-pointer flex-col border-b border-border px-4 py-2 hover:bg-secondary/20', - activeConvoId === convo._id && 'bg-secondary-10' + activeConvoId === convo.id && 'bg-secondary-10' )} onClick={() => handleActiveModel(convo as Conversation)} > @@ -100,7 +100,7 @@ export default function HistoryList() {

{convo?.lastMessage ?? 'No new message'}

- {activeModel && activeConvoId === convo._id && ( + {activeModel && activeConvoId === convo.id && ( { const conversations = useAtomValue(userConversationsAtom) const isEnableChat = (currentConvo && activeModel) || conversations.length > 0 const [isModelAvailable, setIsModelAvailable] = useState( - downloadedModels.some((x) => x._id === currentConvo?.modelId) + downloadedModels.some((x) => x.id === currentConvo?.modelId) ) const textareaRef = useRef(null) @@ -72,7 +72,7 @@ const ChatScreen = () => { useEffect(() => { setIsModelAvailable( - downloadedModels.some((x) => x._id === currentConvo?.modelId) + downloadedModels.some((x) => x.id === currentConvo?.modelId) ) }, [currentConvo, downloadedModels]) @@ -196,7 +196,7 @@ const ChatScreen = () => { disabled={ !activeModel || stateModel.loading || - activeModel._id !== currentConvo?.modelId + activeModel.id !== currentConvo?.modelId } value={currentPrompt} onChange={(e) => { diff --git a/web/screens/ExploreModels/ExploreModelItem/index.tsx b/web/screens/ExploreModels/ExploreModelItem/index.tsx index b13816d39..a201210e5 100644 --- a/web/screens/ExploreModels/ExploreModelItem/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItem/index.tsx @@ -105,7 +105,7 @@ const ExploreModelItem = forwardRef(({ model }, ref) => { )} diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index fd4babfd3..bf55aec61 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -35,8 +35,8 @@ const ExploreModelItemHeader: React.FC = ({ const { performanceTag, title, getPerformanceForModel } = useGetPerformanceTag() const downloadAtom = useMemo( - () => atom((get) => get(modelDownloadStateAtom)[suitableModel._id]), - [suitableModel._id] + () => atom((get) => get(modelDownloadStateAtom)[suitableModel.id]), + [suitableModel.id] ) const downloadState = useAtomValue(downloadAtom) const { setMainViewState } = useMainViewState() @@ -52,7 +52,7 @@ const ExploreModelItemHeader: React.FC = ({ }, [exploreModel, suitableModel]) const isDownloaded = - downloadedModels.find((model) => model._id === suitableModel._id) != null + downloadedModels.find((model) => model.id === suitableModel.id) != null let downloadButton = (