mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 04:42:39 +02:00
feat: add general assist feature and enhance shortcut management
- Introduced a new "General Assist" shortcut, allowing users to open SurfSense from anywhere. - Updated shortcut management to include the new general assist functionality in both the desktop and web applications. - Enhanced the UI to reflect changes in shortcut labels and descriptions for better clarity. - Improved the Electron API to support the new shortcut configuration.
This commit is contained in:
parent
e574b5ec4a
commit
27e9e8d873
10 changed files with 159 additions and 33 deletions
|
|
@ -19,6 +19,9 @@ files:
|
||||||
- "!scripts"
|
- "!scripts"
|
||||||
- "!release"
|
- "!release"
|
||||||
extraResources:
|
extraResources:
|
||||||
|
- from: assets/
|
||||||
|
to: assets/
|
||||||
|
filter: ["*.ico", "*.png", "*.icns"]
|
||||||
- from: ../surfsense_web/.next/standalone/surfsense_web/
|
- from: ../surfsense_web/.next/standalone/surfsense_web/
|
||||||
to: standalone/
|
to: standalone/
|
||||||
filter:
|
filter:
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import {
|
||||||
import { getShortcuts, setShortcuts, type ShortcutConfig } from '../modules/shortcuts';
|
import { getShortcuts, setShortcuts, type ShortcutConfig } from '../modules/shortcuts';
|
||||||
import { reregisterQuickAsk } from '../modules/quick-ask';
|
import { reregisterQuickAsk } from '../modules/quick-ask';
|
||||||
import { reregisterAutocomplete } from '../modules/autocomplete';
|
import { reregisterAutocomplete } from '../modules/autocomplete';
|
||||||
|
import { reregisterGeneralAssist } from '../modules/tray';
|
||||||
|
|
||||||
let authTokens: { bearer: string; refresh: string } | null = null;
|
let authTokens: { bearer: string; refresh: string } | null = null;
|
||||||
|
|
||||||
|
|
@ -107,6 +108,7 @@ export function registerIpcHandlers(): void {
|
||||||
|
|
||||||
ipcMain.handle(IPC_CHANNELS.SET_SHORTCUTS, async (_event, config: Partial<ShortcutConfig>) => {
|
ipcMain.handle(IPC_CHANNELS.SET_SHORTCUTS, async (_event, config: Partial<ShortcutConfig>) => {
|
||||||
const updated = await setShortcuts(config);
|
const updated = await setShortcuts(config);
|
||||||
|
if (config.generalAssist) await reregisterGeneralAssist();
|
||||||
if (config.quickAsk) await reregisterQuickAsk();
|
if (config.quickAsk) await reregisterQuickAsk();
|
||||||
if (config.autocomplete) await reregisterAutocomplete();
|
if (config.autocomplete) await reregisterAutocomplete();
|
||||||
return updated;
|
return updated;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { app, BrowserWindow } from 'electron';
|
import { app, BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
let isQuitting = false;
|
||||||
import { registerGlobalErrorHandlers, showErrorDialog } from './modules/errors';
|
import { registerGlobalErrorHandlers, showErrorDialog } from './modules/errors';
|
||||||
import { startNextServer } from './modules/server';
|
import { startNextServer } from './modules/server';
|
||||||
import { createMainWindow } from './modules/window';
|
import { createMainWindow, getMainWindow } from './modules/window';
|
||||||
import { setupDeepLinks, handlePendingDeepLink } from './modules/deep-links';
|
import { setupDeepLinks, handlePendingDeepLink } from './modules/deep-links';
|
||||||
import { setupAutoUpdater } from './modules/auto-updater';
|
import { setupAutoUpdater } from './modules/auto-updater';
|
||||||
import { setupMenu } from './modules/menu';
|
import { setupMenu } from './modules/menu';
|
||||||
|
|
@ -9,6 +11,7 @@ import { registerQuickAsk, unregisterQuickAsk } from './modules/quick-ask';
|
||||||
import { registerAutocomplete, unregisterAutocomplete } from './modules/autocomplete';
|
import { registerAutocomplete, unregisterAutocomplete } from './modules/autocomplete';
|
||||||
import { registerFolderWatcher, unregisterFolderWatcher } from './modules/folder-watcher';
|
import { registerFolderWatcher, unregisterFolderWatcher } from './modules/folder-watcher';
|
||||||
import { registerIpcHandlers } from './ipc/handlers';
|
import { registerIpcHandlers } from './ipc/handlers';
|
||||||
|
import { createTray, destroyTray } from './modules/tray';
|
||||||
|
|
||||||
registerGlobalErrorHandlers();
|
registerGlobalErrorHandlers();
|
||||||
|
|
||||||
|
|
@ -28,7 +31,18 @@ app.whenReady().then(async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createMainWindow('/dashboard');
|
await createTray();
|
||||||
|
|
||||||
|
const win = createMainWindow('/dashboard');
|
||||||
|
|
||||||
|
// Minimize to tray instead of closing the app
|
||||||
|
win.on('close', (e) => {
|
||||||
|
if (!isQuitting) {
|
||||||
|
e.preventDefault();
|
||||||
|
win.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await registerQuickAsk();
|
await registerQuickAsk();
|
||||||
await registerAutocomplete();
|
await registerAutocomplete();
|
||||||
registerFolderWatcher();
|
registerFolderWatcher();
|
||||||
|
|
@ -37,20 +51,28 @@ app.whenReady().then(async () => {
|
||||||
handlePendingDeepLink();
|
handlePendingDeepLink();
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
const mw = getMainWindow();
|
||||||
|
if (!mw || mw.isDestroyed()) {
|
||||||
createMainWindow('/dashboard');
|
createMainWindow('/dashboard');
|
||||||
|
} else {
|
||||||
|
mw.show();
|
||||||
|
mw.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keep running in the background — the tray "Quit" calls app.exit()
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
// Do nothing: the app stays alive in the tray
|
||||||
app.quit();
|
});
|
||||||
}
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
isQuitting = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('will-quit', () => {
|
app.on('will-quit', () => {
|
||||||
unregisterQuickAsk();
|
unregisterQuickAsk();
|
||||||
unregisterAutocomplete();
|
unregisterAutocomplete();
|
||||||
unregisterFolderWatcher();
|
unregisterFolderWatcher();
|
||||||
|
destroyTray();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ async function quickAskHandler(): Promise<void> {
|
||||||
const text = selected || savedClipboard.trim();
|
const text = selected || savedClipboard.trim();
|
||||||
|
|
||||||
sourceApp = getFrontmostApp();
|
sourceApp = getFrontmostApp();
|
||||||
console.log('[quick-ask] Source app:', sourceApp, '| Opening Quick Ask with', text.length, 'chars', selected ? '(selected)' : text ? '(clipboard fallback)' : '(empty)');
|
console.log('[quick-ask] Source app:', sourceApp, '| Opening Quick Assist with', text.length, 'chars', selected ? '(selected)' : text ? '(clipboard fallback)' : '(empty)');
|
||||||
openQuickAsk(text);
|
openQuickAsk(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
export interface ShortcutConfig {
|
export interface ShortcutConfig {
|
||||||
|
generalAssist: string;
|
||||||
quickAsk: string;
|
quickAsk: string;
|
||||||
autocomplete: string;
|
autocomplete: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULTS: ShortcutConfig = {
|
const DEFAULTS: ShortcutConfig = {
|
||||||
|
generalAssist: 'CommandOrControl+Shift+S',
|
||||||
quickAsk: 'CommandOrControl+Alt+S',
|
quickAsk: 'CommandOrControl+Alt+S',
|
||||||
autocomplete: 'CommandOrControl+Shift+Space',
|
autocomplete: 'CommandOrControl+Shift+Space',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
77
surfsense_desktop/src/modules/tray.ts
Normal file
77
surfsense_desktop/src/modules/tray.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { app, globalShortcut, Menu, nativeImage, Tray } from 'electron';
|
||||||
|
import path from 'path';
|
||||||
|
import { getMainWindow, createMainWindow } from './window';
|
||||||
|
import { getShortcuts } from './shortcuts';
|
||||||
|
|
||||||
|
let tray: Tray | null = null;
|
||||||
|
let currentShortcut: string | null = null;
|
||||||
|
|
||||||
|
function getTrayIcon(): nativeImage {
|
||||||
|
const iconName = process.platform === 'win32' ? 'icon.ico' : 'icon.png';
|
||||||
|
const iconPath = app.isPackaged
|
||||||
|
? path.join(process.resourcesPath, 'assets', iconName)
|
||||||
|
: path.join(__dirname, '..', 'assets', iconName);
|
||||||
|
const img = nativeImage.createFromPath(iconPath);
|
||||||
|
return img.resize({ width: 16, height: 16 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMainWindow(): void {
|
||||||
|
let win = getMainWindow();
|
||||||
|
if (!win || win.isDestroyed()) {
|
||||||
|
win = createMainWindow('/dashboard');
|
||||||
|
} else {
|
||||||
|
win.show();
|
||||||
|
win.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerShortcut(accelerator: string): void {
|
||||||
|
if (currentShortcut) {
|
||||||
|
globalShortcut.unregister(currentShortcut);
|
||||||
|
currentShortcut = null;
|
||||||
|
}
|
||||||
|
if (!accelerator) return;
|
||||||
|
try {
|
||||||
|
const ok = globalShortcut.register(accelerator, showMainWindow);
|
||||||
|
if (ok) {
|
||||||
|
currentShortcut = accelerator;
|
||||||
|
} else {
|
||||||
|
console.warn(`[tray] Failed to register General Assist shortcut: ${accelerator}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[tray] Error registering General Assist shortcut:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTray(): Promise<void> {
|
||||||
|
if (tray) return;
|
||||||
|
|
||||||
|
tray = new Tray(getTrayIcon());
|
||||||
|
tray.setToolTip('SurfSense');
|
||||||
|
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{ label: 'Open SurfSense', click: showMainWindow },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Quit', click: () => { app.exit(0); } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
|
tray.on('double-click', showMainWindow);
|
||||||
|
|
||||||
|
const shortcuts = await getShortcuts();
|
||||||
|
registerShortcut(shortcuts.generalAssist);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reregisterGeneralAssist(): Promise<void> {
|
||||||
|
const shortcuts = await getShortcuts();
|
||||||
|
registerShortcut(shortcuts.generalAssist);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyTray(): void {
|
||||||
|
if (currentShortcut) {
|
||||||
|
globalShortcut.unregister(currentShortcut);
|
||||||
|
currentShortcut = null;
|
||||||
|
}
|
||||||
|
tray?.destroy();
|
||||||
|
tray = null;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Clipboard, Sparkles } from "lucide-react";
|
import { AppWindow, Clipboard, Sparkles } from "lucide-react";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { useElectronAPI } from "@/hooks/use-platform";
|
import { useElectronAPI } from "@/hooks/use-platform";
|
||||||
|
|
||||||
export function DesktopContent() {
|
export function DesktopContent() {
|
||||||
|
|
@ -68,7 +69,7 @@ export function DesktopContent() {
|
||||||
await api.setAutocompleteEnabled(checked);
|
await api.setAutocompleteEnabled(checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateShortcut = (key: "quickAsk" | "autocomplete", accelerator: string) => {
|
const updateShortcut = (key: "generalAssist" | "quickAsk" | "autocomplete", accelerator: string) => {
|
||||||
setShortcuts((prev) => {
|
setShortcuts((prev) => {
|
||||||
const updated = { ...prev, [key]: accelerator };
|
const updated = { ...prev, [key]: accelerator };
|
||||||
api.setShortcuts?.({ [key]: accelerator }).catch(() => {
|
api.setShortcuts?.({ [key]: accelerator }).catch(() => {
|
||||||
|
|
@ -79,7 +80,7 @@ export function DesktopContent() {
|
||||||
toast.success("Shortcut updated");
|
toast.success("Shortcut updated");
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetShortcut = (key: "quickAsk" | "autocomplete") => {
|
const resetShortcut = (key: "generalAssist" | "quickAsk" | "autocomplete") => {
|
||||||
updateShortcut(key, DEFAULT_SHORTCUTS[key]);
|
updateShortcut(key, DEFAULT_SHORTCUTS[key]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -95,23 +96,32 @@ export function DesktopContent() {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-3 md:px-6 pb-3 md:pb-6">
|
<CardContent className="px-3 md:px-6 pb-3 md:pb-6">
|
||||||
{shortcutsLoaded ? (
|
{shortcutsLoaded ? (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<ShortcutRecorder
|
<ShortcutRecorder
|
||||||
value={shortcuts.quickAsk}
|
value={shortcuts.generalAssist}
|
||||||
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
onChange={(accel) => updateShortcut("generalAssist", accel)}
|
||||||
onReset={() => resetShortcut("quickAsk")}
|
onReset={() => resetShortcut("generalAssist")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
||||||
label="Quick Ask"
|
label="General Assist"
|
||||||
|
description="Open SurfSense from anywhere"
|
||||||
|
icon={AppWindow}
|
||||||
|
/>
|
||||||
|
<ShortcutRecorder
|
||||||
|
value={shortcuts.quickAsk}
|
||||||
|
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
||||||
|
onReset={() => resetShortcut("quickAsk")}
|
||||||
|
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
||||||
|
label="Quick Assist"
|
||||||
description="Copy selected text and ask AI about it"
|
description="Copy selected text and ask AI about it"
|
||||||
icon={Clipboard}
|
icon={Clipboard}
|
||||||
/>
|
/>
|
||||||
<ShortcutRecorder
|
<ShortcutRecorder
|
||||||
value={shortcuts.autocomplete}
|
value={shortcuts.autocomplete}
|
||||||
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
||||||
onReset={() => resetShortcut("autocomplete")}
|
onReset={() => resetShortcut("autocomplete")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
||||||
label="Autocomplete"
|
label="Extreme Assist"
|
||||||
description="Get AI writing suggestions from a screenshot"
|
description="AI writing powered by your screen and knowledge base"
|
||||||
icon={Sparkles}
|
icon={Sparkles}
|
||||||
/>
|
/>
|
||||||
<p className="text-[11px] text-muted-foreground">
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
|
@ -126,10 +136,10 @@ export function DesktopContent() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Autocomplete Toggle */}
|
{/* Extreme Assist Toggle */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3">
|
<CardHeader className="px-3 md:px-6 pt-3 md:pt-6 pb-2 md:pb-3">
|
||||||
<CardTitle className="text-base md:text-lg">Autocomplete</CardTitle>
|
<CardTitle className="text-base md:text-lg">Extreme Assist</CardTitle>
|
||||||
<CardDescription className="text-xs md:text-sm">
|
<CardDescription className="text-xs md:text-sm">
|
||||||
Get inline writing suggestions powered by your knowledge base as you type in any app.
|
Get inline writing suggestions powered by your knowledge base as you type in any app.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
|
|
@ -138,7 +148,7 @@ export function DesktopContent() {
|
||||||
<div className="flex items-center justify-between rounded-lg border p-4">
|
<div className="flex items-center justify-between rounded-lg border p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="autocomplete-toggle" className="text-sm font-medium cursor-pointer">
|
<Label htmlFor="autocomplete-toggle" className="text-sm font-medium cursor-pointer">
|
||||||
Enable autocomplete
|
Enable Extreme Assist
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Show suggestions while typing in other applications.
|
Show suggestions while typing in other applications.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { IconBrandGoogleFilled } from "@tabler/icons-react";
|
import { IconBrandGoogleFilled } from "@tabler/icons-react";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { Clipboard, Eye, EyeOff, Keyboard, Sparkles } from "lucide-react";
|
import { AppWindow, Clipboard, Eye, EyeOff, Keyboard, Sparkles } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
@ -48,7 +48,7 @@ export default function DesktopLoginPage() {
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
const updateShortcut = useCallback(
|
const updateShortcut = useCallback(
|
||||||
(key: "quickAsk" | "autocomplete", accelerator: string) => {
|
(key: "generalAssist" | "quickAsk" | "autocomplete", accelerator: string) => {
|
||||||
setShortcuts((prev) => {
|
setShortcuts((prev) => {
|
||||||
const updated = { ...prev, [key]: accelerator };
|
const updated = { ...prev, [key]: accelerator };
|
||||||
api?.setShortcuts?.({ [key]: accelerator }).catch(() => {
|
api?.setShortcuts?.({ [key]: accelerator }).catch(() => {
|
||||||
|
|
@ -62,7 +62,7 @@ export default function DesktopLoginPage() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetShortcut = useCallback(
|
const resetShortcut = useCallback(
|
||||||
(key: "quickAsk" | "autocomplete") => {
|
(key: "generalAssist" | "quickAsk" | "autocomplete") => {
|
||||||
updateShortcut(key, DEFAULT_SHORTCUTS[key]);
|
updateShortcut(key, DEFAULT_SHORTCUTS[key]);
|
||||||
},
|
},
|
||||||
[updateShortcut]
|
[updateShortcut]
|
||||||
|
|
@ -132,12 +132,21 @@ export default function DesktopLoginPage() {
|
||||||
<Keyboard className="size-3" />
|
<Keyboard className="size-3" />
|
||||||
Keyboard Shortcuts
|
Keyboard Shortcuts
|
||||||
</div>
|
</div>
|
||||||
|
<ShortcutRecorder
|
||||||
|
value={shortcuts.generalAssist}
|
||||||
|
onChange={(accel) => updateShortcut("generalAssist", accel)}
|
||||||
|
onReset={() => resetShortcut("generalAssist")}
|
||||||
|
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
|
||||||
|
label="General Assist"
|
||||||
|
description="Open SurfSense from anywhere"
|
||||||
|
icon={AppWindow}
|
||||||
|
/>
|
||||||
<ShortcutRecorder
|
<ShortcutRecorder
|
||||||
value={shortcuts.quickAsk}
|
value={shortcuts.quickAsk}
|
||||||
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
onChange={(accel) => updateShortcut("quickAsk", accel)}
|
||||||
onReset={() => resetShortcut("quickAsk")}
|
onReset={() => resetShortcut("quickAsk")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
|
||||||
label="Quick Ask"
|
label="Quick Assist"
|
||||||
description="Copy selected text and ask AI about it"
|
description="Copy selected text and ask AI about it"
|
||||||
icon={Clipboard}
|
icon={Clipboard}
|
||||||
/>
|
/>
|
||||||
|
|
@ -146,8 +155,8 @@ export default function DesktopLoginPage() {
|
||||||
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
onChange={(accel) => updateShortcut("autocomplete", accel)}
|
||||||
onReset={() => resetShortcut("autocomplete")}
|
onReset={() => resetShortcut("autocomplete")}
|
||||||
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
|
||||||
label="Autocomplete"
|
label="Extreme Assist"
|
||||||
description="Get AI writing suggestions from a screenshot"
|
description="AI writing powered by your screen and knowledge base"
|
||||||
icon={Sparkles}
|
icon={Sparkles}
|
||||||
/>
|
/>
|
||||||
<p className="text-[11px] text-muted-foreground text-center">
|
<p className="text-[11px] text-muted-foreground text-center">
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export function acceleratorToDisplay(accel: string): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SHORTCUTS = {
|
export const DEFAULT_SHORTCUTS = {
|
||||||
|
generalAssist: "CommandOrControl+Shift+S",
|
||||||
quickAsk: "CommandOrControl+Alt+S",
|
quickAsk: "CommandOrControl+Alt+S",
|
||||||
autocomplete: "CommandOrControl+Shift+Space",
|
autocomplete: "CommandOrControl+Shift+Space",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
6
surfsense_web/types/window.d.ts
vendored
6
surfsense_web/types/window.d.ts
vendored
|
|
@ -89,10 +89,10 @@ interface ElectronAPI {
|
||||||
getAuthTokens: () => Promise<{ bearer: string; refresh: string } | null>;
|
getAuthTokens: () => Promise<{ bearer: string; refresh: string } | null>;
|
||||||
setAuthTokens: (bearer: string, refresh: string) => Promise<void>;
|
setAuthTokens: (bearer: string, refresh: string) => Promise<void>;
|
||||||
// Keyboard shortcut configuration
|
// Keyboard shortcut configuration
|
||||||
getShortcuts: () => Promise<{ quickAsk: string; autocomplete: string }>;
|
getShortcuts: () => Promise<{ generalAssist: string; quickAsk: string; autocomplete: string }>;
|
||||||
setShortcuts: (
|
setShortcuts: (
|
||||||
config: Partial<{ quickAsk: string; autocomplete: string }>
|
config: Partial<{ generalAssist: string; quickAsk: string; autocomplete: string }>
|
||||||
) => Promise<{ quickAsk: string; autocomplete: string }>;
|
) => Promise<{ generalAssist: string; quickAsk: string; autocomplete: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue