Merge branch 'dev' into docs-update-changelog-configuration

This commit is contained in:
Arista Indrajaya 2024-03-12 09:10:51 +07:00 committed by GitHub
commit c2c179338e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 117 additions and 57 deletions

View File

@ -7,6 +7,6 @@ if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then
fi fi
# If both variables are set, execute the following commands # 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 {} \;

View File

@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
<tr style="text-align:center"> <tr style="text-align:center">
<td style="text-align:center"><b>Stable (Recommended)</b></td> <td style="text-align:center"><b>Stable (Recommended)</b></td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-win-x64-0.4.7.exe'> <a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-win-x64-0.4.8.exe'>
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
<b>jan.exe</b> <b>jan.exe</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-x64-0.4.7.dmg'> <a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-x64-0.4.8.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" /> <img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>Intel</b> <b>Intel</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-arm64-0.4.7.dmg'> <a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-mac-arm64-0.4.8.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" /> <img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>M1/M2</b> <b>M1/M2</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-amd64-0.4.7.deb'> <a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-amd64-0.4.8.deb'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.deb</b> <b>jan.deb</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-x86_64-0.4.7.AppImage'> <a href='https://github.com/janhq/jan/releases/download/v0.4.8/jan-linux-x86_64-0.4.8.AppImage'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.AppImage</b> <b>jan.AppImage</b>
</a> </a>
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
<tr style="text-align:center"> <tr style="text-align:center">
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td> <td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.7-304.exe'> <a href='https://delta.jan.ai/latest/jan-win-x64-0.4.8-312.exe'>
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
<b>jan.exe</b> <b>jan.exe</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.7-304.dmg'> <a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.8-312.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" /> <img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>Intel</b> <b>Intel</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.7-304.dmg'> <a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.8-312.dmg'>
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" /> <img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
<b>M1/M2</b> <b>M1/M2</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.7-304.deb'> <a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.8-312.deb'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.deb</b> <b>jan.deb</b>
</a> </a>
</td> </td>
<td style="text-align:center"> <td style="text-align:center">
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.7-304.AppImage'> <a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.8-312.AppImage'>
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" /> <img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
<b>jan.AppImage</b> <b>jan.AppImage</b>
</a> </a>

View File

@ -1,6 +1,5 @@
// @ts-check // @ts-check
// Note: type annotations allow type checking and IDEs autocompletion // Note: type annotations allow type checking and IDEs autocompletion
require("dotenv").config(); require("dotenv").config();
const darkCodeTheme = require("prism-react-renderer/themes/dracula"); const darkCodeTheme = require("prism-react-renderer/themes/dracula");
@ -105,6 +104,9 @@ const config = {
{ {
from: "/troubleshooting/undefined-issue/", from: "/troubleshooting/undefined-issue/",
to: "/guides/error-codes/undefined-issue/", to: "/guides/error-codes/undefined-issue/",
}, {
from: "/install/",
to: "/guides/install/",
}, },
], ],
}, },

View File

@ -33,16 +33,24 @@
/* Dark mode styles based on Docusaurus dark theme */ /* Dark mode styles based on Docusaurus dark theme */
[data-theme='dark'] .head_Menu div { [data-theme='dark'] .head_Menu div {
font-weight: bold;
background-color: var(--ifm-background-color); background-color: var(--ifm-background-color);
color: var(--ifm-font-color-base); color: var(--ifm-font-color-base);
margin-left: 0.7rem;
font-size: larger;
} }
[data-theme='dark'] .head_Menu li { [data-theme='dark'] .head_Menu li {
font-weight: normal;
background-color: var(--ifm-background-color); background-color: var(--ifm-background-color);
margin-bottom: 5px;
color: var(--ifm-font-color-base); color: var(--ifm-font-color-base);
} }
[data-theme='dark'] .head_SubMenu div { [data-theme='dark'] .head_SubMenu div {
font-weight: normal;
background-color: var(--ifm-background-color); background-color: var(--ifm-background-color);
color: var(--ifm-font-color-base); color: var(--ifm-font-color-base);
margin-left: 0rem;
font-size: medium;
} }

View File

@ -7,6 +7,7 @@ import {
autoUpdater, autoUpdater,
} from 'electron-updater' } from 'electron-updater'
import { AppEvent } from '@janhq/core' import { AppEvent } from '@janhq/core'
import { trayManager } from '../managers/tray'
export let waitingToInstallVersion: string | undefined = undefined export let waitingToInstallVersion: string | undefined = undefined
@ -22,6 +23,7 @@ export function handleAppUpdates() {
message: 'Would you like to download and install it now?', message: 'Would you like to download and install it now?',
buttons: ['Download', 'Later'], buttons: ['Download', 'Later'],
}) })
trayManager.destroyCurrentTray()
if (action.response === 0) await autoUpdater.downloadUpdate() if (action.response === 0) await autoUpdater.downloadUpdate()
}) })

View File

@ -1,4 +1,4 @@
import { app, BrowserWindow, Menu, Tray } from 'electron' import { app, BrowserWindow, Tray } from 'electron'
import { join } from 'path' import { join } from 'path'
/** /**
@ -27,6 +27,7 @@ import { setupReactDevTool } from './utils/dev'
import { cleanLogs } from './utils/log' import { cleanLogs } from './utils/log'
import { registerShortcut } from './utils/selectedText' import { registerShortcut } from './utils/selectedText'
import { trayManager } from './managers/tray'
const preloadPath = join(__dirname, 'preload.js') const preloadPath = join(__dirname, 'preload.js')
const rendererPath = join(__dirname, '..', 'renderer') const rendererPath = join(__dirname, '..', 'renderer')
@ -38,6 +39,8 @@ const quickAskUrl = `${mainUrl}/search`
const quickAskHotKey = 'CommandOrControl+J' const quickAskHotKey = 'CommandOrControl+J'
const gotTheLock = app.requestSingleInstanceLock()
app app
.whenReady() .whenReady()
.then(setupReactDevTool) .then(setupReactDevTool)
@ -48,37 +51,26 @@ app
.then(setupMenu) .then(setupMenu)
.then(handleIPCs) .then(handleIPCs)
.then(handleAppUpdates) .then(handleAppUpdates)
.then(createQuickAskWindow) .then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
.then(createMainWindow) .then(createMainWindow)
.then(() => { .then(() => {
if (!app.isPackaged) { if (!app.isPackaged) {
windowManager.mainWindow?.webContents.openDevTools() windowManager.mainWindow?.webContents.openDevTools()
} }
}) })
.then(() => { .then(() => process.env.CI !== 'e2e' && trayManager.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)
})
.then(() => { .then(() => {
log(`Version: ${app.getVersion()}`) log(`Version: ${app.getVersion()}`)
}) })
.then(() => { .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', () => { app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) { if (!BrowserWindow.getAllWindows().length) {
createMainWindow() createMainWindow()
@ -91,6 +83,10 @@ app.on('ready', () => {
registerGlobalShortcuts() registerGlobalShortcuts()
}) })
app.on('before-quit', function (evt) {
trayManager.destroyCurrentTray()
})
app.once('quit', () => { app.once('quit', () => {
cleanUpAndQuit() cleanUpAndQuit()
}) })

38
electron/managers/tray.ts Normal file
View File

@ -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()

View File

@ -101,6 +101,7 @@ class WindowManager {
expandQuickAskWindow(heightOffset: number): void { expandQuickAskWindow(heightOffset: number): void {
const width = quickAskWindowConfig.width! const width = quickAskWindowConfig.width!
const height = quickAskWindowConfig.height! + heightOffset const height = quickAskWindowConfig.height! + heightOffset
this._quickAskWindow?.setMinimumSize(width, height)
this._quickAskWindow?.setSize(width, height, true) this._quickAskWindow?.setSize(width, height, true)
} }

View File

@ -41,7 +41,6 @@
"notarize": { "notarize": {
"teamId": "F8AH6NHVY5" "teamId": "F8AH6NHVY5"
}, },
"icon": "icons/icon.png" "icon": "icons/icon.png"
}, },
"linux": { "linux": {
@ -92,7 +91,7 @@
"request": "^2.88.2", "request": "^2.88.2",
"request-progress": "^3.0.0", "request-progress": "^3.0.0",
"ulid": "^2.3.0", "ulid": "^2.3.0",
"@hurdlegroup/robotjs": "^0.11.4" "@nut-tree/nut-js": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@electron/notarize": "^2.1.0", "@electron/notarize": "^2.1.0",

View File

@ -1,19 +1,24 @@
import { clipboard, globalShortcut } from "electron"; import { clipboard, globalShortcut } from 'electron'
import { keyTap, keys } from "@hurdlegroup/robotjs"; import { keyboard, Key } from '@nut-tree/nut-js'
/** /**
* Gets selected text by synthesizing the keyboard shortcut * Gets selected text by synthesizing the keyboard shortcut
* "CommandOrControl+c" then reading text from the clipboard * "CommandOrControl+c" then reading text from the clipboard
*/ */
export const getSelectedText = async () => { export const getSelectedText = async () => {
const currentClipboardContent = clipboard.readText(); // preserve clipboard content const currentClipboardContent = clipboard.readText() // preserve clipboard content
clipboard.clear(); clipboard.clear()
keyTap("c" as keys, process.platform === "darwin" ? "command" : "control"); const hotkeys: Key[] = [
await new Promise((resolve) => setTimeout(resolve, 200)); // add a delay before checking clipboard process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl,
const selectedText = clipboard.readText(); Key.C,
clipboard.writeText(currentClipboardContent); ]
return selectedText; 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 * Registers a global shortcut of `accelerator`. The `callback` is called
@ -26,14 +31,14 @@ export const registerShortcut = (
callback: (selectedText: string) => void callback: (selectedText: string) => void
) => { ) => {
return globalShortcut.register(accelerator, async () => { return globalShortcut.register(accelerator, async () => {
callback(await getSelectedText()); callback(await getSelectedText())
}); })
}; }
/** /**
* Unregisters a global shortcut of `accelerator` and * Unregisters a global shortcut of `accelerator` and
* is equivalent to electron.globalShortcut.unregister * is equivalent to electron.globalShortcut.unregister
*/ */
export const unregisterShortcut = (accelerator: Electron.Accelerator) => { export const unregisterShortcut = (accelerator: Electron.Accelerator) => {
globalShortcut.unregister(accelerator); globalShortcut.unregister(accelerator)
}; }

View File

@ -79,6 +79,8 @@ export default function useSendChatMessage() {
const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom) const setIsGeneratingResponse = useSetAtom(isGeneratingResponseAtom)
const activeThreadRef = useRef<Thread | undefined>() const activeThreadRef = useRef<Thread | undefined>()
const selectedModelRef = useRef<Model | undefined>()
useEffect(() => { useEffect(() => {
modelRef.current = activeModel modelRef.current = activeModel
}, [activeModel]) }, [activeModel])
@ -91,6 +93,10 @@ export default function useSendChatMessage() {
activeThreadRef.current = activeThread activeThreadRef.current = activeThread
}, [activeThread]) }, [activeThread])
useEffect(() => {
selectedModelRef.current = selectedModel
}, [selectedModel])
const resendChatMessage = async (currentMessage: ThreadMessage) => { const resendChatMessage = async (currentMessage: ThreadMessage) => {
if (!activeThreadRef.current) { if (!activeThreadRef.current) {
console.error('No active thread') console.error('No active thread')
@ -128,11 +134,13 @@ export default function useSendChatMessage() {
type: MessageRequestType.Thread, type: MessageRequestType.Thread,
messages: messages, messages: messages,
threadId: activeThreadRef.current.id, threadId: activeThreadRef.current.id,
model: activeThreadRef.current.assistants[0].model ?? selectedModel, model:
activeThreadRef.current.assistants[0].model ?? selectedModelRef.current,
} }
const modelId = const modelId =
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id selectedModelRef.current?.id ??
activeThreadRef.current.assistants[0].model.id
if (modelRef.current?.id !== modelId) { if (modelRef.current?.id !== modelId) {
setQueuedMessage(true) setQueuedMessage(true)
@ -213,7 +221,7 @@ export default function useSendChatMessage() {
{ {
role: ChatCompletionRole.User, role: ChatCompletionRole.User,
content: content:
selectedModel && base64Blob selectedModelRef.current && base64Blob
? [ ? [
{ {
type: ChatCompletionMessageContentType.Text, type: ChatCompletionMessageContentType.Text,
@ -242,7 +250,7 @@ export default function useSendChatMessage() {
) )
let modelRequest = let modelRequest =
selectedModel ?? activeThreadRef.current.assistants[0].model selectedModelRef?.current ?? activeThreadRef.current.assistants[0].model
if (runtimeParams.stream == null) { if (runtimeParams.stream == null) {
runtimeParams.stream = true runtimeParams.stream = true
} }
@ -344,7 +352,8 @@ export default function useSendChatMessage() {
?.addNewMessage(threadMessage) ?.addNewMessage(threadMessage)
const modelId = const modelId =
selectedModel?.id ?? activeThreadRef.current.assistants[0].model.id selectedModelRef.current?.id ??
activeThreadRef.current.assistants[0].model.id
if (modelRef.current?.id !== modelId) { if (modelRef.current?.id !== modelId) {
setQueuedMessage(true) setQueuedMessage(true)