From 49ad85584318145ccba760f4ea89d6a1ea612fee Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 7 Mar 2024 09:36:42 +0700 Subject: [PATCH 01/36] feat: prompt user to download an update manually (#2261) --- electron/handlers/update.ts | 5 +- .../BottomBar/UpdateFailedModal/index.tsx | 69 +++++++++++++++++++ web/containers/Layout/BottomBar/index.tsx | 2 + .../Providers/AppUpdateListener.tsx | 11 ++- web/containers/Providers/Jotai.tsx | 1 + 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts index 0d8cc4cc0..c8e28e580 100644 --- a/electron/handlers/update.ts +++ b/electron/handlers/update.ts @@ -3,6 +3,8 @@ import { WindowManager } from './../managers/window' import { autoUpdater } from 'electron-updater' import { AppEvent } from '@janhq/core' +export let waitingToInstallVersion: string | undefined = undefined + export function handleAppUpdates() { /* Should not check for update during development */ if (!app.isPackaged) { @@ -29,6 +31,7 @@ export function handleAppUpdates() { buttons: ['Restart', 'Later'], }) if (action.response === 0) { + waitingToInstallVersion = _info?.version autoUpdater.quitAndInstall() } }) @@ -37,7 +40,7 @@ export function handleAppUpdates() { autoUpdater.on('error', (info: any) => { WindowManager.instance.currentWindow?.webContents.send( AppEvent.onAppUpdateDownloadError, - info + { failedToInstallVersion: waitingToInstallVersion, info } ) }) diff --git a/web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx b/web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx new file mode 100644 index 000000000..f82fc0ee2 --- /dev/null +++ b/web/containers/Layout/BottomBar/UpdateFailedModal/index.tsx @@ -0,0 +1,69 @@ +import React from 'react' + +import { + Modal, + ModalPortal, + ModalContent, + ModalHeader, + ModalTitle, + ModalFooter, + ModalClose, + Button, +} from '@janhq/uikit' +import { Share2Icon } from '@radix-ui/react-icons' +import { useAtom } from 'jotai' + +import { updateVersionError } from '@/containers/Providers/Jotai' + +const UpdatedFailedModal = () => { + const [error, setError] = useAtom(updateVersionError) + + return ( + setError(undefined)}> + + + + Unable to Install Update + +

+ An error occurred while installing Jan{' '} + {error}. We + appreciate your help with{' '} + + manual downloading and installation. + +

+ +
+ setError(undefined)}> + + + { + window.open('https://github.com/janhq/jan#download', '_blank') + setError(undefined) + }} + > + + +
+
+
+
+ ) +} + +export default UpdatedFailedModal diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 66c089744..2373ac3d4 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -17,6 +17,7 @@ import { appDownloadProgress } from '@/containers/Providers/Jotai' import ImportingModelState from './ImportingModelState' import SystemMonitor from './SystemMonitor' +import UpdatedFailedModal from './UpdateFailedModal' const menuLinks = [ { @@ -44,6 +45,7 @@ const BottomBar = () => { +
diff --git a/web/containers/Providers/AppUpdateListener.tsx b/web/containers/Providers/AppUpdateListener.tsx index dceb4df13..542886ee5 100644 --- a/web/containers/Providers/AppUpdateListener.tsx +++ b/web/containers/Providers/AppUpdateListener.tsx @@ -3,10 +3,11 @@ import { Fragment, PropsWithChildren, useEffect } from 'react' import { useSetAtom } from 'jotai' -import { appDownloadProgress } from './Jotai' +import { appDownloadProgress, updateVersionError } from './Jotai' const AppUpdateListener = ({ children }: PropsWithChildren) => { const setProgress = useSetAtom(appDownloadProgress) + const setUpdateVersionError = useSetAtom(updateVersionError) useEffect(() => { if (window && window.electronAPI) { @@ -18,9 +19,13 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => { ) window.electronAPI.onAppUpdateDownloadError( - (_event: string, callback: any) => { - console.error('Download error', callback) + (_event: string, error: any) => { + console.error('Download error: ', error) setProgress(-1) + + // Can not install update + // Prompt user to download the update manually + setUpdateVersionError(error.failedToInstallVersion) } ) diff --git a/web/containers/Providers/Jotai.tsx b/web/containers/Providers/Jotai.tsx index 5907ac746..c43786c89 100644 --- a/web/containers/Providers/Jotai.tsx +++ b/web/containers/Providers/Jotai.tsx @@ -12,6 +12,7 @@ export const editPromptAtom = atom('') export const currentPromptAtom = atom('') export const fileUploadAtom = atom([]) export const appDownloadProgress = atom(-1) +export const updateVersionError = atom(undefined) export const searchAtom = atom('') export default function JotaiWrapper({ children }: Props) { From 04077ac493d8228dbb7614db982abe02225f4bbb Mon Sep 17 00:00:00 2001 From: Service Account Date: Thu, 7 Mar 2024 04:48:40 +0000 Subject: [PATCH 02/36] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 748470924..bc885a39e 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 5abe2e1c24d75e5a31754c1f9f2cf73263ec7f0a Mon Sep 17 00:00:00 2001 From: NamH Date: Thu, 7 Mar 2024 12:16:18 +0700 Subject: [PATCH 03/36] chore: temporary remove convert model (#2266) Signed-off-by: James Co-authored-by: James --- web/screens/ExploreModels/index.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/web/screens/ExploreModels/index.tsx b/web/screens/ExploreModels/index.tsx index 1ad0b00ae..484e62b0e 100644 --- a/web/screens/ExploreModels/index.tsx +++ b/web/screens/ExploreModels/index.tsx @@ -20,7 +20,6 @@ import { setImportModelStageAtom } from '@/hooks/useImportModel' import ExploreModelList from './ExploreModelList' import { HuggingFaceModal } from './HuggingFaceModal' -import { experimentalFeatureEnabledAtom } from '@/helpers/atoms/AppConfig.atom' import { configuredModelsAtom, downloadedModelsAtom, @@ -37,8 +36,6 @@ const ExploreModelsScreen = () => { const [showHuggingFaceModal, setShowHuggingFaceModal] = useState(false) const setImportModelStage = useSetAtom(setImportModelStageAtom) - const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom) - const filteredModels = configuredModels.filter((x) => { if (sortSelected === 'Downloaded') { return ( @@ -59,10 +56,6 @@ const ExploreModelsScreen = () => { setImportModelStage('SELECTING_MODEL') }, [setImportModelStage]) - const onHuggingFaceConverterClick = () => { - setShowHuggingFaceModal(true) - } - return (
{ Import Model
- {experimentalFeature && ( + {/* {experimentalFeature && (

{ Convert from Hugging Face

- )} + )} */}
From 3ef660b7fe2a044c25d4da9ac4c36a1aff7e53b0 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:25:30 +0700 Subject: [PATCH 04/36] Add modify notary team in CI (#2265) Co-authored-by: Hien To --- .github/workflows/template-build-macos-arm64.yml | 7 +++++++ .github/workflows/template-build-macos-x64.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/template-build-macos-arm64.yml b/.github/workflows/template-build-macos-arm64.yml index 54355d55c..2ef40b7c0 100644 --- a/.github/workflows/template-build-macos-arm64.yml +++ b/.github/workflows/template-build-macos-arm64.yml @@ -78,6 +78,10 @@ jobs: jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json + + jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + cat electron/package.json - name: Update app version base on tag @@ -91,6 +95,9 @@ jobs: mv /tmp/package.json electron/package.json jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json mv /tmp/package.json web/package.json + jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + cat electron/package.json env: VERSION_TAG: ${{ inputs.new_version }} diff --git a/.github/workflows/template-build-macos-x64.yml b/.github/workflows/template-build-macos-x64.yml index e313c2947..12aad37bd 100644 --- a/.github/workflows/template-build-macos-x64.yml +++ b/.github/workflows/template-build-macos-x64.yml @@ -72,6 +72,10 @@ jobs: jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json + + jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + cat electron/package.json - name: Update app version base on tag @@ -85,6 +89,9 @@ jobs: mv /tmp/package.json electron/package.json jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json mv /tmp/package.json web/package.json + jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + cat electron/package.json env: VERSION_TAG: ${{ inputs.new_version }} From e70773da918a96d6dad3893b239b47e921b16ebf Mon Sep 17 00:00:00 2001 From: Service Account Date: Thu, 7 Mar 2024 06:29:36 +0000 Subject: [PATCH 05/36] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bc885a39e..8a4c03098 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 61da85234355bb42c220ec0ad000c1589fbde5eb Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:12:44 +0900 Subject: [PATCH 06/36] feat: changeEditUrl --- docs/docusaurus.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index cd6078327..79d675c7a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -123,7 +123,7 @@ const config = { docs: { routeBasePath: "/", sidebarPath: require.resolve("./sidebars.js"), - editUrl: "https://github.com/janhq/jan/tree/main/docs", + editUrl: "https://github.com/janhq/jan/tree/dev/docs", showLastUpdateAuthor: true, showLastUpdateTime: true, }, From f08eacb0779d4d514500d96c36606c7ecec13e69 Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Thu, 7 Mar 2024 18:24:25 +0700 Subject: [PATCH 07/36] docs: Add a highlight category for each Menu groups. --- docs/sidebars.js | 74 +++++++++++++++++++++++++++++++++++++++-- docs/src/css/custom.css | 15 +++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index fb14f0abe..dea87cd9e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -150,10 +150,80 @@ const sidebars = { ], }, ], + // guidesSidebar: [ + // { + // type: "autogenerated", + // dirName: "guides", + // }, + // ], guidesSidebar: [ { - type: "autogenerated", - dirName: "guides", + type: "category", + label: "Get Started", + collapsible: false, + className: "head_Menu", + items: [ + "guides/quickstart", + "guides/install", + "guides/start-server", + "guides/models-list" + ] + }, + { + type: "category", + label: "Guides", + collapsible: false, + className: "head_Menu", + items: [ + "guides/best-practices", + "guides/thread", + "guides/faq", + "guides/hardware-requiement", + ] + }, + { + type: "category", + label: "Advanced Features", + collapsible: false, + className: "head_Menu", + items: [ + "guides/models/README", + "guides/models/customize-engine", + "guides/models/import-models", + "guides/models/integrate-remote", + "guides/extensions/README", + "guides/extensions/import-ext", + "guides/extensions/setup-ext", + "guides/integration/README", + "guides/integration/azure", + "guides/integration/discord", + "guides/integration/groq", + "guides/integration/lmstudio", + "guides/integration/mistral", + "guides/integration/ollama", + "guides/integration/openinterpreter", + "guides/integration/openrouter", + "guides/integration/raycast", + "guides/integration/vscode", + ] + }, + { + type: "category", + label: "Troubleshooting", + collapsible: false, + className: "head_Menu", + items: [ + "guides/common-error/README", + "guides/common-error/broken-build", + "guides/common-error/not-using-gpu", + "guides/error-codes/README", + "guides/error-codes/how-to-get-error-logs", + "guides/error-codes/permission-denied", + "guides/error-codes/something-amiss", + "guides/error-codes/undefined-issue", + "guides/error-codes/unexpected-token", + "guides/faq" + ] }, ], developerSidebar: [ diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 9ed8fd0d8..5307bec2e 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -6,3 +6,18 @@ [data-theme="dark"] .DocSearch { --docsearch-hit-active-color: #090a11; /* Keep the color unchanged */ } + +/* sidebarStyles.css */ +.head_Menu div { + font-weight: bold; + background-color: whitesmoke; + margin-left: 0.7rem; + font-size: larger; +} + +.head_Menu li { + /* Custom styles for the sidebar items */ + font-weight: normal; + background-color: white; + margin-bottom: 5px; /* Adjust margin as needed */ +} \ No newline at end of file From 751c273cde8a5f67a82350e7a79677a9ea052bdd Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Thu, 7 Mar 2024 18:24:56 +0700 Subject: [PATCH 08/36] docs: Add content Groq integration --- docs/docs/guides/integration/groq.mdx | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 docs/docs/guides/integration/groq.mdx diff --git a/docs/docs/guides/integration/groq.mdx b/docs/docs/guides/integration/groq.mdx new file mode 100644 index 000000000..a3a12dbc5 --- /dev/null +++ b/docs/docs/guides/integration/groq.mdx @@ -0,0 +1,84 @@ +--- +title: Groq +sidebar_position: 10 +slug: /guides/integration/groq +description: Learn how to integrate Groq API with Jan for enhanced functionality. +keywords: + [ + Groq API, + Jan, + Jan AI, + ChatGPT alternative, + conversational AI, + large language model, + integration, + Groq integration, + API integration + ] +--- + +## How to Integrate Mistral AI with Jan + +This guide provides step-by-step instructions on integrating the Groq API with Jan, enabling users to leverage Groq's capabilities within Jan's conversational interface. + +Before proceeding, ensure you have the following: +- Access to the Jan platform +- Groq API credentials + +## Integration Steps + +### Step 1: Obtain Groq API Credentials + +If you haven't already, sign up for the Groq API and obtain your API credentials. +Obtain Groq API keys from your [Groq Console](https://console.groq.com/keys). + +### Step 2: Configure Jan Settings + +1. Insert the Groq AI API key into `~/jan/engines/openai.json`. + +```json title="~/jan/engines/openai.json" +{ + "full_url": "https://api.groq.com/openai/v1", + "api_key": "" +} +``` + +### Step 3: Enable Groq Integration + +To set up the configuration for Groq in Jan, follow these steps: + +1. Navigate to `~/jan/models`. +2. Create a folder named `groq`. +3. Inside the groq folder, create a model.json file with the specified settings: +```json title="~/jan/models/groq/model.json +{ + "id": "groq", + "object": "model", + "name": "Groq Integration", + "version": "1.0", + "description": "Integration with Groq API for enhanced functionality.", + "format": "api", + "sources": [], + "settings": {}, + "parameters": {}, + "metadata": { + "author": "Your Name", + "tags": ["Groq Integration"] + }, + "engine": "groq" +} +``` + +### Step 4: Start the Model + +1. Restart Jan and navigate to the **Hub**. +2. Locate your model and click the **Use** button. + +## Troubleshooting + +If you encounter any issues during the integration process or while using Groq with Jan, consider the following troubleshooting steps: + +- Double-check your API credentials and ensure they are correctly entered. +- Verify that the Groq integration is enabled within Jan's settings. +- Check for any error messages or logs that may provide insight into the issue. +- Reach out to Groq API support for assistance if needed. \ No newline at end of file From 7d988cc2d9c048049292457910fa16f01d32d9a9 Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Thu, 7 Mar 2024 18:25:08 +0700 Subject: [PATCH 09/36] docs: Add FAQs content --- docs/docs/guides/faq.mdx | 80 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/docs/guides/faq.mdx b/docs/docs/guides/faq.mdx index 2a79e9fde..d25b0f980 100644 --- a/docs/docs/guides/faq.mdx +++ b/docs/docs/guides/faq.mdx @@ -17,6 +17,80 @@ keywords: ] --- -:::caution -This is currently under development. -::: \ No newline at end of file +## General Issues + +- **Why can't I download models like Pandora 11B Q4 and Solar Instruct 10.7B Q4?** + - These models might have been removed or taken down. Please check the [Pre-configured Models](models-list.mdx) for the latest updates on model availability. + +- **Why does Jan display "Apologies, something's amiss" when I try to run it?** + - This issue may arise if you're using an older Intel chip that does not fully support AVX instructions required for running AI models. Upgrading your hardware may resolve this issue. + +- **How can I use Jan in Russia?** + - To use Jan in Russia, a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) is recommended to bypass any regional restrictions that might be in place. + +- **I'm experiencing an error on startup from Nitro. What should I do?** + - If you encounter errors with Nitro, try switching the path to use the Nitro executable for the version 12-0. This adjustment can help resolve path-related issues. + +## Download and Installation Issues + +- **What does "Error occurred: Unexpected token" mean?** + - This error usually indicates a problem with your internet connection or that your access to certain resources is being blocked. Using a VPN or [HTTPS - Proxy](./advanced-settings/http-proxy.mdx) can help avoid these issues by providing a secure and unrestricted internet connection. + +- **Why aren't my downloads working?** + - If you're having trouble downloading directly through Jan, you might want to download the model separately and then import it into Jan. Detailed instructions are available on [here](install.mdx). + +- **Jan AI doesn't open on my Mac with an Intel processor. What can I do?** + - Granting the `.npm` folder permission for the user can resolve issues related to permissions on macOS, especially for users with Intel processors. + +- **What should I do if the model download freezes?** + - If a model download freezes, consider importing the models manually. You can find more detailed guidance on how to do this at [Manual Import](./models/import-models.mdx) article. + +- **I received a message that the model GPT4 does not exist or I do not have access. What should I do?** + - This message typically means you need to top up your credit with OpenAI or check your access permissions for the model. + +- **I can't download models from "Explore the Hub." What's the solution?** + - Uninstalling Jan, clearing the cache, and reinstalling it following the guide provided [here](install.mdx) may help. Also, consider downloading the `.gguf` model via a browser as an alternative approach. + +## Technical Issues and Solutions + +- **How can I download models with a socks5 proxy or import a local model file?** + - Nightly builds of Jan offer support for downloading models with socks5 proxies or importing local model files. + +- **My device shows no GPU usage and lacks a Settings folder. What should I do?** + - Using the nightly builds of Jan can address issues related to GPU usage and the absence of a Settings folder, as these builds contain the latest fixes and features. + +- **Why does Jan display a toast message saying a model is loaded when it is not actually loaded?** + - This issue can be resolved by downloading the `.gguf` file from Hugging Face and replacing it in the model folder. This ensures the correct model is loaded. + +- **How to enable CORS when running Nitro?** + - By default, CORS (Cross-Origin Resource Sharing) is disabled when running Nitro. Enabling CORS can be necessary for certain operations and integrations. Check the official documentation for instructions on how to enable CORS if your workflow requires it. + +## Compatibility and Support + +- **How to use GPU AMD for Jan?** + - Jan now supports AMD GPUs through Vulkan. This enhancement allows users with AMD graphics cards to leverage GPU acceleration, improving performance for AI model computations. + +- **Is Jan available for Android or iOS?** + - Jan is primarily focused on the Desktop app and does not currently offer mobile apps for Android or iOS. The development team is concentrating on enhancing the desktop experience. + +## Development and Features + +- **Can I use local files from my computer with Jan?** + - The feature to use local files is currently in development. For more information, please our offical [Discord](https://discord.com/invite/FTk2MvZwJH) for the latest update. + +- **Does Jan support Safetensors?** + - At the moment, Jan only supports GGUF. However, there are plans to support `.safetensor` files in the future. + +- **I hope to customize the installation path of each model. Is that possible?** + - Yes you can customize the installation path. Please see [here](https://github.com/janhq/jan/issues/1010) for more information. + +## Troubleshooting + +- **What should I do if there's high CPU usage while Jan is idle?** + - If you notice high CPU usage while Jan is idle, consider using the nightly builds of Jan + +- **What does the error "Failed to fetch" mean, and how can I fix it?** + - The "Failed to fetch" error typically occurs due to network issues or restrictions. Using the nightly builds of Jan may help overcome these issues by providing updated fixes and features. + +- **What should I do if "Failed to fetch" occurs using MacBook Pro with Intel HD Graphics 4000 1536 MB?** + - Ensure that the model size is less than 90% of your available VRAM and that the VRAM is accessible to the app. Managing the resources effectively can help mitigate this issue. From ca90b008c10d3ce3b78befe1aeeb662df7e1b1f8 Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Thu, 7 Mar 2024 18:50:30 +0700 Subject: [PATCH 10/36] docs: fix the sidebar issues --- docs/sidebars.js | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index dea87cd9e..61bd85439 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -178,7 +178,6 @@ const sidebars = { "guides/best-practices", "guides/thread", "guides/faq", - "guides/hardware-requiement", ] }, { From d07c3deac4f19447c404024604adc229d93d8d44 Mon Sep 17 00:00:00 2001 From: eckartal <159995642+eckartal@users.noreply.github.com> Date: Thu, 7 Mar 2024 23:27:31 +0300 Subject: [PATCH 11/36] Update about.md Updated how we tell the Jan's story Changed the sections and their hierarchies Added values/principles Updated the table Added "Join the team" and "how we work" sections --- docs/docs/about/about.md | 84 +++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/docs/docs/about/about.md b/docs/docs/about/about.md index 3066f3003..3f3f01f28 100644 --- a/docs/docs/about/about.md +++ b/docs/docs/about/about.md @@ -1,7 +1,7 @@ --- title: About Jan slug: /about -description: Jan is a productivity tool to customize AI to your needs and workflows. +description: Jan is a desktop application that turns computers into thinking machines. keywords: [ Jan AI, @@ -15,51 +15,75 @@ keywords: ] --- -Jan is a [open-source](https://en.wikipedia.org/wiki/Open_source), [local-first](https://www.inkandswitch.com/local-first/) tool to [create, customize and use AI](https://www.gatesnotes.com/AI-agents) for everyday tasks. +Jan turns computers into a thinking machine to change how you use computers. +Jan is created and maintained by Jan Labs, a robotics company. -You can: +With Jan, you can: -- Run locally using [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/) -- Fine-tune AI with specific knowledge -- Search the web and other databases -- Connect AI to your everyday tools and (with your permission) do work on your behalf +- Run [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) locally or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/). +- Fine-tune AI with specific knowledge. +- Supercharge your productivity by leveraging AI. +- Search the web and databases. +- Integrate AI with everyday tools to work on your behalf (with permission). +- Customize and add features with Extensions. -Longer-term, Jan is building a cognitive framework for future robots. We envision a world where we have personal or company robots that we continually improve and customize, growing together with us. +:::tip + +Jan aims for long-term human-robot collaboration, envisioning AI as a harmonious extension of human capabilities. Our goal is to build customizable robots that we continually improve and customize, growing together. + +::: ![Human repairing a Droid](/img/star-wars-droids.png) -## Why do we exist +## Jan’s Principles -At Jan, our mission is to advance human-machine collaboration. We achieve this through delivering the best open-source, local-first tools to allow users to run, customize and tinker with AI. +- **Ownership**: Jan is committed to developing a product that fully belong to users. You're the true owner, free from data tracking and storage by us. +- **Privacy**: Jan works locally by default, allowing use without an internet connection. Your data stays on your device in a universal format, giving you complete privacy control. +- **100% User Supported**: Every user can access, develop, and customize Jan codebases to suit their needs. +- **Rejecting Dark Patterns**: We never use tricks to extract more money or lock you into an ecosystem. -## What's different about it? +## Why do we exist? -| | Status Quo | Jan | -| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| Ownership | AI Monopolies owned by Big Tech | AI that you own and control | -| Openness? | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) | -| Your role | Consume | Create, Tinker and Customize | -| Approach | Cloud | [Local-first](https://www.inkandswitch.com/local-first/), running 100% on your devices | -| Data | Data stored on their servers | Data stored in your local filesystem in open, non-proprietary file formats | -| Privacy | 😂 | Runs 100% on your own machine, predictably, privately and offline | -| Transparency | "Black Box" | Runs predictability with code available to tinker and customize | -| What happens if there's an outage or goes out of business? | Your life's work held hostage in the cloud in proprietary data formats[^1] | Continues to run 100% on your computer, your data is safe in your local folder | -| Driving Philosophy | Monetize your users | [Privacy as a human right](https://en.wikipedia.org/wiki/Right_to_privacy) and the [Right to Repair](https://www.repair.org/) | +> *"I do not fear computers. I fear the lack of them." - Isaac Asimov* -## How do I get it? +Jan was founded on the belief that AI should coexist with humans, not replace them. Our mission is to democratize AI access, ensuring everyone can easily utilize it with full ownership and control over their data, free from privacy concerns. + +### What are the things Jan committed on? + +We are committed to creating open, local-first products that extend individual freedom, rejecting dark patterns and ecosystem lock-ins, and embracing an open-source ethos. + +#### What's different about it? + +| | Status Quo | Jan | +|----------------|---------------------|-----------------------------| +| **Ownership** | Owned by Big Tech | Fully owned by you | +| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) | +| **Your Role** | Consumer | Creator | +| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) | +| **Data Handling** | Stored on external servers | Stored locally, openly accessible | +| **Privacy** | Questionable | Private and offline | +| **Transparency** | Opaque "Black Box" | Open-source and customizable | +| **Outage Resilience** | Potential data hostage | Continues to work on your device | +| **Philosophy** | User monetization | Empowerment with the right to repair | + +## How we work + +Jan is an open-source product with transparent development and future features. Users have the right to modify and customize Jan. We are committed to building an open-source AI ecosystem. + +Jan is building in public using GitHub, where anyone is welcome to join. Key resources include Jan's [Kanban](https://github.com/orgs/janhq/projects/5/views/7) and Jan's [Roadmap](https://github.com/orgs/janhq/projects/5/views/29). + +Jan has a fully-remote team, primarily based in the APAC timezone, and we use Discord and GitHub for collaboration. Our community is central to our operations, and we embrace asynchronous work. We hold meetings only for synchronization and vision sharing, using [Excalidraw](https://excalidraw.com/) or [Miro](https://miro.com/) for visualization and sharing notes on Discord for alignment. We also use [HackMD](https://hackmd.io/) to document our ideas and build a Jan library. + +## How to get it? You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan). -You can read the [User Guide](/docs/user-guide) if you need some help to get started. - ## What license is the code under? Jan is licensed under the [AGPLv3 License](https://github.com/janhq/jan/blob/main/LICENSE). We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2]. -We also have a [Contributor Program](/docs/team/contributor-program) to provide ownership and upside to contributors who have made significant contributions to the project. - ## What was it built with? [Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]). @@ -73,11 +97,9 @@ We follow [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/ Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale. -:::tip +## Join the team -**At its core, Jan is a software development kit to build and run copilots on personal devices**. The Desktop Client many folks use is, rather, a specific set of extensions packaged by default. We're excited to see what developers do with the SDK (once its in better shape). - -::: +Join us on this journey at Jan Labs, where we embrace open-source collaboration and transparency. Together, let's shape a future where Jan becomes an essential companion in the open-source community. Explore [careers](https://janai.bamboohr.com/careers) with us. ## Contact From 11d8c4783e710ae228d1bf3a14ddeac794acfa1f Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:04:17 +0900 Subject: [PATCH 12/36] docs: correct typo and format style --- docs/docs/about/about.md | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/docs/about/about.md b/docs/docs/about/about.md index 3f3f01f28..0f1651c1a 100644 --- a/docs/docs/about/about.md +++ b/docs/docs/about/about.md @@ -12,10 +12,13 @@ keywords: conversational AI, no-subscription fee, large language model, + about Jan, + desktop application, + thinking machine, ] --- -Jan turns computers into a thinking machine to change how you use computers. +Jan turns computers into a thinking machine to change how you use computers. Jan is created and maintained by Jan Labs, a robotics company. With Jan, you can: @@ -35,16 +38,16 @@ Jan aims for long-term human-robot collaboration, envisioning AI as a harmonious ![Human repairing a Droid](/img/star-wars-droids.png) -## Jan’s Principles +## Jan’s principles -- **Ownership**: Jan is committed to developing a product that fully belong to users. You're the true owner, free from data tracking and storage by us. +- **Ownership**: Jan is committed to developing a product that fully belongs to users. You're the true owner, free from data tracking and storage by us. - **Privacy**: Jan works locally by default, allowing use without an internet connection. Your data stays on your device in a universal format, giving you complete privacy control. -- **100% User Supported**: Every user can access, develop, and customize Jan codebases to suit their needs. +- **100% User Supported**: Every user can access, develop, and customize Jan's codebases to suit their needs. - **Rejecting Dark Patterns**: We never use tricks to extract more money or lock you into an ecosystem. ## Why do we exist? -> *"I do not fear computers. I fear the lack of them." - Isaac Asimov* +> _"I do not fear computers. I fear the lack of them." - Isaac Asimov_ Jan was founded on the belief that AI should coexist with humans, not replace them. Our mission is to democratize AI access, ensuring everyone can easily utilize it with full ownership and control over their data, free from privacy concerns. @@ -54,17 +57,17 @@ We are committed to creating open, local-first products that extend individual f #### What's different about it? -| | Status Quo | Jan | -|----------------|---------------------|-----------------------------| -| **Ownership** | Owned by Big Tech | Fully owned by you | -| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) | -| **Your Role** | Consumer | Creator | -| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) | -| **Data Handling** | Stored on external servers | Stored locally, openly accessible | -| **Privacy** | Questionable | Private and offline | -| **Transparency** | Opaque "Black Box" | Open-source and customizable | -| **Outage Resilience** | Potential data hostage | Continues to work on your device | -| **Philosophy** | User monetization | Empowerment with the right to repair | +| | Status Quo | Jan | +| --------------------- | -------------------------- | ---------------------------------------------------------------------- | +| **Ownership** | Owned by Big Tech | Fully owned by you | +| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) | +| **Your Role** | Consumer | Creator | +| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) | +| **Data Handling** | Stored on external servers | Stored locally, openly accessible | +| **Privacy** | Questionable | Private and offline | +| **Transparency** | Opaque "Black Box" | Open-source and customizable | +| **Outage Resilience** | Potential data hostage | Continues to work on your device | +| **Philosophy** | User monetization | Empowerment with the right to repair | ## How we work @@ -76,7 +79,7 @@ Jan has a fully-remote team, primarily based in the APAC timezone, and we use Di ## How to get it? -You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan). +You can install and start using Jan in less than 5 minutes, from [Jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan). ## What license is the code under? From f36d740b1eb6e1b5caadd1fc115a0da3044b0e5b Mon Sep 17 00:00:00 2001 From: NamH Date: Fri, 8 Mar 2024 10:01:37 +0700 Subject: [PATCH 13/36] feat: add quick ask (#2197) * feat: add quick ask Signed-off-by: James --------- Signed-off-by: James Co-authored-by: James Co-authored-by: Louis --- .gitignore | 1 + core/package.json | 11 +- core/src/api/index.ts | 11 ++ .../node/api/restful/helper/startStopModel.ts | 6 +- core/src/types/miscellaneous/appUpdate.ts | 7 + core/src/types/miscellaneous/index.ts | 1 + core/src/types/model/modelEntity.ts | 4 + electron/handlers/common.ts | 15 +- electron/handlers/native.ts | 41 +++++- electron/handlers/update.ts | 26 ++-- electron/icons/icon-tray.png | Bin 0 -> 1352 bytes electron/icons/icon-tray@2x.png | Bin 0 -> 2269 bytes electron/main.ts | 103 +++++++++----- electron/managers/mainWindowConfig.ts | 16 +++ electron/managers/quickAskWindowConfig.ts | 13 ++ electron/managers/window.ts | 132 +++++++++++++++--- electron/package.json | 18 ++- electron/utils/clean.ts | 4 +- electron/utils/selectedText.ts | 39 ++++++ extensions/assistant-extension/package.json | 2 +- .../inference-nitro-extension/package.json | 2 +- server/package.json | 4 +- uikit/package.json | 2 +- web/app/search/SelectedText.tsx | 47 +++++++ web/app/search/UserInput.tsx | 86 ++++++++++++ web/app/search/page.tsx | 13 ++ .../Providers/AppUpdateListener.tsx | 12 +- .../Providers/ClipboardListener.tsx | 17 +++ web/containers/Providers/DataLoader.tsx | 2 +- web/containers/Providers/EventHandler.tsx | 13 +- web/containers/Providers/EventListener.tsx | 12 +- web/containers/Providers/Jotai.tsx | 2 + web/containers/Providers/KeyListener.tsx | 4 + web/containers/Providers/QuickAskListener.tsx | 39 ++++++ web/helpers/atoms/Assistant.atom.ts | 2 +- web/hooks/useActiveModel.ts | 10 +- web/hooks/useSendChatMessage.ts | 55 ++++---- web/package.json | 5 +- .../ImportingModelItem.tsx | 2 +- 39 files changed, 631 insertions(+), 148 deletions(-) create mode 100644 core/src/types/miscellaneous/appUpdate.ts create mode 100644 electron/icons/icon-tray.png create mode 100644 electron/icons/icon-tray@2x.png create mode 100644 electron/managers/mainWindowConfig.ts create mode 100644 electron/managers/quickAskWindowConfig.ts create mode 100644 electron/utils/selectedText.ts create mode 100644 web/app/search/SelectedText.tsx create mode 100644 web/app/search/UserInput.tsx create mode 100644 web/app/search/page.tsx create mode 100644 web/containers/Providers/ClipboardListener.tsx create mode 100644 web/containers/Providers/QuickAskListener.tsx diff --git a/.gitignore b/.gitignore index 75518bf5a..ae0691605 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ electron/renderer electron/models electron/docs electron/engines +electron/playwright-report server/pre-install package-lock.json diff --git a/core/package.json b/core/package.json index c3abe2d56..2bf3e1735 100644 --- a/core/package.json +++ b/core/package.json @@ -45,19 +45,20 @@ "start": "rollup -c rollup.config.ts -w" }, "devDependencies": { - "jest": "^25.4.0", - "@types/jest": "^29.5.11", + "jest": "^29.7.0", + "@types/jest": "^29.5.12", "@types/node": "^12.0.2", - "eslint-plugin-jest": "^23.8.2", + "eslint-plugin-jest": "^27.9.0", + "eslint": "8.57.0", "rollup": "^2.38.5", "rollup-plugin-commonjs": "^9.1.8", "rollup-plugin-json": "^3.1.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript2": "^0.36.0", - "ts-jest": "^26.1.1", + "ts-jest": "^29.1.2", "tslib": "^2.6.2", - "typescript": "^5.2.2", + "typescript": "^5.3.3", "rimraf": "^3.0.2" } } diff --git a/core/src/api/index.ts b/core/src/api/index.ts index 7fb8eeb38..e62b49087 100644 --- a/core/src/api/index.ts +++ b/core/src/api/index.ts @@ -9,6 +9,14 @@ export enum NativeRoute { selectDirectory = 'selectDirectory', selectModelFiles = 'selectModelFiles', relaunch = 'relaunch', + + hideQuickAskWindow = 'hideQuickAskWindow', + sendQuickAskInput = 'sendQuickAskInput', + + hideMainWindow = 'hideMainWindow', + showMainWindow = 'showMainWindow', + + quickAskSizeUpdated = 'quickAskSizeUpdated', } /** @@ -31,6 +39,9 @@ export enum AppEvent { onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate', onAppUpdateDownloadError = 'onAppUpdateDownloadError', onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess', + + onUserSubmitQuickAsk = 'onUserSubmitQuickAsk', + onSelectedText = 'onSelectedText', } export enum DownloadRoute { diff --git a/core/src/node/api/restful/helper/startStopModel.ts b/core/src/node/api/restful/helper/startStopModel.ts index 0e6972b0b..4627b4120 100644 --- a/core/src/node/api/restful/helper/startStopModel.ts +++ b/core/src/node/api/restful/helper/startStopModel.ts @@ -41,7 +41,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr const modelFolderFullPath = join(janDataFolderPath, 'models', modelId) if (!fs.existsSync(modelFolderFullPath)) { - throw `Model not found: ${modelId}` + throw new Error(`Model not found: ${modelId}`) } const files: string[] = fs.readdirSync(modelFolderFullPath) @@ -53,7 +53,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr const modelMetadata: Model = JSON.parse(fs.readFileSync(modelMetadataPath, 'utf-8')) if (!ggufBinFile) { - throw 'No GGUF model file found' + throw new Error('No GGUF model file found') } const modelBinaryPath = join(modelFolderFullPath, ggufBinFile) @@ -76,7 +76,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr const promptTemplate = modelMetadata.settings.prompt_template const prompt = promptTemplateConverter(promptTemplate) if (prompt?.error) { - return Promise.reject(prompt.error) + throw new Error(prompt.error) } nitroModelSettings.system_prompt = prompt.system_prompt nitroModelSettings.user_prompt = prompt.user_prompt diff --git a/core/src/types/miscellaneous/appUpdate.ts b/core/src/types/miscellaneous/appUpdate.ts new file mode 100644 index 000000000..ed135e3bd --- /dev/null +++ b/core/src/types/miscellaneous/appUpdate.ts @@ -0,0 +1,7 @@ +export type AppUpdateInfo = { + total: number + delta: number + transferred: number + percent: number + bytesPerSecond: number +} diff --git a/core/src/types/miscellaneous/index.ts b/core/src/types/miscellaneous/index.ts index 02c973323..e9c205a73 100644 --- a/core/src/types/miscellaneous/index.ts +++ b/core/src/types/miscellaneous/index.ts @@ -1,2 +1,3 @@ export * from './systemResourceInfo' export * from './promptTemplate' +export * from './appUpdate' diff --git a/core/src/types/model/modelEntity.ts b/core/src/types/model/modelEntity.ts index 3cbe799e2..11d3e0526 100644 --- a/core/src/types/model/modelEntity.ts +++ b/core/src/types/model/modelEntity.ts @@ -138,3 +138,7 @@ export type ModelRuntimeParams = { presence_penalty?: number engine?: string } + +export type ModelInitFailed = Model & { + error: Error +} diff --git a/electron/handlers/common.ts b/electron/handlers/common.ts index 5a54a92bd..a2a1bd2f7 100644 --- a/electron/handlers/common.ts +++ b/electron/handlers/common.ts @@ -1,25 +1,20 @@ import { Handler, RequestHandler } from '@janhq/core/node' import { ipcMain } from 'electron' -import { WindowManager } from '../managers/window' +import { windowManager } from '../managers/window' export function injectHandler() { const ipcWrapper: Handler = ( route: string, listener: (...args: any[]) => any - ) => { - return ipcMain.handle(route, async (event, ...args: any[]) => { + ) => + ipcMain.handle(route, async (_event, ...args: any[]) => { return listener(...args) }) - } const handler = new RequestHandler( ipcWrapper, - (channel: string, args: any) => { - return WindowManager.instance.currentWindow?.webContents.send( - channel, - args - ) - } + (channel: string, args: any) => + windowManager.mainWindow?.webContents.send(channel, args) ) handler.handle() } diff --git a/electron/handlers/native.ts b/electron/handlers/native.ts index 19a473e73..04e9b71af 100644 --- a/electron/handlers/native.ts +++ b/electron/handlers/native.ts @@ -1,13 +1,13 @@ import { app, ipcMain, dialog, shell } from 'electron' import { join } from 'path' -import { WindowManager } from '../managers/window' +import { windowManager } from '../managers/window' import { ModuleManager, getJanDataFolderPath, getJanExtensionsPath, init, } from '@janhq/core/node' -import { NativeRoute } from '@janhq/core' +import { AppEvent, NativeRoute } from '@janhq/core' export function handleAppIPCs() { /** @@ -62,12 +62,12 @@ export function handleAppIPCs() { // Path to install extension to extensionsPath: getJanExtensionsPath(), }) - WindowManager.instance.currentWindow?.reload() + windowManager.mainWindow?.reload() } }) ipcMain.handle(NativeRoute.selectDirectory, async () => { - const mainWindow = WindowManager.instance.currentWindow + const mainWindow = windowManager.mainWindow if (!mainWindow) { console.error('No main window found') return @@ -85,7 +85,7 @@ export function handleAppIPCs() { }) ipcMain.handle(NativeRoute.selectModelFiles, async () => { - const mainWindow = WindowManager.instance.currentWindow + const mainWindow = windowManager.mainWindow if (!mainWindow) { console.error('No main window found') return @@ -101,4 +101,35 @@ export function handleAppIPCs() { return filePaths }) + + ipcMain.handle( + NativeRoute.hideQuickAskWindow, + async (): Promise => windowManager.hideQuickAskWindow() + ) + + ipcMain.handle( + NativeRoute.sendQuickAskInput, + async (_event, input: string): Promise => { + windowManager.mainWindow?.webContents.send( + AppEvent.onUserSubmitQuickAsk, + input + ) + } + ) + + ipcMain.handle( + NativeRoute.hideMainWindow, + async (): Promise => windowManager.hideMainWindow() + ) + + ipcMain.handle( + NativeRoute.showMainWindow, + async (): Promise => windowManager.showMainWindow() + ) + + ipcMain.handle( + NativeRoute.quickAskSizeUpdated, + async (_event, heightOffset: number): Promise => + windowManager.expandQuickAskWindow(heightOffset) + ) } diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts index c8e28e580..3f52c401e 100644 --- a/electron/handlers/update.ts +++ b/electron/handlers/update.ts @@ -1,6 +1,11 @@ import { app, dialog } from 'electron' -import { WindowManager } from './../managers/window' -import { autoUpdater } from 'electron-updater' +import { windowManager } from './../managers/window' +import { + ProgressInfo, + UpdateDownloadedEvent, + UpdateInfo, + autoUpdater, +} from 'electron-updater' import { AppEvent } from '@janhq/core' export let waitingToInstallVersion: string | undefined = undefined @@ -11,7 +16,7 @@ export function handleAppUpdates() { return } /* New Update Available */ - autoUpdater.on('update-available', async (_info: any) => { + autoUpdater.on('update-available', async (_info: UpdateInfo) => { const action = await dialog.showMessageBox({ title: 'Update Available', message: 'Would you like to download and install it now?', @@ -21,8 +26,8 @@ export function handleAppUpdates() { }) /* App Update Completion Message */ - autoUpdater.on('update-downloaded', async (_info: any) => { - WindowManager.instance.currentWindow?.webContents.send( + autoUpdater.on('update-downloaded', async (_info: UpdateDownloadedEvent) => { + windowManager.mainWindow?.webContents.send( AppEvent.onAppUpdateDownloadSuccess, {} ) @@ -37,23 +42,24 @@ export function handleAppUpdates() { }) /* App Update Error */ - autoUpdater.on('error', (info: any) => { - WindowManager.instance.currentWindow?.webContents.send( + autoUpdater.on('error', (info: Error) => { + windowManager.mainWindow?.webContents.send( AppEvent.onAppUpdateDownloadError, { failedToInstallVersion: waitingToInstallVersion, info } ) }) /* App Update Progress */ - autoUpdater.on('download-progress', (progress: any) => { + autoUpdater.on('download-progress', (progress: ProgressInfo) => { console.debug('app update progress: ', progress.percent) - WindowManager.instance.currentWindow?.webContents.send( + windowManager.mainWindow?.webContents.send( AppEvent.onAppUpdateDownloadUpdate, { - percent: progress.percent, + ...progress, } ) }) + autoUpdater.autoDownload = false autoUpdater.autoInstallOnAppQuit = true if (process.env.CI !== 'e2e') { diff --git a/electron/icons/icon-tray.png b/electron/icons/icon-tray.png new file mode 100644 index 0000000000000000000000000000000000000000..ab356a9dc4318aabb43fb6e8d3b04486a0c95fc7 GIT binary patch literal 1352 zcmZ`%3ou+~6#myHgrd^LdZk`jgpg|!E4DHvc7vp=-ibl$u~%ynkJwewNf2~0`ie)J zv?C@;5*Z<0QSV)k6)YnjiK=R)GAR{Qi)C4RdoF!Ur|q5p{*QCM@0|0U|IhLD@zl~V z(m)7ld3$mEVbvl3Jye)&>|YLm#Vp#(pNG)p()~H)8>aG%X6NB~&iA7Jx^@TDDFTw5OZl#)d#cN|+5nI>=`9wcO7Rc`fk6z|ZHP^lDpVnYqRVWd zhF&Qe_|s*p?6rb)vIZJtUvHsH>MvJ`djIe~vX_gDf2-1fs>oStvr?^A)keky3^9a= z!GqvZ_A=I*^w(Pq$Ox^5tMz-qC~q~GtaQ06{#M?11PWI6Fu=I-KxeCGKRG|#zg(*& zYtR7-Sk{{Lz-_L}GTv zeUbFnUJe*n8)(y4|F)xI0*t_gZd8jTThfEDhXJ|fZ*5=S_7N<~K5K=<92&cLS7isI z=rUif(faXI1Q)v1Z2=vCB0>AM*BbPKC{PM*-0ER$bz71*LaGA^2cQ*-ZVL!Vwvix? z`I)N2b14=2`;brVKfjk8RW)o?bdsIfQ*ZN9PnbTJvc7r5yLiz^m23HNR@x;W0j5UP zf70^$!qB$SoApcpWZR_S!_e%Rg5; z{`g!@(+9Uduh*i|ODl`ovPui;KDt-fa;1z;b$;#Yn!o83o~G=ls$^w)7+i?+=<2kz zC@qPOdNxgarNCf4Kfr%<2 zrjMhL%OOs>D$HxpD~6DJM#zs8;_T=o92UgHm`Cif!}b^}5OZN;Og6@{!!S06xt(0~ z{Rbf~fgc-_`u_w+I&U5bHh*Oh#>U|SVPst5zd4RBJ4wQxns9()NOA%aVuUG?NjOSL hNpXmcKP!liOvD`$lHMs?bTtAH^5*(*?jMiH{2h=^c>w?b literal 0 HcmV?d00001 diff --git a/electron/icons/icon-tray@2x.png b/electron/icons/icon-tray@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a82c285f53bd7bce072322ef67f6e9ffd73aba28 GIT binary patch literal 2269 zcmZ{lc{CJm7ssEOVeFc+W=*9cG?pxbNMjkYG)=N=8f%8k5@Y#gCMIS7$&xgw5E7~E zk|ngFcRP zd&$a;Nf|pM(`m83wXk^fF?+t3Scj`-x-{+0GO)xHCrR(xE3Yl1prVmfmfC^HN<42T ztOl26UZ5o;CS8-YBZbmR?Mhuj84?DL5YM1*wU$kz-XHTmvGQ2|m@llQ=MC3VH|`qc zoh18CQ(C6SD1FZ1DLvoU=vf^#3h&1u_bi8IlD$dIp0L!DLOPH;un6cQ_omDC!ckwIiK3imp)U;xZ(|GJ z?1l+LFtORJUfy3lU!t?S2Zq@r0J)=zp~KPTy#3+i+`^lE0_1sC%M>Ra+Q1R%A&<&v z;Ao4h00LIRof0c1lTjAK%?}AVZNC)4Eta&FcAQg3N?8W~p#|u=`b>n_7%?N_XDNFt*=mm4KQ6{iJ>uT5>MAC;86vnMoJ&aKJgo>6ac{FgqJW+!j3h2tStj2WXS;C7HIKzbNUJXpJoM> zb4SYFp4eC3p`J<(mamr+W>zh&uxcku6%&n!HyMa@!wq$x%9k}+jrfSK;|u7bJ0TiN zKW=W#!w!xd>77mUeIY~>HO}|rW*m1D{2|)izNq!(8*a9;i^GY%5_@FH8F{F$vj=XT z)02uRd?87b67O0k=?&}e{cAns!UrA1`h4ninvR(4uRSUPmj$%?G0zil0h$2ja>2Xc zAx`r0y*YtNlNJ?^ZI7L5i|{TG_BUf?v)q1jvt(aK!H8-7ag}b8t5SR>2aD&yg#+JFsdx1&5Grtf&Q5?-7 zIaE?_skkK3NhZmYv5J6puT~Lb?o_iTm{}S?O&jsCA3aGk%{IiAB?E@8!7H1@`yLaQ z@u#-@#EY2Kjl9^BY4|^LC9L-J^7tlSpb{LWxh!VQc8aP?|D%c`{84z1HdB#G!x+*W*0#$x@1jYxK2^pt zg9n+|x&!!+Z}7J=b_I`i1(_3eIfPI%aSZt1)V5nSb5TtsLHl=wSJoC}5z}I7qFt+@ z%86o!VX#pwle=0`#B~^bg$aZPyGJD$@3-_SxU1{>s(bQ@k{nPzXz>}&=^ous z%t*d)i-kj;AbD3BOUMK^J@#l%gwRK$5yE@)J^}P%k)B>`v!J1X6pS zUwyNc7jS(|kdco~uZ9efd@nKtp!QGV%ZYxek~gW;ac z!V3=`mo%Hs{A_A9w+uJX>Brlx6*d=_VjCoBN*dtA_)6iX4(Q-jXuA_y&Fr6jP&FH9 ztamL&nboU5bj?v5G?Zj^TNFcuG#KFRuy*ckG3v?Q{#*OSFX77PMGq62N#?OGZs%iS z&P-2kguO0-8zZ0~Jz$rq)J^{sl44;onGj2K@G4)gXt<>;x4m;d%hqeh*XQ%CC13cn z`t2O$fm2V{RpWrzz72HEtY2NR){4W8_y^mnrA0HbdTgz${0Xg2wRWTW;i>*80@(Jwe4>i+|&^?~_(;Lv|MP=iDL zDO7xD`2S#x{@vtScJKtB+5MY2eKCL { + if (!app.isPackaged) { + windowManager.mainWindow?.webContents.openDevTools() + } + }) + .then(() => { + const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png') + const tray = new Tray(iconPath) + tray.setToolTip(app.getName()) + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Open Jan', + type: 'normal', + click: () => windowManager.showMainWindow(), + }, + { + label: 'Open Quick Ask', + type: 'normal', + click: () => windowManager.showQuickAskWindow(), + }, + { label: 'Quit', type: 'normal', click: () => app.quit() }, + ]) + tray.setContextMenu(contextMenu) + }) + .then(() => { + log(`Version: ${app.getVersion()}`) + }) .then(() => { app.on('activate', () => { if (!BrowserWindow.getAllWindows().length) { @@ -45,45 +87,42 @@ app }) .then(() => cleanLogs()) -app.once('window-all-closed', () => { - cleanUpAndQuit() +app.on('ready', () => { + registerGlobalShortcuts() }) app.once('quit', () => { cleanUpAndQuit() }) +function createQuickAskWindow() { + const startUrl = app.isPackaged ? `file://${quickAskPath}` : quickAskUrl + windowManager.createQuickAskWindow(preloadPath, startUrl) +} + function createMainWindow() { - /* Create main window */ - const mainWindow = WindowManager.instance.createWindow({ - webPreferences: { - nodeIntegration: true, - preload: join(__dirname, 'preload.js'), - webSecurity: false, - }, + const startUrl = app.isPackaged ? `file://${mainPath}` : mainUrl + windowManager.createMainWindow(preloadPath, startUrl) +} + +function registerGlobalShortcuts() { + // TODO: Toggle below line when build production + // const ret = globalShortcut.register(quickAskHotKey, () => { + // const selectedText = '' + const ret = registerShortcut(quickAskHotKey, (selectedText: string) => { + if (!windowManager.isQuickAskWindowVisible()) { + windowManager.showQuickAskWindow() + windowManager.sendQuickAskSelectedText(selectedText) + } else { + windowManager.hideQuickAskWindow() + } }) - const startURL = app.isPackaged - ? `file://${join(__dirname, '..', 'renderer', 'index.html')}` - : 'http://localhost:3000' - - /* Load frontend app to the window */ - mainWindow.loadURL(startURL) - - mainWindow.once('ready-to-show', () => mainWindow?.show()) - mainWindow.on('closed', () => { - if (process.platform !== 'darwin') app.quit() - }) - - /* Open external links in the default browser */ - mainWindow.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url) - return { action: 'deny' } - }) - - /* Enable dev tools for development */ - if (!app.isPackaged) mainWindow.webContents.openDevTools() - log(`Version: ${app.getVersion()}`) + if (!ret) { + console.error('Global shortcut registration failed') + } else { + console.log('Global shortcut registered successfully') + } } /** diff --git a/electron/managers/mainWindowConfig.ts b/electron/managers/mainWindowConfig.ts new file mode 100644 index 000000000..184fb1c86 --- /dev/null +++ b/electron/managers/mainWindowConfig.ts @@ -0,0 +1,16 @@ +const DEFAULT_WIDTH = 1200 +const DEFAULT_HEIGHT = 800 + +export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = { + width: DEFAULT_WIDTH, + minWidth: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + skipTaskbar: true, + show: true, + trafficLightPosition: { + x: 10, + y: 15, + }, + titleBarStyle: 'hiddenInset', + vibrancy: 'sidebar', +} diff --git a/electron/managers/quickAskWindowConfig.ts b/electron/managers/quickAskWindowConfig.ts new file mode 100644 index 000000000..4a5ce1e5d --- /dev/null +++ b/electron/managers/quickAskWindowConfig.ts @@ -0,0 +1,13 @@ +const DEFAULT_WIDTH = 556 + +const DEFAULT_HEIGHT = 60 + +export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = { + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + skipTaskbar: true, + resizable: false, + transparent: true, + frame: false, + type: 'panel', +} diff --git a/electron/managers/window.ts b/electron/managers/window.ts index 4edf505b2..5a5254bc8 100644 --- a/electron/managers/window.ts +++ b/electron/managers/window.ts @@ -1,37 +1,123 @@ -import { BrowserWindow } from 'electron' +import { BrowserWindow, app, shell } from 'electron' +import { quickAskWindowConfig } from './quickAskWindowConfig' +import { AppEvent } from '@janhq/core' +import { mainWindowConfig } from './mainWindowConfig' /** * Manages the current window instance. */ -export class WindowManager { - public static instance: WindowManager = new WindowManager() - public currentWindow?: BrowserWindow - - constructor() { - if (WindowManager.instance) { - return WindowManager.instance - } - } +// TODO: refactor this +let isAppQuitting = false +class WindowManager { + public mainWindow?: BrowserWindow + private _quickAskWindow: BrowserWindow | undefined = undefined + private _quickAskWindowVisible = false + private _mainWindowVisible = false /** * Creates a new window instance. * @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with. * @returns The created window instance. */ - createWindow(options?: Electron.BrowserWindowConstructorOptions | undefined) { - this.currentWindow = new BrowserWindow({ - width: 1200, - minWidth: 1200, - height: 800, - show: true, - trafficLightPosition: { - x: 10, - y: 15, + createMainWindow(preloadPath: string, startUrl: string) { + this.mainWindow = new BrowserWindow({ + ...mainWindowConfig, + webPreferences: { + nodeIntegration: true, + preload: preloadPath, + webSecurity: false, }, - titleBarStyle: 'hiddenInset', - vibrancy: 'sidebar', - ...options, }) - return this.currentWindow + + /* Load frontend app to the window */ + this.mainWindow.loadURL(startUrl) + + /* Open external links in the default browser */ + this.mainWindow.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url) + return { action: 'deny' } + }) + + app.on('before-quit', function () { + isAppQuitting = true + }) + + windowManager.mainWindow?.on('close', function (evt) { + if (!isAppQuitting) { + evt.preventDefault() + windowManager.hideMainWindow() + } + }) + } + + createQuickAskWindow(preloadPath: string, startUrl: string): void { + this._quickAskWindow = new BrowserWindow({ + ...quickAskWindowConfig, + webPreferences: { + nodeIntegration: true, + preload: preloadPath, + webSecurity: false, + }, + }) + + this._quickAskWindow.loadURL(startUrl) + this._quickAskWindow.on('blur', () => { + this.hideQuickAskWindow() + }) + this.hideQuickAskWindow() + } + + isMainWindowVisible(): boolean { + return this._mainWindowVisible + } + + hideMainWindow(): void { + this.mainWindow?.hide() + this._mainWindowVisible = false + // Only macos + if (process.platform === 'darwin') app.dock.hide() + } + + showMainWindow(): void { + this.mainWindow?.show() + this._mainWindowVisible = true + // Only macos + if (process.platform === 'darwin') app.dock.show() + } + + hideQuickAskWindow(): void { + this._quickAskWindow?.hide() + this._quickAskWindowVisible = false + } + + showQuickAskWindow(): void { + this._quickAskWindow?.show() + this._quickAskWindowVisible = true + } + + isQuickAskWindowVisible(): boolean { + return this._quickAskWindowVisible + } + + expandQuickAskWindow(heightOffset: number): void { + const width = quickAskWindowConfig.width! + const height = quickAskWindowConfig.height! + heightOffset + this._quickAskWindow?.setSize(width, height, true) + } + + sendQuickAskSelectedText(selectedText: string): void { + this._quickAskWindow?.webContents.send( + AppEvent.onSelectedText, + selectedText + ) + } + + cleanUp(): void { + this.mainWindow?.destroy() + this._quickAskWindow?.destroy() + this._quickAskWindowVisible = false + this._mainWindowVisible = false } } + +export const windowManager = new WindowManager() diff --git a/electron/package.json b/electron/package.json index cd6b83137..93c30682c 100644 --- a/electron/package.json +++ b/electron/package.json @@ -16,13 +16,15 @@ "pre-install", "models/**/*", "docs/**/*", - "scripts/**/*" + "scripts/**/*", + "icons/**/*" ], "asarUnpack": [ "pre-install", "models", "docs", - "scripts" + "scripts", + "icons" ], "publish": [ { @@ -39,6 +41,7 @@ "notarize": { "teamId": "F8AH6NHVY5" }, + "icon": "icons/icon.png" }, "linux": { @@ -81,7 +84,6 @@ "@janhq/core": "link:./core", "@janhq/server": "link:./server", "@npmcli/arborist": "^7.1.0", - "@uiball/loaders": "^1.3.0", "electron-store": "^8.1.0", "electron-updater": "^6.1.7", "fs-extra": "^11.2.0", @@ -90,7 +92,7 @@ "request": "^2.88.2", "request-progress": "^3.0.0", "ulid": "^2.3.0", - "use-debounce": "^9.0.4" + "@hurdlegroup/robotjs": "^0.11.4" }, "devDependencies": { "@electron/notarize": "^2.1.0", @@ -101,13 +103,15 @@ "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "electron": "28.0.0", - "electron-builder": "^24.9.1", + "electron-builder": "^24.13.3", + "electron-builder-squirrel-windows": "^24.13.3", "electron-devtools-installer": "^3.2.0", "electron-playwright-helpers": "^1.6.0", - "eslint-plugin-react": "^7.33.2", + "eslint": "8.57.0", + "eslint-plugin-react": "^7.34.0", "rimraf": "^5.0.5", "run-script-os": "^1.1.6", - "typescript": "^5.2.2" + "typescript": "^5.3.3" }, "installConfig": { "hoistingLimits": "workspaces" diff --git a/electron/utils/clean.ts b/electron/utils/clean.ts index 2334b589a..12a68d39e 100644 --- a/electron/utils/clean.ts +++ b/electron/utils/clean.ts @@ -1,12 +1,12 @@ import { ModuleManager } from '@janhq/core/node' -import { WindowManager } from './../managers/window' +import { windowManager } from './../managers/window' import { dispose } from './disposable' import { app } from 'electron' export function cleanUpAndQuit() { if (!ModuleManager.instance.cleaningResource) { ModuleManager.instance.cleaningResource = true - WindowManager.instance.currentWindow?.destroy() + windowManager.cleanUp() dispose(ModuleManager.instance.requiredModules) ModuleManager.instance.clearImportedModules() app.quit() diff --git a/electron/utils/selectedText.ts b/electron/utils/selectedText.ts new file mode 100644 index 000000000..6b2349725 --- /dev/null +++ b/electron/utils/selectedText.ts @@ -0,0 +1,39 @@ +import { clipboard, globalShortcut } from "electron"; +import { keyTap, keys } from "@hurdlegroup/robotjs"; + +/** + * Gets selected text by synthesizing the keyboard shortcut + * "CommandOrControl+c" then reading text from the clipboard + */ +export const getSelectedText = async () => { + const currentClipboardContent = clipboard.readText(); // preserve clipboard content + clipboard.clear(); + keyTap("c" as keys, process.platform === "darwin" ? "command" : "control"); + await new Promise((resolve) => setTimeout(resolve, 200)); // add a delay before checking clipboard + const selectedText = clipboard.readText(); + clipboard.writeText(currentClipboardContent); + return selectedText; +}; + +/** + * Registers a global shortcut of `accelerator`. The `callback` is called + * with the selected text when the registered shorcut is pressed by the user + * + * Returns `true` if the shortcut was registered successfully + */ +export const registerShortcut = ( + accelerator: Electron.Accelerator, + callback: (selectedText: string) => void +) => { + return globalShortcut.register(accelerator, async () => { + callback(await getSelectedText()); + }); +}; + +/** + * Unregisters a global shortcut of `accelerator` and + * is equivalent to electron.globalShortcut.unregister + */ +export const unregisterShortcut = (accelerator: Electron.Accelerator) => { + globalShortcut.unregister(accelerator); +}; \ No newline at end of file diff --git a/extensions/assistant-extension/package.json b/extensions/assistant-extension/package.json index baa858655..e3860a1c1 100644 --- a/extensions/assistant-extension/package.json +++ b/extensions/assistant-extension/package.json @@ -26,7 +26,7 @@ "rollup-plugin-define": "^1.0.1", "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript2": "^0.36.0", - "typescript": "^5.2.2", + "typescript": "^5.3.3", "run-script-os": "^1.1.6" }, "dependencies": { diff --git a/extensions/inference-nitro-extension/package.json b/extensions/inference-nitro-extension/package.json index ba6b473eb..e6365ad92 100644 --- a/extensions/inference-nitro-extension/package.json +++ b/extensions/inference-nitro-extension/package.json @@ -35,7 +35,7 @@ "rollup-plugin-sourcemaps": "^0.6.3", "rollup-plugin-typescript2": "^0.36.0", "run-script-os": "^1.1.6", - "typescript": "^5.2.2", + "typescript": "^5.3.3", "@types/os-utils": "^0.0.4", "@rollup/plugin-replace": "^5.0.5" }, diff --git a/server/package.json b/server/package.json index d9a2bbc9a..b2c237c61 100644 --- a/server/package.json +++ b/server/package.json @@ -39,8 +39,8 @@ "@types/tcp-port-used": "^1.0.4", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", - "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react": "^7.34.0", "run-script-os": "^1.1.6", - "typescript": "^5.2.2" + "typescript": "^5.3.3" } } diff --git a/uikit/package.json b/uikit/package.json index 66f05840b..b011ed497 100644 --- a/uikit/package.json +++ b/uikit/package.json @@ -52,6 +52,6 @@ "tailwind-merge": "^2.0.0", "terser": "^5.24.0", "tsup": "^7.2.0", - "typescript": "^5.2.2" + "typescript": "^5.3.3" } } diff --git a/web/app/search/SelectedText.tsx b/web/app/search/SelectedText.tsx new file mode 100644 index 000000000..742eba956 --- /dev/null +++ b/web/app/search/SelectedText.tsx @@ -0,0 +1,47 @@ +import React, { useCallback, useEffect, useRef } from 'react' + +import { useAtom } from 'jotai' +import { X } from 'lucide-react' + +import { selectedTextAtom } from '@/containers/Providers/Jotai' + +const SelectedText = ({ onCleared }: { onCleared?: () => void }) => { + const [text, setText] = useAtom(selectedTextAtom) + const containerRef = useRef(null) + + useEffect(() => { + if (text.trim().length === 0) { + window.core?.api?.quickAskSizeUpdated(0) + } else { + window.core?.api?.quickAskSizeUpdated( + (containerRef.current?.offsetHeight ?? 0) + 14 + ) + } + }) + + const onClearClicked = useCallback(() => { + setText('') + onCleared?.() + }, [setText, onCleared]) + + const shouldShowSelectedText = text.trim().length > 0 + + return shouldShowSelectedText ? ( +
+
+ +
+

{text}

+
+ ) : ( +
+ ) +} + +export default SelectedText diff --git a/web/app/search/UserInput.tsx b/web/app/search/UserInput.tsx new file mode 100644 index 000000000..a5fbfc682 --- /dev/null +++ b/web/app/search/UserInput.tsx @@ -0,0 +1,86 @@ +import React, { useState, useRef, useEffect } from 'react' + +import { Button } from '@janhq/uikit' +import { useAtomValue } from 'jotai' + +import { Send } from 'lucide-react' + +import LogoMark from '@/containers/Brand/Logo/Mark' + +import { selectedTextAtom } from '@/containers/Providers/Jotai' + +import SelectedText from './SelectedText' + +const UserInput: React.FC = () => { + const [inputValue, setInputValue] = useState('') + const inputRef = useRef(null) + const formRef = useRef(null) + const selectedText = useAtomValue(selectedTextAtom) + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus() + } + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + window.core?.api?.hideQuickAskWindow() + } + } + + document.addEventListener('keydown', onKeyDown) + + return () => { + document.removeEventListener('keydown', onKeyDown) + } + }, []) + + const handleChange = ( + event: + | React.ChangeEvent + | React.ChangeEvent + ) => { + const { value } = event.target + setInputValue(value) + } + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (inputValue.trim() !== '') { + const fullText = `${inputValue} ${selectedText}`.trim() + window.core?.api?.sendQuickAskInput(fullText) + setInputValue('') + window.core?.api?.hideQuickAskWindow() + window.core?.api?.showMainWindow() + } + } + + return ( +
+
+
+ + + +
+
+ + inputRef?.current?.focus()} /> +
+ ) +} + +export default UserInput diff --git a/web/app/search/page.tsx b/web/app/search/page.tsx new file mode 100644 index 000000000..0822c2676 --- /dev/null +++ b/web/app/search/page.tsx @@ -0,0 +1,13 @@ +'use client' + +import UserInput from './UserInput' + +const Search: React.FC = () => { + return ( +
+ +
+ ) +} + +export default Search diff --git a/web/containers/Providers/AppUpdateListener.tsx b/web/containers/Providers/AppUpdateListener.tsx index 542886ee5..d339b240a 100644 --- a/web/containers/Providers/AppUpdateListener.tsx +++ b/web/containers/Providers/AppUpdateListener.tsx @@ -1,6 +1,6 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Fragment, PropsWithChildren, useEffect } from 'react' +import { AppUpdateInfo } from '@janhq/core' import { useSetAtom } from 'jotai' import { appDownloadProgress, updateVersionError } from './Jotai' @@ -12,13 +12,14 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => { useEffect(() => { if (window && window.electronAPI) { window.electronAPI.onAppUpdateDownloadUpdate( - (_event: string, progress: any) => { - setProgress(progress.percent) - console.debug('app update progress:', progress.percent) + (_event: string, appUpdateInfo: AppUpdateInfo) => { + setProgress(appUpdateInfo.percent) + console.debug('app update progress:', appUpdateInfo.percent) } ) window.electronAPI.onAppUpdateDownloadError( + // eslint-disable-next-line @typescript-eslint/no-explicit-any (_event: string, error: any) => { console.error('Download error: ', error) setProgress(-1) @@ -33,8 +34,7 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => { setProgress(-1) }) } - return () => {} - }, [setProgress]) + }, [setProgress, setUpdateVersionError]) return {children} } diff --git a/web/containers/Providers/ClipboardListener.tsx b/web/containers/Providers/ClipboardListener.tsx new file mode 100644 index 000000000..780515461 --- /dev/null +++ b/web/containers/Providers/ClipboardListener.tsx @@ -0,0 +1,17 @@ +import { Fragment, PropsWithChildren } from 'react' + +import { useSetAtom } from 'jotai' + +import { selectedTextAtom } from './Jotai' + +const ClipboardListener = ({ children }: PropsWithChildren) => { + const setSelectedText = useSetAtom(selectedTextAtom) + + window?.electronAPI?.onSelectedText((_event: string, text: string) => { + setSelectedText(text) + }) + + return {children} +} + +export default ClipboardListener diff --git a/web/containers/Providers/DataLoader.tsx b/web/containers/Providers/DataLoader.tsx index fb439c92f..bc1461d5b 100644 --- a/web/containers/Providers/DataLoader.tsx +++ b/web/containers/Providers/DataLoader.tsx @@ -2,7 +2,7 @@ import { Fragment, ReactNode, useEffect } from 'react' -import { AppConfiguration } from '@janhq/core/.' +import { AppConfiguration } from '@janhq/core' import { useSetAtom } from 'jotai' import useAssistants from '@/hooks/useAssistants' diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index 1dd0bd042..f0020d311 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Fragment, ReactNode, useCallback, useEffect, useRef } from 'react' import { @@ -15,6 +14,7 @@ import { MessageRequestType, ModelEvent, Thread, + ModelInitFailed, } from '@janhq/core' import { useAtomValue, useSetAtom } from 'jotai' import { ulid } from 'ulid' @@ -113,15 +113,14 @@ export default function EventHandler({ children }: { children: ReactNode }) { }, [setActiveModel, setStateModel]) const onModelInitFailed = useCallback( - (res: any) => { - const errorMessage = res?.error ?? res - console.error('Failed to load model: ', errorMessage) + (res: ModelInitFailed) => { + console.error('Failed to load model: ', res.error.message) setStateModel(() => ({ state: 'start', loading: false, - model: res.modelId, + model: res.id, })) - setLoadModelError(errorMessage) + setLoadModelError(res.error.message) setQueuedMessage(false) }, [setStateModel, setQueuedMessage, setLoadModelError] @@ -245,7 +244,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { if (!threadMessages || threadMessages.length === 0) return - const summarizeFirstPrompt = `Summarize this text "${threadMessages[0].content[0].text.value}" for a conversation title in less than 10 words` + const summarizeFirstPrompt = `Summarize in a 5-word Title. Give the title only. "${threadMessages[0].content[0].text.value}"` // Prompt: Given this query from user {query}, return to me the summary in 5 words as the title const msgId = ulid() const messages: ChatCompletionMessage[] = [ diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index 9febbade5..bfc87917b 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -8,9 +8,11 @@ import { useSetAtom } from 'jotai' import { setDownloadStateAtom } from '@/hooks/useDownloadState' import AppUpdateListener from './AppUpdateListener' +import ClipboardListener from './ClipboardListener' import EventHandler from './EventHandler' import ModelImportListener from './ModelImportListener' +import QuickAskListener from './QuickAskListener' const EventListenerWrapper = ({ children }: PropsWithChildren) => { const setDownloadState = useSetAtom(setDownloadStateAtom) @@ -55,9 +57,13 @@ const EventListenerWrapper = ({ children }: PropsWithChildren) => { return ( - - {children} - + + + + {children} + + + ) } diff --git a/web/containers/Providers/Jotai.tsx b/web/containers/Providers/Jotai.tsx index c43786c89..7829041e9 100644 --- a/web/containers/Providers/Jotai.tsx +++ b/web/containers/Providers/Jotai.tsx @@ -15,6 +15,8 @@ export const appDownloadProgress = atom(-1) export const updateVersionError = atom(undefined) export const searchAtom = atom('') +export const selectedTextAtom = atom('') + export default function JotaiWrapper({ children }: Props) { return {children} } diff --git a/web/containers/Providers/KeyListener.tsx b/web/containers/Providers/KeyListener.tsx index a4702783c..d832059c2 100644 --- a/web/containers/Providers/KeyListener.tsx +++ b/web/containers/Providers/KeyListener.tsx @@ -24,6 +24,10 @@ export default function KeyListener({ children }: Props) { useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + window.core?.api?.hideMainWindow() + } + const prefixKey = isMac ? e.metaKey : e.ctrlKey if (e.key === 'b' && prefixKey) { diff --git a/web/containers/Providers/QuickAskListener.tsx b/web/containers/Providers/QuickAskListener.tsx new file mode 100644 index 000000000..3073c9036 --- /dev/null +++ b/web/containers/Providers/QuickAskListener.tsx @@ -0,0 +1,39 @@ +import { Fragment, ReactNode, useRef } from 'react' + +import { useSetAtom } from 'jotai' + +import { MainViewState } from '@/constants/screens' + +import useSendChatMessage from '@/hooks/useSendChatMessage' + +import { showRightSideBarAtom } from '@/screens/Chat/Sidebar' + +import { showLeftSideBarAtom } from './KeyListener' + +import { mainViewStateAtom } from '@/helpers/atoms/App.atom' + +type Props = { + children: ReactNode +} + +const QuickAskListener: React.FC = ({ children }) => { + const { sendChatMessage } = useSendChatMessage() + const setShowRightSideBar = useSetAtom(showRightSideBarAtom) + const setShowLeftSideBar = useSetAtom(showLeftSideBarAtom) + const setMainState = useSetAtom(mainViewStateAtom) + + const previousMessage = useRef('') + + window.electronAPI.onUserSubmitQuickAsk((_event: string, input: string) => { + if (previousMessage.current === input) return + setMainState(MainViewState.Thread) + setShowRightSideBar(false) + setShowLeftSideBar(false) + sendChatMessage(input) + previousMessage.current = input + }) + + return {children} +} + +export default QuickAskListener diff --git a/web/helpers/atoms/Assistant.atom.ts b/web/helpers/atoms/Assistant.atom.ts index e90923d3d..d44703cf4 100644 --- a/web/helpers/atoms/Assistant.atom.ts +++ b/web/helpers/atoms/Assistant.atom.ts @@ -1,4 +1,4 @@ -import { Assistant } from '@janhq/core/.' +import { Assistant } from '@janhq/core' import { atom } from 'jotai' export const assistantsAtom = atom([]) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 1b61a0dd1..600e10783 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -1,3 +1,5 @@ +import { useEffect, useRef } from 'react' + import { events, Model, ModelEvent } from '@janhq/core' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' @@ -24,6 +26,12 @@ export function useActiveModel() { const downloadedModels = useAtomValue(downloadedModelsAtom) const setLoadModelError = useSetAtom(loadModelErrorAtom) + const downloadedModelsRef = useRef([]) + + useEffect(() => { + downloadedModelsRef.current = downloadedModels + }, [downloadedModels]) + const startModel = async (modelId: string) => { if ( (activeModel && activeModel.id === modelId) || @@ -39,7 +47,7 @@ export function useActiveModel() { setStateModel({ state: 'start', loading: true, model: modelId }) - let model = downloadedModels.find((e) => e.id === modelId) + let model = downloadedModelsRef?.current.find((e) => e.id === modelId) if (!model) { toaster({ diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 09c64a0f1..9e88e763a 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -63,9 +63,8 @@ export default function useSendChatMessage() { const setEditPrompt = useSetAtom(editPromptAtom) const currentMessages = useAtomValue(getCurrentChatMessagesAtom) - const { activeModel } = useActiveModel() const selectedModel = useAtomValue(selectedModelAtom) - const { startModel } = useActiveModel() + const { activeModel, startModel } = useActiveModel() const setQueuedMessage = useSetAtom(queuedMessageAtom) const loadModelFailed = useAtomValue(loadModelErrorAtom) @@ -78,6 +77,7 @@ export default function useSendChatMessage() { const setReloadModel = useSetAtom(reloadModelAtom) const [fileUpload, setFileUpload] = useAtom(fileUploadAtom) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) + const activeThreadRef = useRef() useEffect(() => { modelRef.current = activeModel @@ -87,15 +87,19 @@ export default function useSendChatMessage() { loadModelFailedRef.current = loadModelFailed }, [loadModelFailed]) + useEffect(() => { + activeThreadRef.current = activeThread + }, [activeThread]) + const resendChatMessage = async (currentMessage: ThreadMessage) => { - if (!activeThread) { + if (!activeThreadRef.current) { console.error('No active thread') return } setIsGeneratingResponse(true) - updateThreadWaiting(activeThread.id, true) + updateThreadWaiting(activeThreadRef.current.id, true) const messages: ChatCompletionMessage[] = [ - activeThread.assistants[0]?.instructions, + activeThreadRef.current.assistants[0]?.instructions, ] .filter((e) => e && e.trim() !== '') .map((instructions) => { @@ -123,13 +127,14 @@ export default function useSendChatMessage() { id: ulid(), type: MessageRequestType.Thread, messages: messages, - threadId: activeThread.id, - model: activeThread.assistants[0].model ?? selectedModel, + threadId: activeThreadRef.current.id, + model: activeThreadRef.current.assistants[0].model ?? selectedModel, } - const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id + const modelId = + selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id - if (activeModel?.id !== modelId) { + if (modelRef.current?.id !== modelId) { setQueuedMessage(true) startModel(modelId) await waitForModelStarting(modelId) @@ -139,11 +144,11 @@ export default function useSendChatMessage() { if (currentMessage.role !== ChatCompletionRole.User) { // Delete last response before regenerating deleteMessage(currentMessage.id ?? '') - if (activeThread) { + if (activeThreadRef.current) { await extensionManager .get(ExtensionTypeEnum.Conversational) ?.writeMessages( - activeThread.id, + activeThreadRef.current.id, currentMessages.filter((msg) => msg.id !== currentMessage.id) ) } @@ -154,7 +159,7 @@ export default function useSendChatMessage() { const sendChatMessage = async (message: string) => { if (!message || message.trim().length === 0) return - if (!activeThread) { + if (!activeThreadRef.current) { console.error('No active thread') return } @@ -165,7 +170,7 @@ export default function useSendChatMessage() { const runtimeParams = toRuntimeParams(activeModelParams) const settingParams = toSettingParams(activeModelParams) - updateThreadWaiting(activeThread.id, true) + updateThreadWaiting(activeThreadRef.current.id, true) const prompt = message.trim() setCurrentPrompt('') setEditPrompt('') @@ -187,7 +192,7 @@ export default function useSendChatMessage() { } const messages: ChatCompletionMessage[] = [ - activeThread.assistants[0]?.instructions, + activeThreadRef.current.assistants[0]?.instructions, ] .filter((e) => e && e.trim() !== '') .map((instructions) => { @@ -218,7 +223,7 @@ export default function useSendChatMessage() { ? { type: ChatCompletionMessageContentType.Doc, doc_url: { - url: `threads/${activeThread.id}/files/${msgId}.pdf`, + url: `threads/${activeThreadRef.current.id}/files/${msgId}.pdf`, }, } : null, @@ -236,13 +241,14 @@ export default function useSendChatMessage() { ]) ) - let modelRequest = selectedModel ?? activeThread.assistants[0].model + let modelRequest = + selectedModel ?? activeThreadRef.current.assistants[0].model if (runtimeParams.stream == null) { runtimeParams.stream = true } // Add middleware to the model request with tool retrieval enabled if ( - activeThread.assistants[0].tools?.some( + activeThreadRef.current.assistants[0].tools?.some( (tool: AssistantTool) => tool.type === 'retrieval' && tool.enabled ) ) { @@ -260,14 +266,14 @@ export default function useSendChatMessage() { const messageRequest: MessageRequest = { id: msgId, type: MessageRequestType.Thread, - threadId: activeThread.id, + threadId: activeThreadRef.current.id, messages, model: { ...modelRequest, settings: settingParams, parameters: runtimeParams, }, - thread: activeThread, + thread: activeThreadRef.current, } const timestamp = Date.now() @@ -307,7 +313,7 @@ export default function useSendChatMessage() { const threadMessage: ThreadMessage = { id: msgId, - thread_id: activeThread.id, + thread_id: activeThreadRef.current.id, role: ChatCompletionRole.User, status: MessageStatus.Ready, created: timestamp, @@ -322,10 +328,10 @@ export default function useSendChatMessage() { } const updatedThread: Thread = { - ...activeThread, + ...activeThreadRef.current, updated: timestamp, metadata: { - ...(activeThread.metadata ?? {}), + ...(activeThreadRef.current.metadata ?? {}), lastMessage: prompt, }, } @@ -337,9 +343,10 @@ export default function useSendChatMessage() { .get(ExtensionTypeEnum.Conversational) ?.addNewMessage(threadMessage) - const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id + const modelId = + selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id - if (activeModel?.id !== modelId) { + if (modelRef.current?.id !== modelId) { setQueuedMessage(true) startModel(modelId) await waitForModelStarting(modelId) diff --git a/web/package.json b/web/package.json index 0a8af0f92..e3301f68b 100644 --- a/web/package.json +++ b/web/package.json @@ -37,6 +37,7 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.47.0", "react-hot-toast": "^2.4.1", + "csstype": "^3.0.10", "react-icons": "^4.12.0", "react-scroll-to-bottom": "^4.2.0", "react-toastify": "^9.1.3", @@ -66,11 +67,11 @@ "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.28.1", "eslint-plugin-prettier": "^5.0.1", - "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.6", "rimraf": "^5.0.5", - "typescript": "^5.2.2" + "typescript": "^5.3.3" } } diff --git a/web/screens/Settings/ImportingModelModal/ImportingModelItem.tsx b/web/screens/Settings/ImportingModelModal/ImportingModelItem.tsx index 0426c3dd0..d4d50722f 100644 --- a/web/screens/Settings/ImportingModelModal/ImportingModelItem.tsx +++ b/web/screens/Settings/ImportingModelModal/ImportingModelItem.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo } from 'react' -import { ImportingModel } from '@janhq/core/.' +import { ImportingModel } from '@janhq/core' import { useSetAtom } from 'jotai' import { AlertCircle } from 'lucide-react' From bb4c72d892b9550559ab7f861bd19876f67fc0f2 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 12:07:10 +0700 Subject: [PATCH 14/36] add: init first draft version --- .../guides/error-codes/stuck-on-loading.mdx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/docs/guides/error-codes/stuck-on-loading.mdx diff --git a/docs/docs/guides/error-codes/stuck-on-loading.mdx b/docs/docs/guides/error-codes/stuck-on-loading.mdx new file mode 100644 index 000000000..09762446b --- /dev/null +++ b/docs/docs/guides/error-codes/stuck-on-loading.mdx @@ -0,0 +1,45 @@ +--- +title: Stuck on loading model +slug: /troubleshooting/stuck-on-loading +description: Troubleshooting steps to resolve issues related to loading model. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + troubleshooting, + ] +--- + +Encountering an stuck on loading model issue in Jan is caused by errors related the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. + +It can be resolved through the following steps: + +#### Adding WMIC Path to Environment Variables: + +1. **Open System Properties:** + - Press `Windows key + R`. + - Type `sysdm.cpl` and press `Enter`. + +2. **Access Environment Variables:** + - Go to the "Advanced" tab. + - Click "Environment Variables" button. + +3. **Edit System PATH:** + - Under "System Variables," find and select `Path`. + - Click "Edit." + +4. **Add WMIC Path:** + - Click "New" and enter `C:\Windows\System32\Wbem`. + +5. **Save Changes:** + - Click "OK" to close and save your changes. + +6. **Verify Installation:** + - Restart any command prompts or terminals. + - Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`. \ No newline at end of file From 5c7ac0a75291ea3e86032da0921b7d161caa36b7 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 13:40:37 +0700 Subject: [PATCH 15/36] add: docs for cpus without avx --- .../guides/error-codes/stuck-on-loading.mdx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/docs/guides/error-codes/stuck-on-loading.mdx b/docs/docs/guides/error-codes/stuck-on-loading.mdx index 09762446b..26ad6f6a5 100644 --- a/docs/docs/guides/error-codes/stuck-on-loading.mdx +++ b/docs/docs/guides/error-codes/stuck-on-loading.mdx @@ -16,11 +16,13 @@ keywords: ] --- +## 1. Missing Windows Management Instrumentation Command-line (WMIC) + Encountering an stuck on loading model issue in Jan is caused by errors related the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. It can be resolved through the following steps: -#### Adding WMIC Path to Environment Variables: +### Adding WMIC Path to Environment Variables: 1. **Open System Properties:** - Press `Windows key + R`. @@ -42,4 +44,15 @@ It can be resolved through the following steps: 6. **Verify Installation:** - Restart any command prompts or terminals. - - Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`. \ No newline at end of file + - Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`. + + +## 2. CPU without AVX + +Encountering an issue with models stuck on loading in Jan can be due to the use of older generation CPUs that do not support Advanced Vector Extensions (AVX). + +To check if your CPU supports AVX, visit the following link: [CPUs with AVX](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#CPUs_with_AVX) + +:::warning [Please use this with caution] +As a workaround, consider using an [emulator](https://www.intel.com/content/www/us/en/developer/articles/tool/software-development-emulator.html) to simulate AVX support. +::: \ No newline at end of file From 6cf2a462538ddc59dff60adc655dd50c7a4ad735 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 13:45:18 +0700 Subject: [PATCH 16/36] add: error message for wbem --- docs/docs/guides/error-codes/stuck-on-loading.mdx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/docs/guides/error-codes/stuck-on-loading.mdx b/docs/docs/guides/error-codes/stuck-on-loading.mdx index 26ad6f6a5..584857303 100644 --- a/docs/docs/guides/error-codes/stuck-on-loading.mdx +++ b/docs/docs/guides/error-codes/stuck-on-loading.mdx @@ -16,13 +16,16 @@ keywords: ] --- -## 1. Missing Windows Management Instrumentation Command-line (WMIC) +## 1. Issue: Missing Windows Management Instrumentation Command-line (WMIC) Encountering an stuck on loading model issue in Jan is caused by errors related the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. -It can be resolved through the following steps: +Error message: +``` +index.js:47 Uncaught (in promise) Error: Error invoking remote method 'invokeExtensionFunc': Error: Command failed: WMIC CPU Get NumberOfCores +``` -### Adding WMIC Path to Environment Variables: +It can be resolved through the following steps: 1. **Open System Properties:** - Press `Windows key + R`. @@ -47,7 +50,7 @@ It can be resolved through the following steps: - Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`. -## 2. CPU without AVX +## 2. Issue: Model Loading Stuck Due to CPU without AVX Encountering an issue with models stuck on loading in Jan can be due to the use of older generation CPUs that do not support Advanced Vector Extensions (AVX). From 17181927a4456a2a2f35cf28b86b8e4f11bac587 Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Fri, 8 Mar 2024 14:12:50 +0700 Subject: [PATCH 17/36] docs: fix the navbar based on the latest feedback --- docs/sidebars.js | 105 +++++++++++++++++++++++++++++----------- docs/src/css/custom.css | 7 +++ 2 files changed, 84 insertions(+), 28 deletions(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index 61bd85439..4c45cadbe 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -177,7 +177,6 @@ const sidebars = { items: [ "guides/best-practices", "guides/thread", - "guides/faq", ] }, { @@ -186,24 +185,54 @@ const sidebars = { collapsible: false, className: "head_Menu", items: [ - "guides/models/README", - "guides/models/customize-engine", - "guides/models/import-models", - "guides/models/integrate-remote", - "guides/extensions/README", - "guides/extensions/import-ext", - "guides/extensions/setup-ext", - "guides/integration/README", - "guides/integration/azure", - "guides/integration/discord", - "guides/integration/groq", - "guides/integration/lmstudio", - "guides/integration/mistral", - "guides/integration/ollama", - "guides/integration/openinterpreter", - "guides/integration/openrouter", - "guides/integration/raycast", - "guides/integration/vscode", + { + type: "category", + label: "Advanced Model Setup", + className: "head_SubMenu", + link: { + type: 'doc', + id: "guides/models/README", + }, + items: [ + "guides/models/customize-engine", + "guides/models/import-models", + "guides/models/integrate-remote", + ] + }, + { + type: "category", + label: "Extensions", + className: "head_SubMenu", + link: { + type: 'doc', + id: "guides/extensions/README", + }, + items: [ + "guides/extensions/import-ext", + "guides/extensions/setup-ext", + ] + }, + { + type: "category", + label: "Integrations", + className: "head_SubMenu", + link: { + type: 'doc', + id: "guides/integration/README", + }, + items: [ + "guides/integration/azure", + "guides/integration/discord", + "guides/integration/groq", + "guides/integration/lmstudio", + "guides/integration/mistral", + "guides/integration/ollama", + "guides/integration/openinterpreter", + "guides/integration/openrouter", + "guides/integration/raycast", + "guides/integration/vscode", + ] + }, ] }, { @@ -212,15 +241,35 @@ const sidebars = { collapsible: false, className: "head_Menu", items: [ - "guides/common-error/README", - "guides/common-error/broken-build", - "guides/common-error/not-using-gpu", - "guides/error-codes/README", - "guides/error-codes/how-to-get-error-logs", - "guides/error-codes/permission-denied", - "guides/error-codes/something-amiss", - "guides/error-codes/undefined-issue", - "guides/error-codes/unexpected-token", + { + type: "category", + label: "Error Codes", + className: "head_SubMenu", + link: { + type: 'doc', + id: "guides/error-codes/README", + }, + items: [ + "guides/error-codes/how-to-get-error-logs", + "guides/error-codes/permission-denied", + "guides/error-codes/something-amiss", + "guides/error-codes/undefined-issue", + "guides/error-codes/unexpected-token", + ] + }, + { + type: "category", + label: "Common Error", + className: "head_SubMenu", + link: { + type: 'doc', + id: "guides/common-error/README", + }, + items: [ + "guides/common-error/broken-build", + "guides/common-error/not-using-gpu", + ] + }, "guides/faq" ] }, diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 5307bec2e..7d6bd33d2 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -20,4 +20,11 @@ font-weight: normal; background-color: white; margin-bottom: 5px; /* Adjust margin as needed */ +} + +.head_SubMenu div { + font-weight: normal; + background-color: white; + margin-left: 0rem; + font-size: medium; } \ No newline at end of file From 74a72a1d6fb878774d676f633d9aa07c8ba7b202 Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Fri, 8 Mar 2024 14:13:07 +0700 Subject: [PATCH 18/36] docs: update Faq and guides integration content --- docs/docs/guides/faq.mdx | 11 +++++++---- docs/docs/guides/integration/groq.mdx | 10 +++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/docs/guides/faq.mdx b/docs/docs/guides/faq.mdx index d25b0f980..7e3d7d13d 100644 --- a/docs/docs/guides/faq.mdx +++ b/docs/docs/guides/faq.mdx @@ -75,14 +75,11 @@ keywords: ## Development and Features -- **Can I use local files from my computer with Jan?** - - The feature to use local files is currently in development. For more information, please our offical [Discord](https://discord.com/invite/FTk2MvZwJH) for the latest update. - - **Does Jan support Safetensors?** - At the moment, Jan only supports GGUF. However, there are plans to support `.safetensor` files in the future. - **I hope to customize the installation path of each model. Is that possible?** - - Yes you can customize the installation path. Please see [here](https://github.com/janhq/jan/issues/1010) for more information. + - Yes you can customize the installation path. Please see [here](https://jan.ai/guides/advanced-settings/#access-the-jan-data-folder) for more information. ## Troubleshooting @@ -94,3 +91,9 @@ keywords: - **What should I do if "Failed to fetch" occurs using MacBook Pro with Intel HD Graphics 4000 1536 MB?** - Ensure that the model size is less than 90% of your available VRAM and that the VRAM is accessible to the app. Managing the resources effectively can help mitigate this issue. + +:::info[Assistance and Support] + +If you have questions, please join our [Discord community](https://discord.gg/Dt7MxDyNNZ) for support, updates, and discussions. + +::: \ No newline at end of file diff --git a/docs/docs/guides/integration/groq.mdx b/docs/docs/guides/integration/groq.mdx index a3a12dbc5..01eee1cb3 100644 --- a/docs/docs/guides/integration/groq.mdx +++ b/docs/docs/guides/integration/groq.mdx @@ -22,7 +22,7 @@ keywords: This guide provides step-by-step instructions on integrating the Groq API with Jan, enabling users to leverage Groq's capabilities within Jan's conversational interface. Before proceeding, ensure you have the following: -- Access to the Jan platform +- Access to the Jan Application - Groq API credentials ## Integration Steps @@ -38,7 +38,7 @@ Obtain Groq API keys from your [Groq Console](https://console.groq.com/keys). ```json title="~/jan/engines/openai.json" { - "full_url": "https://api.groq.com/openai/v1", + "full_url": "https://api.groq.com/openai/v1/chat/completions", "api_key": "" } ``` @@ -52,7 +52,7 @@ To set up the configuration for Groq in Jan, follow these steps: 3. Inside the groq folder, create a model.json file with the specified settings: ```json title="~/jan/models/groq/model.json { - "id": "groq", + "id": "mixtral-8x7b-32768", "object": "model", "name": "Groq Integration", "version": "1.0", @@ -62,10 +62,10 @@ To set up the configuration for Groq in Jan, follow these steps: "settings": {}, "parameters": {}, "metadata": { - "author": "Your Name", + "author": "Mistral ", "tags": ["Groq Integration"] }, - "engine": "groq" + "engine": "openai" } ``` From 2726ed34cd9ef6cc93372db9544764a09ce1f657 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 14:34:35 +0700 Subject: [PATCH 19/36] fix: typo --- docs/docs/guides/error-codes/stuck-on-loading.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/guides/error-codes/stuck-on-loading.mdx b/docs/docs/guides/error-codes/stuck-on-loading.mdx index 584857303..38ffed481 100644 --- a/docs/docs/guides/error-codes/stuck-on-loading.mdx +++ b/docs/docs/guides/error-codes/stuck-on-loading.mdx @@ -16,7 +16,7 @@ keywords: ] --- -## 1. Issue: Missing Windows Management Instrumentation Command-line (WMIC) +## 1. Issue: Model Loading Stuck Due To Missing Windows Management Instrumentation Command-line (WMIC) Encountering an stuck on loading model issue in Jan is caused by errors related the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. @@ -50,7 +50,7 @@ It can be resolved through the following steps: - Run `where wmic` to verify. Expected output: `C:\Windows\System32\wbem\WMIC.exe`. -## 2. Issue: Model Loading Stuck Due to CPU without AVX +## 2. Issue: Model Loading Stuck Due To CPU Without AVX Encountering an issue with models stuck on loading in Jan can be due to the use of older generation CPUs that do not support Advanced Vector Extensions (AVX). From e22fdec23049d94347395812e3237530e9a99715 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Fri, 8 Mar 2024 14:34:42 +0700 Subject: [PATCH 20/36] fix: reducing height title-bar drag window for enable clickable quick ask (#2278) --- web/styles/base/global.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/styles/base/global.scss b/web/styles/base/global.scss index 05584c9e7..f1b7c3537 100644 --- a/web/styles/base/global.scss +++ b/web/styles/base/global.scss @@ -12,7 +12,7 @@ left: 0px; top: 0px; width: 100%; - height: 48px; + height: 24px; user-select: none; -webkit-app-region: drag; } From 775ae0b994ca146f41884f6a8b9bf12a6f1ac30d Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 14:34:57 +0700 Subject: [PATCH 21/36] add: no assistant bug docs --- .../error-codes/no-assistant-available.mdx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/docs/guides/error-codes/no-assistant-available.mdx diff --git a/docs/docs/guides/error-codes/no-assistant-available.mdx b/docs/docs/guides/error-codes/no-assistant-available.mdx new file mode 100644 index 000000000..ed954db60 --- /dev/null +++ b/docs/docs/guides/error-codes/no-assistant-available.mdx @@ -0,0 +1,31 @@ +--- +title: No assistant available +slug: /troubleshooting/no-assistant-available +description: Troubleshooting steps to resolve issues related to loading model. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + troubleshooting, + ] +--- + + +When you encounter the following error message: +``` +No assistant available. +``` + +This issue arises when a new, unintentional file appears in `/jan/assistants`. + +It can be resolved through the following steps: + +1. Access the `/jan/assistants` directory using a file manager or terminal. + +2. Within `/jan/assistants`, this directory should only contain a folder named `jan`. Identify any file outside of this folder and remove it. \ No newline at end of file From 8b8c9d748e10914c6ae40de5bb6f44c4244a38ed Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 14:44:21 +0700 Subject: [PATCH 22/36] add: threads disappearance --- .../guides/error-codes/missing-thread.mdx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/docs/guides/error-codes/missing-thread.mdx diff --git a/docs/docs/guides/error-codes/missing-thread.mdx b/docs/docs/guides/error-codes/missing-thread.mdx new file mode 100644 index 000000000..36bf4a66b --- /dev/null +++ b/docs/docs/guides/error-codes/missing-thread.mdx @@ -0,0 +1,25 @@ +--- +title: Thread Disappearance +slug: /troubleshooting/thread-disappearance +description: Troubleshooting steps to resolve issues threads suddenly disappearance. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + troubleshooting, + ] +--- + +When you encounter the error of old threads suddenly disappear. This can happen when a new, unintentional file is created in `/jan/threads`. + +It can be resolved through the following steps: + +1. Go to `/jan/threads`. + +2. The `/jan/threads` directory contains many folders named with the prefix `jan_` followed by an ID (e.g., `jan_123`). Look for any file not conforming to this naming pattern and remove it. \ No newline at end of file From 3c1607fe440cc14dfad21a869d7e72b8eee974a9 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Fri, 8 Mar 2024 14:44:42 +0700 Subject: [PATCH 23/36] fix: coorect slug --- docs/docs/guides/error-codes/no-assistant-available.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/error-codes/no-assistant-available.mdx b/docs/docs/guides/error-codes/no-assistant-available.mdx index ed954db60..3dbdbe5f6 100644 --- a/docs/docs/guides/error-codes/no-assistant-available.mdx +++ b/docs/docs/guides/error-codes/no-assistant-available.mdx @@ -1,7 +1,7 @@ --- title: No assistant available slug: /troubleshooting/no-assistant-available -description: Troubleshooting steps to resolve issues related to loading model. +description: Troubleshooting steps to resolve issues no assistant available. keywords: [ Jan AI, From 3320162ea9c8eac1f2e9418f89964702bc2163e5 Mon Sep 17 00:00:00 2001 From: NamH Date: Fri, 8 Mar 2024 15:01:04 +0700 Subject: [PATCH 24/36] chore: small update/fixes for quick ask window (#2279) Signed-off-by: James Co-authored-by: James --- electron/main.ts | 5 +---- electron/managers/quickAskWindowConfig.ts | 9 +++++++++ electron/managers/window.ts | 1 - web/app/search/UserInput.tsx | 6 +++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index ea51c2a83..21f95cd00 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, globalShortcut, Menu, Tray } from 'electron' +import { app, BrowserWindow, Menu, Tray } from 'electron' import { join } from 'path' /** @@ -106,9 +106,6 @@ function createMainWindow() { } function registerGlobalShortcuts() { - // TODO: Toggle below line when build production - // const ret = globalShortcut.register(quickAskHotKey, () => { - // const selectedText = '' const ret = registerShortcut(quickAskHotKey, (selectedText: string) => { if (!windowManager.isQuickAskWindowVisible()) { windowManager.showQuickAskWindow() diff --git a/electron/managers/quickAskWindowConfig.ts b/electron/managers/quickAskWindowConfig.ts index 4a5ce1e5d..eb30e8ebc 100644 --- a/electron/managers/quickAskWindowConfig.ts +++ b/electron/managers/quickAskWindowConfig.ts @@ -6,7 +6,16 @@ export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, skipTaskbar: true, + acceptFirstMouse: true, + hasShadow: true, + alwaysOnTop: true, + show: false, + fullscreenable: false, resizable: false, + center: true, + movable: false, + maximizable: false, + focusable: true, transparent: true, frame: false, type: 'panel', diff --git a/electron/managers/window.ts b/electron/managers/window.ts index 5a5254bc8..796a5d54a 100644 --- a/electron/managers/window.ts +++ b/electron/managers/window.ts @@ -64,7 +64,6 @@ class WindowManager { this._quickAskWindow.on('blur', () => { this.hideQuickAskWindow() }) - this.hideQuickAskWindow() } isMainWindowVisible(): boolean { diff --git a/web/app/search/UserInput.tsx b/web/app/search/UserInput.tsx index a5fbfc682..3bf148f40 100644 --- a/web/app/search/UserInput.tsx +++ b/web/app/search/UserInput.tsx @@ -18,10 +18,10 @@ const UserInput: React.FC = () => { const selectedText = useAtomValue(selectedTextAtom) useEffect(() => { - if (inputRef.current) { - inputRef.current.focus() - } + inputRef.current?.focus() + }) + useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { window.core?.api?.hideQuickAskWindow() From 82eb31114bd3c0f91c5c733bcb38227538cdfbfd Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:25:50 +0900 Subject: [PATCH 25/36] docs: fixing slug --- docs/docs/guides/error-codes/no-assistant-available.mdx | 6 +++--- .../{stuck-on-loading.mdx => stuck-on-loading-model.mdx} | 5 +++-- .../{missing-thread.mdx => thread-disappreance.mdx} | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) rename docs/docs/guides/error-codes/{stuck-on-loading.mdx => stuck-on-loading-model.mdx} (96%) rename docs/docs/guides/error-codes/{missing-thread.mdx => thread-disappreance.mdx} (93%) diff --git a/docs/docs/guides/error-codes/no-assistant-available.mdx b/docs/docs/guides/error-codes/no-assistant-available.mdx index 3dbdbe5f6..31d9a75e9 100644 --- a/docs/docs/guides/error-codes/no-assistant-available.mdx +++ b/docs/docs/guides/error-codes/no-assistant-available.mdx @@ -1,6 +1,6 @@ --- -title: No assistant available -slug: /troubleshooting/no-assistant-available +title: No Assistant Available +sidebar_position: 7 description: Troubleshooting steps to resolve issues no assistant available. keywords: [ @@ -13,10 +13,10 @@ keywords: no-subscription fee, large language model, troubleshooting, + no assistant available, ] --- - When you encounter the following error message: ``` No assistant available. diff --git a/docs/docs/guides/error-codes/stuck-on-loading.mdx b/docs/docs/guides/error-codes/stuck-on-loading-model.mdx similarity index 96% rename from docs/docs/guides/error-codes/stuck-on-loading.mdx rename to docs/docs/guides/error-codes/stuck-on-loading-model.mdx index 38ffed481..95b45e6bf 100644 --- a/docs/docs/guides/error-codes/stuck-on-loading.mdx +++ b/docs/docs/guides/error-codes/stuck-on-loading-model.mdx @@ -1,6 +1,6 @@ --- -title: Stuck on loading model -slug: /troubleshooting/stuck-on-loading +title: Stuck on Loading Model +sidebar_position: 8 description: Troubleshooting steps to resolve issues related to loading model. keywords: [ @@ -13,6 +13,7 @@ keywords: no-subscription fee, large language model, troubleshooting, + stuck on loading model, ] --- diff --git a/docs/docs/guides/error-codes/missing-thread.mdx b/docs/docs/guides/error-codes/thread-disappreance.mdx similarity index 93% rename from docs/docs/guides/error-codes/missing-thread.mdx rename to docs/docs/guides/error-codes/thread-disappreance.mdx index 36bf4a66b..06235df56 100644 --- a/docs/docs/guides/error-codes/missing-thread.mdx +++ b/docs/docs/guides/error-codes/thread-disappreance.mdx @@ -1,6 +1,6 @@ --- title: Thread Disappearance -slug: /troubleshooting/thread-disappearance +sidebar_position: 6 description: Troubleshooting steps to resolve issues threads suddenly disappearance. keywords: [ @@ -13,6 +13,7 @@ keywords: no-subscription fee, large language model, troubleshooting, + thread disappearance, ] --- From 1f6ba881fc619e1c4241c683996df38ab8385e75 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Sat, 9 Mar 2024 00:56:19 +0900 Subject: [PATCH 26/36] docs: fix typo --- docs/docs/guides/error-codes/stuck-on-loading-model.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/guides/error-codes/stuck-on-loading-model.mdx b/docs/docs/guides/error-codes/stuck-on-loading-model.mdx index 95b45e6bf..86a16b5fc 100644 --- a/docs/docs/guides/error-codes/stuck-on-loading-model.mdx +++ b/docs/docs/guides/error-codes/stuck-on-loading-model.mdx @@ -1,7 +1,7 @@ --- title: Stuck on Loading Model sidebar_position: 8 -description: Troubleshooting steps to resolve issues related to loading model. +description: Troubleshooting steps to resolve issues related to the loading model. keywords: [ Jan AI, @@ -19,7 +19,7 @@ keywords: ## 1. Issue: Model Loading Stuck Due To Missing Windows Management Instrumentation Command-line (WMIC) -Encountering an stuck on loading model issue in Jan is caused by errors related the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. +Encountering a stuck-on-loading model issue in Jan is caused by errors related to the `Windows Management Instrumentation Command-line (WMIC)` path not being included in the system's PATH environment variable. Error message: ``` @@ -34,10 +34,10 @@ It can be resolved through the following steps: 2. **Access Environment Variables:** - Go to the "Advanced" tab. - - Click "Environment Variables" button. + - Click the "Environment Variables" button. 3. **Edit System PATH:** - - Under "System Variables," find and select `Path`. + - Under "System Variables" find and select `Path`. - Click "Edit." 4. **Add WMIC Path:** From d492207c7fedddc01a39f19bce8aef52512f9784 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:40:44 +0900 Subject: [PATCH 27/36] docs: fix darkmode issue --- docs/src/css/custom.css | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 7d6bd33d2..fd1f2b384 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -1,3 +1,4 @@ +/* Hide descriptions in cards without a description */ .DocCardList--no-description .card p { display: none; } @@ -7,7 +8,7 @@ --docsearch-hit-active-color: #090a11; /* Keep the color unchanged */ } -/* sidebarStyles.css */ +/* Sidebar styles */ .head_Menu div { font-weight: bold; background-color: whitesmoke; @@ -16,10 +17,9 @@ } .head_Menu li { - /* Custom styles for the sidebar items */ font-weight: normal; background-color: white; - margin-bottom: 5px; /* Adjust margin as needed */ + margin-bottom: 5px; } .head_SubMenu div { @@ -27,4 +27,20 @@ background-color: white; margin-left: 0rem; font-size: medium; -} \ No newline at end of file +} + +/* Dark mode styles based on Docusaurus dark theme */ +[data-theme="dark"] .head_Menu div { + background-color: var(--ifm-background-color); + color: var(--ifm-font-color-base); +} + +[data-theme="dark"] .head_Menu li { + background-color: var(--ifm-background-color); + color: var(--ifm-font-color-base); +} + +[data-theme="dark"] .head_SubMenu div { + background-color: var(--ifm-background-color); + color: var(--ifm-font-color-base); +} From 6359364e2f89fa941f6c62ac39c6d83903698361 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:42:11 +0900 Subject: [PATCH 28/36] docs: update groq integration --- docs/docs/guides/integration/groq.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/integration/groq.mdx b/docs/docs/guides/integration/groq.mdx index 01eee1cb3..a57bf16dd 100644 --- a/docs/docs/guides/integration/groq.mdx +++ b/docs/docs/guides/integration/groq.mdx @@ -62,7 +62,7 @@ To set up the configuration for Groq in Jan, follow these steps: "settings": {}, "parameters": {}, "metadata": { - "author": "Mistral ", + "author": "Mistral", "tags": ["Groq Integration"] }, "engine": "openai" From 086f8b207e34eee21618a528c04262776e9cb3eb Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:46:35 +0900 Subject: [PATCH 29/36] docs: enhance css style --- docs/src/css/custom.css | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index fd1f2b384..4593f4f94 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -4,43 +4,45 @@ } /* For dark theme */ -[data-theme="dark"] .DocSearch { +[data-theme='dark'] .DocSearch { --docsearch-hit-active-color: #090a11; /* Keep the color unchanged */ } - -/* Sidebar styles */ -.head_Menu div { +/* Sidebar styles based on Docusaurus light theme */ +[data-theme='light'] .head_Menu div { font-weight: bold; - background-color: whitesmoke; + background-color: var(--ifm-background-color); margin-left: 0.7rem; font-size: larger; + color: var(--ifm-font-color-base); } -.head_Menu li { +[data-theme='light'] .head_Menu li { font-weight: normal; - background-color: white; + background-color: var(--ifm-background-color); margin-bottom: 5px; + color: var(--ifm-font-color-base); } -.head_SubMenu div { +[data-theme='light'] .head_SubMenu div { font-weight: normal; - background-color: white; + background-color: var(--ifm-background-color); margin-left: 0rem; font-size: medium; + color: var(--ifm-font-color-base); } /* Dark mode styles based on Docusaurus dark theme */ -[data-theme="dark"] .head_Menu div { +[data-theme='dark'] .head_Menu div { background-color: var(--ifm-background-color); color: var(--ifm-font-color-base); } -[data-theme="dark"] .head_Menu li { +[data-theme='dark'] .head_Menu li { background-color: var(--ifm-background-color); color: var(--ifm-font-color-base); } -[data-theme="dark"] .head_SubMenu div { +[data-theme='dark'] .head_SubMenu div { background-color: var(--ifm-background-color); color: var(--ifm-font-color-base); } From 3aeb6434b8d65f5540778ceff311c63d6683d933 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:02:54 +0700 Subject: [PATCH 30/36] codesign script force sign (#2291) Co-authored-by: Hien To --- .github/scripts/auto-sign.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/auto-sign.sh b/.github/scripts/auto-sign.sh index a2130e791..e7ea49d40 100755 --- a/.github/scripts/auto-sign.sh +++ b/.github/scripts/auto-sign.sh @@ -7,6 +7,6 @@ if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then fi # If both variables are set, execute the following commands -find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \; +find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \; -find "$APP_PATH" -type f -name "*.o" -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \; +find "$APP_PATH" -type f -name "*.o" -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \; From 15064ac12539b9054847574420a070d4e7907d4a Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 11 Mar 2024 06:17:51 +0000 Subject: [PATCH 31/36] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8a4c03098..dd9b05088 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 1474a28dea28ca7df94a50a61f1640698eabf843 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 11 Mar 2024 06:35:11 +0000 Subject: [PATCH 32/36] Update README.md with Stable Download URLs --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd9b05088..43b43b3e2 100644 --- a/README.md +++ b/README.md @@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Stable (Recommended) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 0f14faf762a3e996718a2b4dfa73a1164de9be0b Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Mon, 11 Mar 2024 14:10:11 +0700 Subject: [PATCH 33/36] docs: fix the navbar style on darkmode version --- docs/src/css/custom.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 4593f4f94..deab47c88 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -33,16 +33,24 @@ /* Dark mode styles based on Docusaurus dark theme */ [data-theme='dark'] .head_Menu div { + font-weight: bold; background-color: var(--ifm-background-color); color: var(--ifm-font-color-base); + margin-left: 0.7rem; + font-size: larger; } [data-theme='dark'] .head_Menu li { + font-weight: normal; background-color: var(--ifm-background-color); + margin-bottom: 5px; color: var(--ifm-font-color-base); } [data-theme='dark'] .head_SubMenu div { + font-weight: normal; background-color: var(--ifm-background-color); color: var(--ifm-font-color-base); + margin-left: 0rem; + font-size: medium; } From 1ce3f1158d48545e99477430fa45bb4b2682881e Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 11 Mar 2024 09:44:33 +0000 Subject: [PATCH 34/36] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 43b43b3e2..f847d63d2 100644 --- a/README.md +++ b/README.md @@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nightly Build) - + jan.exe - + Intel - + M1/M2 - + jan.deb - + jan.AppImage From 9876cf2156f1cbbadd0a227504d34927bf5153ed Mon Sep 17 00:00:00 2001 From: Arista Indrajaya Date: Mon, 11 Mar 2024 20:21:03 +0700 Subject: [PATCH 35/36] docs: redirect the install slug to /install --- docs/docusaurus.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 79d675c7a..43334c988 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -1,6 +1,5 @@ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion - require("dotenv").config(); const darkCodeTheme = require("prism-react-renderer/themes/dracula"); @@ -105,6 +104,9 @@ const config = { { from: "/troubleshooting/undefined-issue/", to: "/guides/error-codes/undefined-issue/", + }, { + from: "/install/", + to: "/guides/install/", }, ], }, From 1fac400d70400dc7aea60491b259298b308f2f30 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 11 Mar 2024 20:30:32 +0700 Subject: [PATCH 36/36] fix: replace robotjs by nutjs (#2295) (#2302) * fix: replace robotjs by nutjs (#2295) * fix: do not create quick ask window on test --- electron/main.ts | 24 +++-------------------- electron/package.json | 3 +-- electron/utils/selectedText.ts | 35 +++++++++++++++++++--------------- electron/utils/tray.ts | 24 +++++++++++++++++++++++ 4 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 electron/utils/tray.ts diff --git a/electron/main.ts b/electron/main.ts index 21f95cd00..8fe4247d6 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -27,6 +27,7 @@ import { setupReactDevTool } from './utils/dev' import { cleanLogs } from './utils/log' import { registerShortcut } from './utils/selectedText' +import { createSystemTray } from './utils/tray' const preloadPath = join(__dirname, 'preload.js') const rendererPath = join(__dirname, '..', 'renderer') @@ -48,33 +49,14 @@ app .then(setupMenu) .then(handleIPCs) .then(handleAppUpdates) - .then(createQuickAskWindow) + .then(() => process.env.CI !== 'e2e' && createQuickAskWindow()) .then(createMainWindow) .then(() => { if (!app.isPackaged) { windowManager.mainWindow?.webContents.openDevTools() } }) - .then(() => { - const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png') - const tray = new Tray(iconPath) - tray.setToolTip(app.getName()) - - const contextMenu = Menu.buildFromTemplate([ - { - label: 'Open Jan', - type: 'normal', - click: () => windowManager.showMainWindow(), - }, - { - label: 'Open Quick Ask', - type: 'normal', - click: () => windowManager.showQuickAskWindow(), - }, - { label: 'Quit', type: 'normal', click: () => app.quit() }, - ]) - tray.setContextMenu(contextMenu) - }) + .then(() => process.env.CI !== 'e2e' && createSystemTray()) .then(() => { log(`Version: ${app.getVersion()}`) }) diff --git a/electron/package.json b/electron/package.json index 93c30682c..e09e0daf2 100644 --- a/electron/package.json +++ b/electron/package.json @@ -41,7 +41,6 @@ "notarize": { "teamId": "F8AH6NHVY5" }, - "icon": "icons/icon.png" }, "linux": { @@ -92,7 +91,7 @@ "request": "^2.88.2", "request-progress": "^3.0.0", "ulid": "^2.3.0", - "@hurdlegroup/robotjs": "^0.11.4" + "@nut-tree/nut-js": "^4.0.0" }, "devDependencies": { "@electron/notarize": "^2.1.0", diff --git a/electron/utils/selectedText.ts b/electron/utils/selectedText.ts index 6b2349725..a39e331a9 100644 --- a/electron/utils/selectedText.ts +++ b/electron/utils/selectedText.ts @@ -1,19 +1,24 @@ -import { clipboard, globalShortcut } from "electron"; -import { keyTap, keys } from "@hurdlegroup/robotjs"; +import { clipboard, globalShortcut } from 'electron' +import { keyboard, Key } from '@nut-tree/nut-js' /** * Gets selected text by synthesizing the keyboard shortcut * "CommandOrControl+c" then reading text from the clipboard */ export const getSelectedText = async () => { - const currentClipboardContent = clipboard.readText(); // preserve clipboard content - clipboard.clear(); - keyTap("c" as keys, process.platform === "darwin" ? "command" : "control"); - await new Promise((resolve) => setTimeout(resolve, 200)); // add a delay before checking clipboard - const selectedText = clipboard.readText(); - clipboard.writeText(currentClipboardContent); - return selectedText; -}; + const currentClipboardContent = clipboard.readText() // preserve clipboard content + clipboard.clear() + const hotkeys: Key[] = [ + process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl, + Key.C, + ] + await keyboard.pressKey(...hotkeys) + await keyboard.releaseKey(...hotkeys) + await new Promise((resolve) => setTimeout(resolve, 200)) // add a delay before checking clipboard + const selectedText = clipboard.readText() + clipboard.writeText(currentClipboardContent) + return selectedText +} /** * Registers a global shortcut of `accelerator`. The `callback` is called @@ -26,14 +31,14 @@ export const registerShortcut = ( callback: (selectedText: string) => void ) => { return globalShortcut.register(accelerator, async () => { - callback(await getSelectedText()); - }); -}; + callback(await getSelectedText()) + }) +} /** * Unregisters a global shortcut of `accelerator` and * is equivalent to electron.globalShortcut.unregister */ export const unregisterShortcut = (accelerator: Electron.Accelerator) => { - globalShortcut.unregister(accelerator); -}; \ No newline at end of file + globalShortcut.unregister(accelerator) +} diff --git a/electron/utils/tray.ts b/electron/utils/tray.ts new file mode 100644 index 000000000..2ab3e6dcc --- /dev/null +++ b/electron/utils/tray.ts @@ -0,0 +1,24 @@ +import { join } from 'path' +import { Tray, app, Menu } from 'electron' +import { windowManager } from '../managers/window' + +export const createSystemTray = () => { + const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png') + const tray = new Tray(iconPath) + tray.setToolTip(app.getName()) + + const contextMenu = Menu.buildFromTemplate([ + { + label: 'Open Jan', + type: 'normal', + click: () => windowManager.showMainWindow(), + }, + { + label: 'Open Quick Ask', + type: 'normal', + click: () => windowManager.showQuickAskWindow(), + }, + { label: 'Quit', type: 'normal', click: () => app.quit() }, + ]) + tray.setContextMenu(contextMenu) +}