diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml index fe28e6003..1db805d4e 100644 --- a/.github/workflows/jan-electron-build-nightly.yml +++ b/.github/workflows/jan-electron-build-nightly.yml @@ -132,14 +132,14 @@ jobs: continue-on-error: true run: | ls -al ./electron/dist - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} @@ -203,15 +203,22 @@ jobs: jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json + jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + + - name: Install AzureSignTool + run: | + dotnet tool install --global AzureSignTool + - name: Build app run: | make build - - - name: Windows Code Sign with AzureSignTool - run: | - dotnet tool install --global AzureSignTool - cd ./electron/dist - azuresigntool.exe sign -kvu "${{ secrets.AZURE_KEY_VAULT_URI }}" -kvi "${{ secrets.AZURE_CLIENT_ID }}" -kvt "${{ secrets.AZURE_TENANT_ID }}" -kvs "${{ secrets.AZURE_CLIENT_SECRET }}" -kvc ${{ secrets.AZURE_CERT_NAME }} -tr http://timestamp.globalsign.com/tsa/r6advanced1 -v "jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" + env: + AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }} - name: Upload Artifact uses: actions/upload-artifact@v2 @@ -223,10 +230,11 @@ jobs: shell: bash run: | ls -al ./electron/dist - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest.yml" --body "./electron/dist/latest.yml" --content-type "text/yaml" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest.yml" --body "./electron/dist/latest.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe.blockmap" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe.blockmap" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest.yml" --body "./electron/dist/latest.yml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest.yml" --body "./electron/dist/latest.yml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} @@ -305,10 +313,10 @@ jobs: - name: put-object using awscli s3api run: | ls -al ./electron/dist - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" - echo "q" | aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml index 5acf13b7d..eba68fea1 100644 --- a/.github/workflows/jan-electron-build.yml +++ b/.github/workflows/jan-electron-build.yml @@ -120,21 +120,26 @@ jobs: fi jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json + jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json env: VERSION_TAG: ${{ steps.tag.outputs.tag }} + - name: Install AzureSignTool + run: | + dotnet tool install --global AzureSignTool + - name: Build app run: | make build env: ANALYTICS_ID: ${{ secrets.JAN_APP_POSTHOG_PROJECT_API_KEY }} ANALYTICS_HOST: ${{ secrets.JAN_APP_POSTHOG_URL }} - - - name: Windows Code Sign with AzureSignTool - run: | - dotnet tool install --global AzureSignTool - cd ./electron/dist - azuresigntool.exe sign -kvu "${{ secrets.AZURE_KEY_VAULT_URI }}" -kvi "${{ secrets.AZURE_CLIENT_ID }}" -kvt "${{ secrets.AZURE_TENANT_ID }}" -kvs "${{ secrets.AZURE_CLIENT_SECRET }}" -kvc ${{ secrets.AZURE_CERT_NAME }} -tr http://timestamp.globalsign.com/tsa/r6advanced1 -v "jan-win-x64-${{ needs.create-draft-release.outputs.version }}.exe" + AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }} - uses: actions/upload-release-asset@v1.0.1 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') @@ -154,7 +159,7 @@ jobs: upload_url: ${{ needs.create-draft-release.outputs.upload_url }} asset_path: ./electron/dist/jan-win-x64-${{ needs.create-draft-release.outputs.version }}.exe.blockmap asset_name: jan-win-x64-${{ needs.create-draft-release.outputs.version }}.exe.blockmap - asset_content_type: text/xml + asset_content_type: application/octet-stream - uses: actions/upload-release-asset@v1.0.1 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') diff --git a/README.md b/README.md index c1473f946..1ebd63b45 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nighlty Build) - + Github action artifactory diff --git a/electron/package.json b/electron/package.json index 989144d58..864934d56 100644 --- a/electron/package.json +++ b/electron/package.json @@ -45,7 +45,10 @@ "icon": "icons/" }, "win": { - "icon": "icons/icon.png" + "icon": "icons/icon.png", + "target": [ + "nsis" + ] }, "artifactName": "jan-${os}-${arch}-${version}.${ext}" }, @@ -73,7 +76,7 @@ "@types/request": "^2.48.12", "@uiball/loaders": "^1.3.0", "electron-store": "^8.1.0", - "electron-updater": "^6.1.4", + "electron-updater": "^6.1.7", "fs-extra": "^11.2.0", "pacote": "^17.0.4", "request": "^2.88.2", @@ -88,7 +91,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "electron": "28.0.0", - "electron-builder": "^24.6.4", + "electron-builder": "^24.9.1", "electron-playwright-helpers": "^1.6.0", "eslint-plugin-react": "^7.33.2", "run-script-os": "^1.1.6" diff --git a/electron/sign.js b/electron/sign.js new file mode 100644 index 000000000..6e973eb6e --- /dev/null +++ b/electron/sign.js @@ -0,0 +1,44 @@ +const { exec } = require('child_process'); + + +function sign({ path, name, certUrl, clientId, tenantId, clientSecret, certName, timestampServer, version }) { + return new Promise((resolve, reject) => { + + const command = `azuresigntool.exe sign -kvu "${certUrl}" -kvi "${clientId}" -kvt "${tenantId}" -kvs "${clientSecret}" -kvc "${certName}" -tr "${timestampServer}" -v "${path}"`; + + + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(`Error: ${error}`); + return reject(error); + } + console.log(`stdout: ${stdout}`); + console.error(`stderr: ${stderr}`); + resolve(); + }); + }); +} + + +exports.default = async function(options) { + + const certUrl = process.env.AZURE_KEY_VAULT_URI; + const clientId = process.env.AZURE_CLIENT_ID; + const tenantId = process.env.AZURE_TENANT_ID; + const clientSecret = process.env.AZURE_CLIENT_SECRET; + const certName = process.env.AZURE_CERT_NAME; + const timestampServer = 'http://timestamp.globalsign.com/tsa/r6advanced1'; + + + await sign({ + path: options.path, + name: "jan-win-x64", + certUrl, + clientId, + tenantId, + clientSecret, + certName, + timestampServer, + version: options.version + }); +}; diff --git a/extensions/conversational-extension/src/index.ts b/extensions/conversational-extension/src/index.ts index 5a2537239..0fdf0b2d4 100644 --- a/extensions/conversational-extension/src/index.ts +++ b/extensions/conversational-extension/src/index.ts @@ -55,7 +55,7 @@ export default class JSONConversationalExtension convos.sort( (a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime() ) - console.debug('getThreads', JSON.stringify(convos, null, 2)) + return convos } catch (error) { console.error(error) diff --git a/extensions/inference-nitro-extension/bin/version.txt b/extensions/inference-nitro-extension/bin/version.txt index 013adb716..28d007539 100644 --- a/extensions/inference-nitro-extension/bin/version.txt +++ b/extensions/inference-nitro-extension/bin/version.txt @@ -1 +1 @@ -0.1.30 +0.1.32 diff --git a/extensions/inference-nitro-extension/src/index.ts b/extensions/inference-nitro-extension/src/index.ts index 4bcfe05b0..4aa15a7a9 100644 --- a/extensions/inference-nitro-extension/src/index.ts +++ b/extensions/inference-nitro-extension/src/index.ts @@ -227,7 +227,7 @@ export default class JanInferenceNitroExtension implements InferenceExtension { events.emit(EventName.OnMessageUpdate, message); }, error: async (err) => { - if (instance.isCancelled) { + if (instance.isCancelled || message.content.length > 0) { message.status = MessageStatus.Ready; events.emit(EventName.OnMessageUpdate, message); return; diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index bc39e8fca..bca0b6fcc 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -71,7 +71,7 @@ async function loadModel(nitroResourceProbe: any | undefined) { .then(() => loadLLMModel(currentSettings)) .then(validateModelStatus) .catch((err) => { - console.log("error: ", err); + console.error("error: ", err); // TODO: Broadcast error so app could display proper error message return { error: err, currentModelFile }; }); @@ -172,7 +172,7 @@ async function validateModelStatus(): Promise { async function killSubprocess(): Promise { const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); - console.log("Start requesting to kill Nitro..."); + console.debug("Start requesting to kill Nitro..."); return fetch(NITRO_HTTP_KILL_URL, { method: "DELETE", signal: controller.signal, @@ -183,7 +183,7 @@ async function killSubprocess(): Promise { }) .catch(() => {}) .then(() => tcpPortUsed.waitUntilFree(PORT, 300, 5000)) - .then(() => console.log("Nitro is killed")); + .then(() => console.debug("Nitro is killed")); } /** * Look for the Nitro binary and execute it @@ -191,7 +191,7 @@ async function killSubprocess(): Promise { * Should run exactly platform specified Nitro binary version */ function spawnNitroProcess(nitroResourceProbe: any): Promise { - console.log("Starting Nitro subprocess..."); + console.debug("Starting Nitro subprocess..."); return new Promise(async (resolve, reject) => { let binaryFolder = path.join(__dirname, "bin"); // Current directory by default let binaryName; @@ -221,7 +221,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise { }); subprocess.stderr.on("data", (data) => { - console.log("subprocess error:" + data.toString()); + console.error("subprocess error:" + data.toString()); console.error(`stderr: ${data}`); }); diff --git a/extensions/inference-openai-extension/src/index.ts b/extensions/inference-openai-extension/src/index.ts index 42c9c3798..64c429664 100644 --- a/extensions/inference-openai-extension/src/index.ts +++ b/extensions/inference-openai-extension/src/index.ts @@ -217,7 +217,7 @@ export default class JanInferenceOpenAIExtension implements InferenceExtension { events.emit(EventName.OnMessageUpdate, message); }, error: async (err) => { - if (instance.isCancelled) { + if (instance.isCancelled || message.content.length > 0) { message.status = MessageStatus.Ready; events.emit(EventName.OnMessageUpdate, message); return; diff --git a/uikit/src/button/styles.scss b/uikit/src/button/styles.scss index 481508841..2b1e239a5 100644 --- a/uikit/src/button/styles.scss +++ b/uikit/src/button/styles.scss @@ -1,6 +1,6 @@ .btn { @apply inline-flex items-center justify-center whitespace-nowrap rounded-lg font-semibold transition-colors; - @apply focus-visible:ring-ring cursor-pointer focus-visible:outline-none focus-visible:ring-1; + @apply cursor-pointer focus:outline-none focus-visible:outline-none focus-visible:ring-0; @apply disabled:pointer-events-none disabled:opacity-50; &-primary { @@ -28,7 +28,7 @@ } &-ghost { - @apply hover:bg-primary hover:text-primary-foreground; + @apply hover:bg-secondary hover:text-secondary-foreground; } &-sm { diff --git a/web/containers/Checkbox/index.tsx b/web/containers/Checkbox/index.tsx index f9f7c17e8..8cb2f5d70 100644 --- a/web/containers/Checkbox/index.tsx +++ b/web/containers/Checkbox/index.tsx @@ -1,6 +1,6 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useState } from 'react' +import { FieldValues, UseFormRegister } from 'react-hook-form' +import { ModelRuntimeParams } from '@janhq/core' import { Switch } from '@janhq/uikit' import { useAtomValue } from 'jotai' @@ -16,44 +16,32 @@ type Props = { name: string title: string checked: boolean - register: any + register: UseFormRegister } const Checkbox: React.FC = ({ name, title, checked, register }) => { - const [currentChecked, setCurrentChecked] = useState(checked) const { updateModelParameter } = useUpdateModelParameters() const threadId = useAtomValue(getActiveThreadIdAtom) const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) - useEffect(() => { - setCurrentChecked(checked) - }, [checked]) + const onCheckedChange = (checked: boolean) => { + if (!threadId || !activeModelParams) return - useEffect(() => { - updateSetting() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentChecked]) - - const updateValue = [name].reduce((accumulator, value) => { - return { ...accumulator, [value]: currentChecked } - }, {}) - - const updateSetting = () => { - return updateModelParameter(String(threadId), { + const updatedModelParams: ModelRuntimeParams = { ...activeModelParams, - ...updateValue, - }) + [name]: checked, + } + + updateModelParameter(threadId, updatedModelParams) } return (
{ - setCurrentChecked(e) - }} + onCheckedChange={onCheckedChange} />
) diff --git a/web/containers/Slider/index.tsx b/web/containers/Slider/index.tsx index c8daa17fb..8f37aed59 100644 --- a/web/containers/Slider/index.tsx +++ b/web/containers/Slider/index.tsx @@ -1,6 +1,6 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useState } from 'react' +import { FieldValues, UseFormRegister } from 'react-hook-form' +import { ModelRuntimeParams } from '@janhq/core' import { Slider, Input } from '@janhq/uikit' import { useAtomValue } from 'jotai' @@ -18,7 +18,7 @@ type Props = { max: number step: number value: number - register: any + register: UseFormRegister } const SliderRightPanel: React.FC = ({ @@ -30,30 +30,19 @@ const SliderRightPanel: React.FC = ({ value, register, }) => { - const [currentValue, setCurrentValue] = useState(value) const { updateModelParameter } = useUpdateModelParameters() const threadId = useAtomValue(getActiveThreadIdAtom) const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) - useEffect(() => { - setCurrentValue(value) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]) + const onValueChanged = (e: number[]) => { + if (!threadId || !activeModelParams) return - useEffect(() => { - updateSetting() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentValue]) - - const updateValue = [name].reduce((accumulator, value) => { - return { ...accumulator, [value]: currentValue } - }, {}) - - const updateSetting = () => { - return updateModelParameter(String(threadId), { + const updatedModelParams: ModelRuntimeParams = { ...activeModelParams, - ...updateValue, - }) + [name]: Number(e[0]), + } + + updateModelParameter(threadId, updatedModelParams) } return ( @@ -63,14 +52,10 @@ const SliderRightPanel: React.FC = ({
parseInt(v), + setValueAs: (v: string) => parseInt(v), })} - value={[currentValue]} - onValueChange={async (e) => { - setCurrentValue(Number(e[0])) - await updateSetting() - }} - type="range" + value={[value]} + onValueChange={onValueChanged} min={min} max={max} step={step} @@ -87,11 +72,8 @@ const SliderRightPanel: React.FC = ({ className="-mt-4 h-8 w-16" min={min} max={max} - value={String(currentValue)} - onChange={async (e) => { - setCurrentValue(Number(e.target.value)) - await updateSetting() - }} + value={String(value)} + onChange={(e) => onValueChanged([Number(e.target.value)])} />
diff --git a/web/helpers/atoms/ChatMessage.atom.ts b/web/helpers/atoms/ChatMessage.atom.ts index 33309e6fc..b11e8f3be 100644 --- a/web/helpers/atoms/ChatMessage.atom.ts +++ b/web/helpers/atoms/ChatMessage.atom.ts @@ -60,19 +60,21 @@ export const addOldMessagesAtom = atom( export const addNewMessageAtom = atom( null, (get, set, newMessage: ThreadMessage) => { - const threadId = get(getActiveThreadIdAtom) - if (!threadId) return - - const currentMessages = get(chatMessages)[threadId] ?? [] + const currentMessages = get(chatMessages)[newMessage.thread_id] ?? [] const updatedMessages = [...currentMessages, newMessage] const newData: Record = { ...get(chatMessages), } - newData[threadId] = updatedMessages + newData[newMessage.thread_id] = updatedMessages set(chatMessages, newData) + // Update thread last message - set(updateThreadStateLastMessageAtom, threadId, newMessage.content) + set( + updateThreadStateLastMessageAtom, + newMessage.thread_id, + newMessage.content + ) } ) diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts index 954929553..b7544f74e 100644 --- a/web/hooks/useCreateNewThread.ts +++ b/web/hooks/useCreateNewThread.ts @@ -5,11 +5,14 @@ import { Thread, ThreadAssistantInfo, ThreadState, + Model, } from '@janhq/core' import { atom, useAtomValue, useSetAtom } from 'jotai' import { generateThreadId } from '@/utils/thread' +import useDeleteThread from './useDeleteThread' + import { extensionManager } from '@/extension' import { threadsAtom, @@ -46,27 +49,33 @@ export const useCreateNewThread = () => { setThreadModelRuntimeParamsAtom ) - const requestCreateNewThread = async (assistant: Assistant) => { + const { deleteThread } = useDeleteThread() + + const requestCreateNewThread = async ( + assistant: Assistant, + model?: Model | undefined + ) => { // loop through threads state and filter if there's any thread that is not finish init - let hasUnfinishedInitThread = false + let unfinishedInitThreadId: string | undefined = undefined for (const key in threadStates) { const isFinishInit = threadStates[key].isFinishInit ?? true if (!isFinishInit) { - hasUnfinishedInitThread = true + unfinishedInitThreadId = key break } } - if (hasUnfinishedInitThread) { - return + if (unfinishedInitThreadId) { + await deleteThread(unfinishedInitThreadId) } + const modelId = model ? model.id : '*' const createdAt = Date.now() const assistantInfo: ThreadAssistantInfo = { assistant_id: assistant.id, assistant_name: assistant.name, model: { - id: '*', + id: modelId, settings: {}, parameters: { stream: true, diff --git a/web/hooks/useDeleteThread.ts b/web/hooks/useDeleteThread.ts index 8822b6aa8..320fe045c 100644 --- a/web/hooks/useDeleteThread.ts +++ b/web/hooks/useDeleteThread.ts @@ -1,5 +1,9 @@ -import { ChatCompletionRole, ExtensionType } from '@janhq/core' -import { ConversationalExtension } from '@janhq/core' +import { + ChatCompletionRole, + ExtensionType, + ConversationalExtension, +} from '@janhq/core' + import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { currentPromptAtom } from '@/containers/Providers/Jotai' @@ -19,6 +23,7 @@ import { threadsAtom, setActiveThreadIdAtom, deleteThreadStateAtom, + threadStatesAtom, } from '@/helpers/atoms/Thread.atom' export default function useDeleteThread() { @@ -32,6 +37,8 @@ export default function useDeleteThread() { const cleanMessages = useSetAtom(cleanChatMessagesAtom) const deleteThreadState = useSetAtom(deleteThreadStateAtom) + const threadStates = useAtomValue(threadStatesAtom) + const cleanThread = async (threadId: string) => { if (threadId) { const thread = threads.filter((c) => c.id === threadId)[0] @@ -59,15 +66,21 @@ export default function useDeleteThread() { const availableThreads = threads.filter((c) => c.id !== threadId) setThreads(availableThreads) + const deletingThreadState = threadStates[threadId] + const isFinishInit = deletingThreadState?.isFinishInit ?? true + // delete the thread state deleteThreadState(threadId) - deleteMessages(threadId) - setCurrentPrompt('') - toaster({ - title: 'Thread successfully deleted.', - description: `Thread with ${activeModel?.name} has been successfully deleted.`, - }) + if (isFinishInit) { + deleteMessages(threadId) + setCurrentPrompt('') + toaster({ + title: 'Thread successfully deleted.', + description: `Thread with ${activeModel?.name} has been successfully deleted.`, + }) + } + if (availableThreads.length > 0) { setActiveThreadId(availableThreads[0].id) } else { diff --git a/web/hooks/useGetAllThreads.ts b/web/hooks/useGetAllThreads.ts deleted file mode 100644 index 867434617..000000000 --- a/web/hooks/useGetAllThreads.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ExtensionType, ModelRuntimeParams, ThreadState } from '@janhq/core' -import { ConversationalExtension } from '@janhq/core' -import { useSetAtom } from 'jotai' - -import { extensionManager } from '@/extension/ExtensionManager' -import { - threadModelRuntimeParamsAtom, - threadStatesAtom, - threadsAtom, -} from '@/helpers/atoms/Thread.atom' - -const useGetAllThreads = () => { - const setThreadStates = useSetAtom(threadStatesAtom) - const setThreads = useSetAtom(threadsAtom) - const setThreadModelRuntimeParams = useSetAtom(threadModelRuntimeParamsAtom) - - const getAllThreads = async () => { - try { - const threads = - (await extensionManager - .get(ExtensionType.Conversational) - ?.getThreads()) ?? [] - - const threadStates: Record = {} - const threadModelParams: Record = {} - - threads.forEach((thread) => { - if (thread.id != null) { - const lastMessage = (thread.metadata?.lastMessage as string) ?? '' - - threadStates[thread.id] = { - hasMore: true, - waitingForResponse: false, - lastMessage, - isFinishInit: true, - } - - // model params - const modelParams = thread.assistants?.[0]?.model?.parameters - threadModelParams[thread.id] = modelParams - } - }) - - // updating app states - setThreadStates(threadStates) - setThreads(threads) - setThreadModelRuntimeParams(threadModelParams) - } catch (error) { - console.error(error) - } - } - - return { - getAllThreads, - } -} - -export default useGetAllThreads diff --git a/web/hooks/useGetAssistants.ts b/web/hooks/useGetAssistants.ts index c40e2861e..0fa66c9c9 100644 --- a/web/hooks/useGetAssistants.ts +++ b/web/hooks/useGetAssistants.ts @@ -4,13 +4,10 @@ import { Assistant, ExtensionType, AssistantExtension } from '@janhq/core' import { extensionManager } from '@/extension/ExtensionManager' -export const getAssistants = async (): Promise => { - return ( - extensionManager - .get(ExtensionType.Assistant) - ?.getAssistants() ?? [] - ) -} +export const getAssistants = async (): Promise => + extensionManager + .get(ExtensionType.Assistant) + ?.getAssistants() ?? [] /** * Hooks for get assistants diff --git a/web/hooks/useRecommendedModel.ts b/web/hooks/useRecommendedModel.ts index 944faf83b..6dc5771f1 100644 --- a/web/hooks/useRecommendedModel.ts +++ b/web/hooks/useRecommendedModel.ts @@ -57,6 +57,17 @@ export default function useRecommendedModel() { } return + } else { + const modelId = activeThread.assistants[0]?.model.id + if (modelId !== '*') { + const models = await getAndSortDownloadedModels() + const model = models.find((model) => model.id === modelId) + + if (model) { + setRecommendedModel(model) + } + return + } } if (activeModel) { diff --git a/web/hooks/useThreads.ts b/web/hooks/useThreads.ts new file mode 100644 index 000000000..69145de05 --- /dev/null +++ b/web/hooks/useThreads.ts @@ -0,0 +1,95 @@ +import { + ExtensionType, + ModelRuntimeParams, + Thread, + ThreadState, +} from '@janhq/core' +import { ConversationalExtension } from '@janhq/core' +import { useAtom } from 'jotai' + +import { extensionManager } from '@/extension/ExtensionManager' +import { + threadModelRuntimeParamsAtom, + threadStatesAtom, + threadsAtom, +} from '@/helpers/atoms/Thread.atom' + +const useThreads = () => { + const [threadStates, setThreadStates] = useAtom(threadStatesAtom) + const [threads, setThreads] = useAtom(threadsAtom) + const [threadModelRuntimeParams, setThreadModelRuntimeParams] = useAtom( + threadModelRuntimeParamsAtom + ) + + const getThreads = async () => { + try { + const localThreads = await getLocalThreads() + const localThreadStates: Record = {} + const threadModelParams: Record = {} + + localThreads.forEach((thread) => { + if (thread.id != null) { + const lastMessage = (thread.metadata?.lastMessage as string) ?? '' + + localThreadStates[thread.id] = { + hasMore: false, + waitingForResponse: false, + lastMessage, + isFinishInit: true, + } + + // model params + const modelParams = thread.assistants?.[0]?.model?.parameters + threadModelParams[thread.id] = modelParams + } + }) + + // allow at max 1 unfinished init thread and it should be at the top of the list + let unfinishedThreadId: string | undefined = undefined + const unfinishedThreadState: Record = {} + + for (const key of Object.keys(threadStates)) { + const threadState = threadStates[key] + if (threadState.isFinishInit === false) { + unfinishedThreadState[key] = threadState + unfinishedThreadId = key + break + } + } + const unfinishedThread: Thread | undefined = threads.find( + (thread) => thread.id === unfinishedThreadId + ) + + let allThreads: Thread[] = [...localThreads] + if (unfinishedThread) { + allThreads = [unfinishedThread, ...localThreads] + } + + if (unfinishedThreadId) { + localThreadStates[unfinishedThreadId] = + unfinishedThreadState[unfinishedThreadId] + + threadModelParams[unfinishedThreadId] = + threadModelRuntimeParams[unfinishedThreadId] + } + + // updating app states + setThreadStates(localThreadStates) + setThreads(allThreads) + setThreadModelRuntimeParams(threadModelParams) + } catch (error) { + console.error(error) + } + } + + return { + getAllThreads: getThreads, + } +} + +const getLocalThreads = async (): Promise => + (await extensionManager + .get(ExtensionType.Conversational) + ?.getThreads()) ?? [] + +export default useThreads diff --git a/web/screens/Chat/ModelSetting/index.tsx b/web/screens/Chat/ModelSetting/index.tsx index e8c9b2453..dbd4c7892 100644 --- a/web/screens/Chat/ModelSetting/index.tsx +++ b/web/screens/Chat/ModelSetting/index.tsx @@ -1,5 +1,3 @@ -import { useEffect, useState } from 'react' - import { useForm } from 'react-hook-form' import { ModelRuntimeParams } from '@janhq/core' @@ -11,42 +9,31 @@ import settingComponentBuilder, { SettingComponentData, } from './settingComponentBuilder' -import { - getActiveThreadIdAtom, - getActiveThreadModelRuntimeParamsAtom, -} from '@/helpers/atoms/Thread.atom' +import { getActiveThreadModelRuntimeParamsAtom } from '@/helpers/atoms/Thread.atom' export default function ModelSetting() { - const threadId = useAtomValue(getActiveThreadIdAtom) - const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) - const [modelParams, setModelParams] = useState< - ModelRuntimeParams | undefined - >(activeModelParams) - const { register } = useForm() + const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) - useEffect(() => { - setModelParams(activeModelParams) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [threadId]) - - if (!modelParams) { - return
This thread has no model parameters
+ if (!activeModelParams) { + return null } const componentData: SettingComponentData[] = [] - Object.keys(modelParams).forEach((key) => { + Object.keys(activeModelParams).forEach((key) => { const componentSetting = presetConfiguration[key] if (componentSetting) { if ('value' in componentSetting.controllerData) { componentSetting.controllerData.value = Number( - modelParams[key as keyof ModelRuntimeParams] + activeModelParams[key as keyof ModelRuntimeParams] ) } else if ('checked' in componentSetting.controllerData) { - componentSetting.controllerData.checked = modelParams[ + const checked = activeModelParams[ key as keyof ModelRuntimeParams ] as boolean + + componentSetting.controllerData.checked = checked } componentData.push(componentSetting) } diff --git a/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx b/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx index 604e70773..761704241 100644 --- a/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx +++ b/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-case-declarations */ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { FieldValues, UseFormRegister } from 'react-hook-form' import Checkbox from '@/containers/Checkbox' import Slider from '@/containers/Slider' @@ -27,7 +27,7 @@ type CheckboxData = { const settingComponentBuilder = ( componentData: SettingComponentData[], - register: any + register: UseFormRegister ) => { const components = componentData.map((data) => { switch (data.controllerType) { diff --git a/web/screens/Chat/ThreadList/index.tsx b/web/screens/Chat/ThreadList/index.tsx index 5b5a8d91d..a32faa92f 100644 --- a/web/screens/Chat/ThreadList/index.tsx +++ b/web/screens/Chat/ThreadList/index.tsx @@ -1,5 +1,16 @@ import { useEffect } from 'react' +import { + Modal, + ModalTrigger, + ModalClose, + ModalFooter, + ModalContent, + ModalHeader, + ModalTitle, + Button, +} from '@janhq/uikit' + import { motion as m } from 'framer-motion' import { useAtomValue } from 'jotai' import { @@ -13,12 +24,13 @@ import { twMerge } from 'tailwind-merge' import { useCreateNewThread } from '@/hooks/useCreateNewThread' import useDeleteThread from '@/hooks/useDeleteThread' -import useGetAllThreads from '@/hooks/useGetAllThreads' import useGetAssistants from '@/hooks/useGetAssistants' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import useSetActiveThread from '@/hooks/useSetActiveThread' +import useThreads from '@/hooks/useThreads' + import { displayDate } from '@/utils/datetime' import { @@ -30,7 +42,7 @@ import { export default function ThreadList() { const threads = useAtomValue(threadsAtom) const threadStates = useAtomValue(threadStatesAtom) - const { getAllThreads } = useGetAllThreads() + const { getAllThreads } = useThreads() const { assistants } = useGetAssistants() const { requestCreateNewThread } = useCreateNewThread() const activeThread = useAtomValue(activeThreadAtom) @@ -107,15 +119,40 @@ export default function ThreadList() { Clean thread -
deleteThread(thread.id)} - > - - - Delete thread - -
+ + +
+ + + Delete thread + +
+
+ + + Delete Thread + +

Are you sure you want to delete this thread?

+ +
+ + + + + + +
+
+
+
{/* {messages.length > 0 && ( diff --git a/web/screens/Chat/index.tsx b/web/screens/Chat/index.tsx index 8f2e30e09..1de97f394 100644 --- a/web/screens/Chat/index.tsx +++ b/web/screens/Chat/index.tsx @@ -75,14 +75,12 @@ const ChatScreen = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [waitingToSendMessage, activeThreadId]) - const resizeTextArea = () => { + useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = '40px' textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px' } - } - - useEffect(resizeTextArea, [currentPrompt]) + }, [currentPrompt]) const onKeyDown = async (e: React.KeyboardEvent) => { if (e.key === 'Enter') { diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index ba23056c6..40813225f 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -14,11 +14,10 @@ import ModalCancelDownload from '@/containers/ModalCancelDownload' import { MainViewState } from '@/constants/screens' -// import { ModelPerformance, TagType } from '@/constants/tagType' - -import { useActiveModel } from '@/hooks/useActiveModel' +import { useCreateNewThread } from '@/hooks/useCreateNewThread' import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' +import { getAssistants } from '@/hooks/useGetAssistants' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' @@ -34,12 +33,7 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const { downloadModel } = useDownloadModel() const { downloadedModels } = useGetDownloadedModels() const { modelDownloadStateAtom, downloadStates } = useDownloadState() - const { startModel } = useActiveModel() - // const [title, setTitle] = useState('Recommended') - - // const [performanceTag, setPerformanceTag] = useState( - // ModelPerformance.PerformancePositive - // ) + const { requestCreateNewThread } = useCreateNewThread() const downloadAtom = useMemo( () => atom((get) => get(modelDownloadStateAtom)[model.id]), @@ -59,10 +53,15 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { ) - const onUseModelClick = () => { - startModel(model.id) + const onUseModelClick = useCallback(async () => { + const assistants = await getAssistants() + if (assistants.length === 0) { + alert('No assistant available') + return + } + await requestCreateNewThread(assistants[0], model) setMainViewState(MainViewState.Thread) - } + }, []) if (isDownloaded) { downloadButton = ( @@ -80,22 +79,6 @@ const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { downloadButton = } - // const renderBadge = (performance: TagType) => { - // switch (performance) { - // case ModelPerformance.PerformancePositive: - // return {title} - - // case ModelPerformance.PerformanceNeutral: - // return {title} - - // case ModelPerformance.PerformanceNegative: - // return {title} - - // default: - // break - // } - // } - return (
{ const { loading, models } = useGetConfiguredModels() const [searchValue, setsearchValue] = useState('') - const [tabActive, setTabActive] = useState('Model') const { downloadedModels } = useGetDownloadedModels() const [sortSelected, setSortSelected] = useState('All Models') const sortMenu = ['All Models', 'Recommended', 'Downloaded'] @@ -45,8 +36,8 @@ const ExploreModelsScreen = () => { ) } else if (sortSelected === 'Recommended') { return ( - x.metadata.tags.includes('Featured') || - x.metadata.tags.includes('Recommended') + x.name.toLowerCase().includes(searchValue.toLowerCase()) && + x.metadata.tags.includes('Featured') ) } else { return x.name.toLowerCase().includes(searchValue.toLowerCase()) diff --git a/web/screens/Settings/Appearance/index.tsx b/web/screens/Settings/Appearance/index.tsx index 6a5619359..0409cb10e 100644 --- a/web/screens/Settings/Appearance/index.tsx +++ b/web/screens/Settings/Appearance/index.tsx @@ -6,15 +6,19 @@ export default function AppearanceOptions() {
-
Themes
-

Choose your default theme.

+
+ Base color scheme +
+

Choose Jan default color scheme.

-
Primary color
-

Choose your primary color.

+
Accent Color
+

+ Choose the accent color used throughout the app. +

diff --git a/web/screens/SystemMonitor/index.tsx b/web/screens/SystemMonitor/index.tsx index 51547f292..61286599a 100644 --- a/web/screens/SystemMonitor/index.tsx +++ b/web/screens/SystemMonitor/index.tsx @@ -25,13 +25,13 @@ export default function SystemMonitorScreen() {
-
+

ram ({Math.round((usedRam / totalRam) * 100)}%)

- {toGigabytes(usedRam)} GB of {toGigabytes(totalRam)} GB used + {toGigabytes(usedRam)} of {toGigabytes(totalRam)} used
@@ -41,7 +41,7 @@ export default function SystemMonitorScreen() { />
-
+

cpu ({cpuUsage}%)