fix: Jan Quick Ask window capture input issues (#4758)
This commit is contained in:
parent
b4ba76aa71
commit
dda0feb548
@ -31,6 +31,7 @@ import { registerGlobalShortcuts } from './utils/shortcut'
|
||||
import { registerLogger } from './utils/logger'
|
||||
|
||||
const preloadPath = join(__dirname, 'preload.js')
|
||||
const preloadQuickAskPath = join(__dirname, 'preload.quickask.js')
|
||||
const rendererPath = join(__dirname, '..', 'renderer')
|
||||
const quickAskPath = join(rendererPath, 'search.html')
|
||||
const mainPath = join(rendererPath, 'index.html')
|
||||
@ -133,7 +134,7 @@ function createQuickAskWindow() {
|
||||
// Feature Toggle for Quick Ask
|
||||
if (!getAppConfigurations().quick_ask) return
|
||||
const startUrl = app.isPackaged ? `file://${quickAskPath}` : quickAskUrl
|
||||
windowManager.createQuickAskWindow(preloadPath, startUrl)
|
||||
windowManager.createQuickAskWindow(preloadQuickAskPath, startUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -13,10 +13,10 @@ export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||
fullscreenable: false,
|
||||
resizable: false,
|
||||
center: true,
|
||||
movable: false,
|
||||
movable: true,
|
||||
maximizable: false,
|
||||
focusable: true,
|
||||
transparent: true,
|
||||
transparent: false,
|
||||
frame: false,
|
||||
type: 'panel',
|
||||
}
|
||||
|
||||
@ -141,6 +141,9 @@ class WindowManager {
|
||||
return this._quickAskWindow?.isDestroyed() ?? true
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the quick ask window
|
||||
*/
|
||||
expandQuickAskWindow(heightOffset: number): void {
|
||||
const width = quickAskWindowConfig.width!
|
||||
const height = quickAskWindowConfig.height! + heightOffset
|
||||
@ -148,6 +151,9 @@ class WindowManager {
|
||||
this._quickAskWindow?.setSize(width, height, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the selected text to the quick ask window.
|
||||
*/
|
||||
sendQuickAskSelectedText(selectedText: string): void {
|
||||
this._quickAskWindow?.webContents.send(
|
||||
AppEvent.onSelectedText,
|
||||
@ -180,6 +186,9 @@ class WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all windows.
|
||||
*/
|
||||
cleanUp(): void {
|
||||
if (!this.mainWindow?.isDestroyed()) {
|
||||
this.mainWindow?.close()
|
||||
|
||||
32
electron/preload.quickask.ts
Normal file
32
electron/preload.quickask.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Exposes a set of APIs to the renderer process via the contextBridge object.
|
||||
* @module preload
|
||||
*/
|
||||
|
||||
import { APIEvents, APIRoutes } from '@janhq/core/node'
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
const interfaces: { [key: string]: (...args: any[]) => any } = {}
|
||||
|
||||
// Loop over each route in APIRoutes
|
||||
APIRoutes.forEach((method) => {
|
||||
// For each method, create a function on the interfaces object
|
||||
// This function invokes the method on the ipcRenderer with any provided arguments
|
||||
|
||||
interfaces[method] = (...args: any[]) => ipcRenderer.invoke(method, ...args)
|
||||
})
|
||||
|
||||
// Loop over each method in APIEvents
|
||||
APIEvents.forEach((method) => {
|
||||
// For each method, create a function on the interfaces object
|
||||
// This function sets up an event listener on the ipcRenderer for the method
|
||||
// The handler for the event is provided as an argument to the function
|
||||
interfaces[method] = (handler: any) => ipcRenderer.on(method, handler)
|
||||
})
|
||||
|
||||
// Expose the 'interfaces' object in the main world under the name 'electronAPI'
|
||||
// This allows the renderer process to access these methods directly
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
...interfaces,
|
||||
isQuickAsk: () => true,
|
||||
})
|
||||
@ -3,7 +3,7 @@
|
||||
* @module preload
|
||||
*/
|
||||
|
||||
import { APIEvents, APIRoutes, AppConfiguration, getAppConfigurations, updateAppConfiguration } from '@janhq/core/node'
|
||||
import { APIEvents, APIRoutes, AppConfiguration } from '@janhq/core/node'
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { readdirSync } from 'fs'
|
||||
|
||||
@ -13,9 +13,8 @@ const interfaces: { [key: string]: (...args: any[]) => any } = {}
|
||||
APIRoutes.forEach((method) => {
|
||||
// For each method, create a function on the interfaces object
|
||||
// This function invokes the method on the ipcRenderer with any provided arguments
|
||||
|
||||
|
||||
interfaces[method] = (...args: any[]) => ipcRenderer.invoke(method, ...args)
|
||||
|
||||
})
|
||||
|
||||
// Loop over each method in APIEvents
|
||||
@ -26,20 +25,21 @@ APIEvents.forEach((method) => {
|
||||
interfaces[method] = (handler: any) => ipcRenderer.on(method, handler)
|
||||
})
|
||||
|
||||
|
||||
interfaces['changeDataFolder'] = async path => {
|
||||
const appConfiguration: AppConfiguration = await ipcRenderer.invoke('getAppConfigurations')
|
||||
interfaces['changeDataFolder'] = async (path) => {
|
||||
const appConfiguration: AppConfiguration = await ipcRenderer.invoke(
|
||||
'getAppConfigurations'
|
||||
)
|
||||
const currentJanDataFolder = appConfiguration.data_folder
|
||||
appConfiguration.data_folder = path
|
||||
const reflect = require('@alumna/reflect')
|
||||
const { err } = await reflect({
|
||||
src: currentJanDataFolder,
|
||||
dest: path,
|
||||
recursive: true,
|
||||
delete: false,
|
||||
overwrite: true,
|
||||
errorOnExist: false,
|
||||
})
|
||||
src: currentJanDataFolder,
|
||||
dest: path,
|
||||
recursive: true,
|
||||
delete: false,
|
||||
overwrite: true,
|
||||
errorOnExist: false,
|
||||
})
|
||||
if (err) {
|
||||
console.error(err)
|
||||
throw err
|
||||
@ -47,7 +47,7 @@ interfaces['changeDataFolder'] = async path => {
|
||||
await ipcRenderer.invoke('updateAppConfiguration', appConfiguration)
|
||||
}
|
||||
|
||||
interfaces['isDirectoryEmpty'] = async path => {
|
||||
interfaces['isDirectoryEmpty'] = async (path) => {
|
||||
const dirChildren = await readdirSync(path)
|
||||
return dirChildren.filter((x) => x !== '.DS_Store').length === 0
|
||||
}
|
||||
@ -56,4 +56,5 @@ interfaces['isDirectoryEmpty'] = async path => {
|
||||
// This allows the renderer process to access these methods directly
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
...interfaces,
|
||||
isQuickAsk: () => false,
|
||||
})
|
||||
|
||||
@ -13,10 +13,7 @@ export const metadata: Metadata = {
|
||||
export default function RootLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="h-screen font-sans text-sm antialiased">
|
||||
<div className="dragable-bar" />
|
||||
{children}
|
||||
</body>
|
||||
<body className="h-screen font-sans text-sm antialiased">{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ const UserInput = () => {
|
||||
<LogoMark width={28} height={28} className="mx-auto" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
className="flex-1 bg-transparent font-bold focus:outline-none"
|
||||
className="flex-1 bg-transparent font-bold text-[hsla(var(--text-primary))] focus:outline-none"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
|
||||
@ -8,9 +8,6 @@ import { useSetAtom } from 'jotai'
|
||||
|
||||
import ClipboardListener from '@/containers/Providers/ClipboardListener'
|
||||
|
||||
import JotaiWrapper from '@/containers/Providers/Jotai'
|
||||
import ThemeWrapper from '@/containers/Providers/Theme'
|
||||
|
||||
import { useLoadTheme } from '@/hooks/useLoadTheme'
|
||||
|
||||
import { setupCoreServices } from '@/services/coreService'
|
||||
@ -48,15 +45,9 @@ export default function RootLayout() {
|
||||
useLoadTheme()
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body className="font-sans antialiased">
|
||||
<JotaiWrapper>
|
||||
<ThemeWrapper>
|
||||
<ClipboardListener />
|
||||
<Search />
|
||||
</ThemeWrapper>
|
||||
</JotaiWrapper>
|
||||
</body>
|
||||
</html>
|
||||
<>
|
||||
<ClipboardListener />
|
||||
<Search />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import UserInput from './UserInput'
|
||||
const Search = () => {
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden bg-[hsla(var(--app-bg))]">
|
||||
<div className={'draggable-bar h-[10px]'} />
|
||||
<UserInput />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -35,10 +35,7 @@ import useDownloadModel from '@/hooks/useDownloadModel'
|
||||
import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
|
||||
import { useGetEngines } from '@/hooks/useEngineManagement'
|
||||
|
||||
import {
|
||||
useGetModelSources,
|
||||
useGetFeaturedSources,
|
||||
} from '@/hooks/useModelSource'
|
||||
import { useGetFeaturedSources } from '@/hooks/useModelSource'
|
||||
import useRecommendedModel from '@/hooks/useRecommendedModel'
|
||||
|
||||
import useUpdateModelParameters from '@/hooks/useUpdateModelParameters'
|
||||
@ -91,7 +88,6 @@ const ModelDropdown = ({
|
||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom)
|
||||
const { recommendedModel, downloadedModels } = useRecommendedModel()
|
||||
const { sources } = useGetModelSources()
|
||||
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
||||
null
|
||||
)
|
||||
|
||||
17
web/containers/Providers/QuickAskConfigurator.tsx
Normal file
17
web/containers/Providers/QuickAskConfigurator.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import { PropsWithChildren, useEffect, useState } from 'react'
|
||||
|
||||
import { setupCoreServices } from '@/services/coreService'
|
||||
|
||||
export const QuickAskConfigurator = ({ children }: PropsWithChildren) => {
|
||||
const [setupCore, setSetupCore] = useState(false)
|
||||
|
||||
// Services Setup
|
||||
useEffect(() => {
|
||||
setupCoreServices()
|
||||
setSetupCore(true)
|
||||
}, [])
|
||||
|
||||
return <>{setupCore && <>{children}</>}</>
|
||||
}
|
||||
@ -14,28 +14,39 @@ import DataLoader from './DataLoader'
|
||||
|
||||
import DeepLinkListener from './DeepLinkListener'
|
||||
import KeyListener from './KeyListener'
|
||||
import { QuickAskConfigurator } from './QuickAskConfigurator'
|
||||
import Responsive from './Responsive'
|
||||
|
||||
import SWRConfigProvider from './SWRConfigProvider'
|
||||
import SettingsHandler from './SettingsHandler'
|
||||
|
||||
const Providers = ({ children }: PropsWithChildren) => {
|
||||
const isQuickAsk =
|
||||
typeof window !== 'undefined' && window.electronAPI?.isQuickAsk()
|
||||
return (
|
||||
<SWRConfigProvider>
|
||||
<ThemeWrapper>
|
||||
<JotaiWrapper>
|
||||
<CoreConfigurator>
|
||||
{isQuickAsk && (
|
||||
<>
|
||||
<Responsive />
|
||||
<KeyListener />
|
||||
<EventListener />
|
||||
<DataLoader />
|
||||
<SettingsHandler />
|
||||
<DeepLinkListener />
|
||||
<Toaster />
|
||||
{children}
|
||||
<QuickAskConfigurator> {children} </QuickAskConfigurator>
|
||||
</>
|
||||
</CoreConfigurator>
|
||||
)}
|
||||
{!isQuickAsk && (
|
||||
<CoreConfigurator>
|
||||
<>
|
||||
<Responsive />
|
||||
<KeyListener />
|
||||
<EventListener />
|
||||
<DataLoader />
|
||||
<SettingsHandler />
|
||||
<DeepLinkListener />
|
||||
<Toaster />
|
||||
<div className={'draggable-bar h-[32px]'} />
|
||||
{children}
|
||||
</>
|
||||
</CoreConfigurator>
|
||||
)}
|
||||
</JotaiWrapper>
|
||||
</ThemeWrapper>
|
||||
</SWRConfigProvider>
|
||||
|
||||
10
web/hooks/useApp.ts
Normal file
10
web/hooks/useApp.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { extensionManager } from '@/extension'
|
||||
|
||||
export function useApp() {
|
||||
async function relaunch() {
|
||||
const extensions = extensionManager.getAll()
|
||||
await Promise.all(extensions.map((e) => e.onUnload()))
|
||||
window.core?.api?.relaunch()
|
||||
}
|
||||
return { relaunch }
|
||||
}
|
||||
@ -43,7 +43,7 @@ export function useGetFeaturedSources() {
|
||||
const { sources, error, mutate } = useGetModelSources()
|
||||
|
||||
return {
|
||||
sources: sources?.filter((e) => e.metadata?.tags.includes('featured')),
|
||||
sources: sources?.filter((e) => e.metadata?.tags?.includes('featured')),
|
||||
error,
|
||||
mutate,
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import Loader from '@/containers/Loader'
|
||||
|
||||
export const SUCCESS_SET_NEW_DESTINATION = 'successSetNewDestination'
|
||||
|
||||
import { useApp } from '@/hooks/useApp'
|
||||
|
||||
import ModalChangeDirectory, {
|
||||
showDirectoryConfirmModalAtom,
|
||||
} from './ModalChangeDirectory'
|
||||
@ -32,6 +34,7 @@ const DataFolder = () => {
|
||||
|
||||
const [destinationPath, setDestinationPath] = useState(undefined)
|
||||
const janDataFolderPath = useAtomValue(janDataFolderPathAtom)
|
||||
const { relaunch } = useApp()
|
||||
|
||||
const onChangeFolderClick = useCallback(async () => {
|
||||
const destFolder = await window.core?.api?.selectDirectory()
|
||||
@ -78,7 +81,7 @@ const DataFolder = () => {
|
||||
setTimeout(() => {
|
||||
setShowLoader(false)
|
||||
}, 1200)
|
||||
await window.core?.api?.relaunch()
|
||||
await relaunch()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setShowLoader(false)
|
||||
|
||||
@ -13,6 +13,7 @@ import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
import { toaster } from '@/containers/Toast'
|
||||
|
||||
import { useApp } from '@/hooks/useApp'
|
||||
import { useConfigurations } from '@/hooks/useConfigurations'
|
||||
|
||||
import ModalDeleteAllThreads from '@/screens/Thread/ThreadLeftPanel/ModalDeleteAllThreads'
|
||||
@ -47,6 +48,7 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => {
|
||||
const { configurePullOptions } = useConfigurations()
|
||||
|
||||
const setModalActionThread = useSetAtom(modalActionThreadAtom)
|
||||
const { relaunch } = useApp()
|
||||
|
||||
/**
|
||||
* There could be a case where the state update is not synced
|
||||
@ -66,13 +68,13 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => {
|
||||
*/
|
||||
const updateQuickAskEnabled = async (
|
||||
e: boolean,
|
||||
relaunch: boolean = true
|
||||
relaunchApp: boolean = true
|
||||
) => {
|
||||
const appConfiguration: AppConfiguration =
|
||||
await window.core?.api?.getAppConfigurations()
|
||||
appConfiguration.quick_ask = e
|
||||
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
||||
if (relaunch) window.core?.api?.relaunch()
|
||||
if (relaunchApp) relaunch()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +94,7 @@ const Advanced = ({ setSubdir }: { setSubdir: (subdir: string) => void }) => {
|
||||
// It affects other settings, so we need to reset them
|
||||
const isRelaunch = quickAskEnabled
|
||||
if (quickAskEnabled) await updateQuickAskEnabled(false, false)
|
||||
if (isRelaunch) window.core?.api?.relaunch()
|
||||
if (isRelaunch) relaunch()
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -9,6 +9,8 @@ import { Marked, Renderer } from 'marked'
|
||||
|
||||
import Loader from '@/containers/Loader'
|
||||
|
||||
import { useApp } from '@/hooks/useApp'
|
||||
|
||||
import { formatExtensionsName } from '@/utils/converter'
|
||||
|
||||
import { extensionManager } from '@/extension'
|
||||
@ -23,6 +25,7 @@ const ExtensionCatalog = () => {
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [showLoading, setShowLoading] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null)
|
||||
const { relaunch } = useApp()
|
||||
|
||||
useEffect(() => {
|
||||
const getAllSettings = async () => {
|
||||
@ -74,7 +77,7 @@ const ExtensionCatalog = () => {
|
||||
// Send the filename of the to be installed extension
|
||||
// to the main process for installation
|
||||
const installed = await extensionManager.install([extensionFile])
|
||||
if (installed) window.core?.api?.relaunch()
|
||||
if (installed) relaunch()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +90,7 @@ const ExtensionCatalog = () => {
|
||||
// Send the filename of the to be uninstalled extension
|
||||
// to the main process for removal
|
||||
const res = await extensionManager.uninstall([name])
|
||||
if (res) window.core?.api?.relaunch()
|
||||
if (res) relaunch()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -7,18 +7,18 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dragable-bar {
|
||||
.draggable-bar {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.unset-drag {
|
||||
user-select: inherit;
|
||||
-webkit-app-region: no-drag;
|
||||
pointer-events: all; /* Ensure it receives input events */
|
||||
}
|
||||
|
||||
.unselect {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user