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 {} \; diff --git a/README.md b/README.md index 8a4c03098..496bbd434 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
jan.exe
Intel
M1/M2
jan.deb
jan.AppImage
@@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
jan.exe
Intel
M1/M2
jan.deb
jan.AppImage
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/",
},
],
},
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;
}
diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts
index 3f52c401e..5ea261e54 100644
--- a/electron/handlers/update.ts
+++ b/electron/handlers/update.ts
@@ -7,6 +7,7 @@ import {
autoUpdater,
} from 'electron-updater'
import { AppEvent } from '@janhq/core'
+import { trayManager } from '../managers/tray'
export let waitingToInstallVersion: string | undefined = undefined
@@ -22,6 +23,7 @@ export function handleAppUpdates() {
message: 'Would you like to download and install it now?',
buttons: ['Download', 'Later'],
})
+ trayManager.destroyCurrentTray()
if (action.response === 0) await autoUpdater.downloadUpdate()
})
diff --git a/electron/main.ts b/electron/main.ts
index 21f95cd00..78577ac68 100644
--- a/electron/main.ts
+++ b/electron/main.ts
@@ -1,4 +1,4 @@
-import { app, BrowserWindow, Menu, Tray } from 'electron'
+import { app, BrowserWindow, Tray } from 'electron'
import { join } from 'path'
/**
@@ -27,6 +27,7 @@ import { setupReactDevTool } from './utils/dev'
import { cleanLogs } from './utils/log'
import { registerShortcut } from './utils/selectedText'
+import { trayManager } from './managers/tray'
const preloadPath = join(__dirname, 'preload.js')
const rendererPath = join(__dirname, '..', 'renderer')
@@ -38,6 +39,8 @@ const quickAskUrl = `${mainUrl}/search`
const quickAskHotKey = 'CommandOrControl+J'
+const gotTheLock = app.requestSingleInstanceLock()
+
app
.whenReady()
.then(setupReactDevTool)
@@ -48,37 +51,26 @@ 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' && trayManager.createSystemTray())
.then(() => {
log(`Version: ${app.getVersion()}`)
})
.then(() => {
+ if (!gotTheLock) {
+ app.quit()
+ } else {
+ app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
+ // Someone tried to run a second instance, we should focus our window.
+ windowManager.showMainWindow()
+ })
+ }
app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) {
createMainWindow()
@@ -91,6 +83,10 @@ app.on('ready', () => {
registerGlobalShortcuts()
})
+app.on('before-quit', function (evt) {
+ trayManager.destroyCurrentTray()
+})
+
app.once('quit', () => {
cleanUpAndQuit()
})
diff --git a/electron/managers/tray.ts b/electron/managers/tray.ts
new file mode 100644
index 000000000..18661e58e
--- /dev/null
+++ b/electron/managers/tray.ts
@@ -0,0 +1,38 @@
+import { join } from 'path'
+import { Tray, app, Menu } from 'electron'
+import { windowManager } from '../managers/window'
+
+class TrayManager {
+ currentTray: Tray | undefined
+
+ createSystemTray = () => {
+ if (this.currentTray) {
+ return
+ }
+ 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)
+ }
+
+ destroyCurrentTray() {
+ this.currentTray?.destroy()
+ this.currentTray = undefined
+ }
+}
+
+export const trayManager = new TrayManager()
diff --git a/electron/managers/window.ts b/electron/managers/window.ts
index 796a5d54a..eed80c37c 100644
--- a/electron/managers/window.ts
+++ b/electron/managers/window.ts
@@ -101,6 +101,7 @@ class WindowManager {
expandQuickAskWindow(heightOffset: number): void {
const width = quickAskWindowConfig.width!
const height = quickAskWindowConfig.height! + heightOffset
+ this._quickAskWindow?.setMinimumSize(width, height)
this._quickAskWindow?.setSize(width, height, true)
}
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/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts
index 9e88e763a..11a57a598 100644
--- a/web/hooks/useSendChatMessage.ts
+++ b/web/hooks/useSendChatMessage.ts
@@ -79,6 +79,8 @@ export default function useSendChatMessage() {
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
const activeThreadRef = useRef