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() {
/>
-