Merge branch 'main' into fix1089
This commit is contained in:
commit
7a08526784
52
.github/workflows/jan-electron-build-nightly.yml
vendored
52
.github/workflows/jan-electron-build-nightly.yml
vendored
@ -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 }}
|
||||
|
||||
19
.github/workflows/jan-electron-build.yml
vendored
19
.github/workflows/jan-electron-build.yml
vendored
@ -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/')
|
||||
|
||||
@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align: center">
|
||||
<td style="text-align:center"><b>Experimental (Nighlty Build)</b></td>
|
||||
<td style="text-align:center" colspan="4">
|
||||
<a href='https://github.com/janhq/jan/actions/runs/7253202390'>
|
||||
<a href='https://github.com/janhq/jan/actions/runs/7266993261'>
|
||||
<b>Github action artifactory</b>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@ -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"
|
||||
|
||||
44
electron/sign.js
Normal file
44
electron/sign.js
Normal file
@ -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
|
||||
});
|
||||
};
|
||||
@ -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)
|
||||
|
||||
@ -1 +1 @@
|
||||
0.1.30
|
||||
0.1.32
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<ModelOperationResponse> {
|
||||
async function killSubprocess(): Promise<void> {
|
||||
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<void> {
|
||||
})
|
||||
.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<void> {
|
||||
* Should run exactly platform specified Nitro binary version
|
||||
*/
|
||||
function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
||||
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<any> {
|
||||
});
|
||||
|
||||
subprocess.stderr.on("data", (data) => {
|
||||
console.log("subprocess error:" + data.toString());
|
||||
console.error("subprocess error:" + data.toString());
|
||||
console.error(`stderr: ${data}`);
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<FieldValues>
|
||||
}
|
||||
|
||||
const Checkbox: React.FC<Props> = ({ name, title, checked, register }) => {
|
||||
const [currentChecked, setCurrentChecked] = useState<boolean>(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 (
|
||||
<div className="flex justify-between">
|
||||
<label>{title}</label>
|
||||
<Switch
|
||||
checked={currentChecked}
|
||||
checked={checked}
|
||||
{...register(name)}
|
||||
onCheckedChange={(e) => {
|
||||
setCurrentChecked(e)
|
||||
}}
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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<FieldValues>
|
||||
}
|
||||
|
||||
const SliderRightPanel: React.FC<Props> = ({
|
||||
@ -30,30 +30,19 @@ const SliderRightPanel: React.FC<Props> = ({
|
||||
value,
|
||||
register,
|
||||
}) => {
|
||||
const [currentValue, setCurrentValue] = useState<number>(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<Props> = ({
|
||||
<div className="relative w-full">
|
||||
<Slider
|
||||
{...register(name, {
|
||||
setValueAs: (v: any) => 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<Props> = ({
|
||||
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)])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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<string, ThreadMessage[]> = {
|
||||
...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
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
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 {
|
||||
|
||||
@ -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<ConversationalExtension>(ExtensionType.Conversational)
|
||||
?.getThreads()) ?? []
|
||||
|
||||
const threadStates: Record<string, ThreadState> = {}
|
||||
const threadModelParams: Record<string, ModelRuntimeParams> = {}
|
||||
|
||||
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
|
||||
@ -4,13 +4,10 @@ import { Assistant, ExtensionType, AssistantExtension } from '@janhq/core'
|
||||
|
||||
import { extensionManager } from '@/extension/ExtensionManager'
|
||||
|
||||
export const getAssistants = async (): Promise<Assistant[]> => {
|
||||
return (
|
||||
export const getAssistants = async (): Promise<Assistant[]> =>
|
||||
extensionManager
|
||||
.get<AssistantExtension>(ExtensionType.Assistant)
|
||||
?.getAssistants() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks for get assistants
|
||||
|
||||
@ -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) {
|
||||
|
||||
95
web/hooks/useThreads.ts
Normal file
95
web/hooks/useThreads.ts
Normal file
@ -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<string, ThreadState> = {}
|
||||
const threadModelParams: Record<string, ModelRuntimeParams> = {}
|
||||
|
||||
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<string, ThreadState> = {}
|
||||
|
||||
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<Thread[]> =>
|
||||
(await extensionManager
|
||||
.get<ConversationalExtension>(ExtensionType.Conversational)
|
||||
?.getThreads()) ?? []
|
||||
|
||||
export default useThreads
|
||||
@ -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 <div>This thread has no model parameters</div>
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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<FieldValues>
|
||||
) => {
|
||||
const components = componentData.map((data) => {
|
||||
switch (data.controllerType) {
|
||||
|
||||
@ -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
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
onClick={() => deleteThread(thread.id)}
|
||||
>
|
||||
<Trash2Icon size={16} className="text-muted-foreground" />
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<div className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary">
|
||||
<Trash2Icon
|
||||
size={16}
|
||||
className="text-muted-foreground"
|
||||
/>
|
||||
<span className="text-bold text-black dark:text-muted-foreground">
|
||||
Delete thread
|
||||
</span>
|
||||
</div>
|
||||
</ModalTrigger>
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<ModalTitle>Delete Thread</ModalTitle>
|
||||
</ModalHeader>
|
||||
<p>Are you sure you want to delete this thread?</p>
|
||||
<ModalFooter>
|
||||
<div className="flex gap-x-2">
|
||||
<ModalClose asChild>
|
||||
<Button themes="ghost">No</Button>
|
||||
</ModalClose>
|
||||
<ModalClose asChild>
|
||||
<Button
|
||||
themes="danger"
|
||||
onClick={() => deleteThread(thread.id)}
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
{/* {messages.length > 0 && (
|
||||
|
||||
@ -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<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
||||
@ -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<Props> = ({ model, onClick, open }) => {
|
||||
const { downloadModel } = useDownloadModel()
|
||||
const { downloadedModels } = useGetDownloadedModels()
|
||||
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
|
||||
const { startModel } = useActiveModel()
|
||||
// const [title, setTitle] = useState<string>('Recommended')
|
||||
|
||||
// const [performanceTag, setPerformanceTag] = useState<TagType>(
|
||||
// ModelPerformance.PerformancePositive
|
||||
// )
|
||||
const { requestCreateNewThread } = useCreateNewThread()
|
||||
|
||||
const downloadAtom = useMemo(
|
||||
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
|
||||
@ -59,10 +53,15 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
||||
<Button onClick={() => onDownloadClick()}>Download</Button>
|
||||
)
|
||||
|
||||
const onUseModelClick = () => {
|
||||
startModel(model.id)
|
||||
setMainViewState(MainViewState.Thread)
|
||||
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<Props> = ({ model, onClick, open }) => {
|
||||
downloadButton = <ModalCancelDownload model={model} />
|
||||
}
|
||||
|
||||
// const renderBadge = (performance: TagType) => {
|
||||
// switch (performance) {
|
||||
// case ModelPerformance.PerformancePositive:
|
||||
// return <Badge themes="success">{title}</Badge>
|
||||
|
||||
// case ModelPerformance.PerformanceNeutral:
|
||||
// return <Badge themes="secondary">{title}</Badge>
|
||||
|
||||
// case ModelPerformance.PerformanceNegative:
|
||||
// return <Badge themes="danger">{title}</Badge>
|
||||
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<div
|
||||
className="cursor-pointer rounded-t-md bg-background/50"
|
||||
|
||||
@ -3,10 +3,6 @@ import { useState } from 'react'
|
||||
import {
|
||||
Input,
|
||||
ScrollArea,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipArrow,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
@ -17,10 +13,6 @@ import {
|
||||
|
||||
import { SearchIcon } from 'lucide-react'
|
||||
|
||||
import { Code2Icon, UserIcon } from 'lucide-react'
|
||||
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import Loader from '@/containers/Loader'
|
||||
|
||||
import { useGetConfiguredModels } from '@/hooks/useGetConfiguredModels'
|
||||
@ -32,7 +24,6 @@ import ExploreModelList from './ExploreModelList'
|
||||
const ExploreModelsScreen = () => {
|
||||
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())
|
||||
|
||||
@ -6,15 +6,19 @@ export default function AppearanceOptions() {
|
||||
<div className="block w-full">
|
||||
<div className="flex w-full items-center justify-between border-b border-border py-3 first:pt-0 last:border-none">
|
||||
<div className="flex-shrink-0 space-y-1">
|
||||
<h6 className="text-sm font-semibold capitalize">Themes</h6>
|
||||
<p className="leading-relaxed ">Choose your default theme.</p>
|
||||
<h6 className="text-sm font-semibold capitalize">
|
||||
Base color scheme
|
||||
</h6>
|
||||
<p className="leading-relaxed ">Choose Jan default color scheme.</p>
|
||||
</div>
|
||||
<ToggleTheme />
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between border-b border-border py-3 first:pt-0 last:border-none">
|
||||
<div className="flex-shrink-0 space-y-1">
|
||||
<h6 className="text-sm font-semibold capitalize">Primary color</h6>
|
||||
<p className="leading-relaxed ">Choose your primary color.</p>
|
||||
<h6 className="text-sm font-semibold capitalize">Accent Color</h6>
|
||||
<p className="leading-relaxed ">
|
||||
Choose the accent color used throughout the app.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleAccent />
|
||||
</div>
|
||||
|
||||
@ -25,13 +25,13 @@ export default function SystemMonitorScreen() {
|
||||
<ScrollArea className="h-full w-full">
|
||||
<div className="h-full p-8" data-test-id="testid-system-monitor">
|
||||
<div className="grid grid-cols-2 gap-8 lg:grid-cols-3">
|
||||
<div className="rounded-xl border border-border px-8 py-6">
|
||||
<div className="rounded-xl border border-border p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-base font-bold uppercase">
|
||||
ram ({Math.round((usedRam / totalRam) * 100)}%)
|
||||
</h4>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{toGigabytes(usedRam)} GB of {toGigabytes(totalRam)} GB used
|
||||
{toGigabytes(usedRam)} of {toGigabytes(totalRam)} used
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
@ -41,7 +41,7 @@ export default function SystemMonitorScreen() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border px-8 py-6">
|
||||
<div className="rounded-xl border border-border p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-base font-bold uppercase">
|
||||
cpu ({cpuUsage}%)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user