mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
Merge pull request #1162 from CREDO23/feat/vision-autocomplete
[Feat] Multi-suggestion autocomplete, Vision LLM config & Desktop analytics
This commit is contained in:
commit
e827a3906d
49 changed files with 3263 additions and 153 deletions
|
|
@ -4,3 +4,7 @@
|
|||
# The hosted web frontend URL. Used to intercept OAuth redirects and keep them
|
||||
# inside the desktop app. Set to your production frontend domain.
|
||||
HOSTED_FRONTEND_URL=https://surfsense.net
|
||||
|
||||
# PostHog analytics (leave empty to disable)
|
||||
POSTHOG_KEY=
|
||||
POSTHOG_HOST=https://assets.surfsense.com
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
"electron-store": "^11.0.2",
|
||||
"electron-updater": "^6.8.3",
|
||||
"get-port-please": "^3.2.0",
|
||||
"node-mac-permissions": "^2.5.0"
|
||||
"node-mac-permissions": "^2.5.0",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"posthog-node": "^5.29.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
31
surfsense_desktop/pnpm-lock.yaml
generated
31
surfsense_desktop/pnpm-lock.yaml
generated
|
|
@ -26,6 +26,12 @@ importers:
|
|||
node-mac-permissions:
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
node-machine-id:
|
||||
specifier: ^1.1.12
|
||||
version: 1.1.12
|
||||
posthog-node:
|
||||
specifier: ^5.29.0
|
||||
version: 5.29.0(rxjs@7.8.2)
|
||||
devDependencies:
|
||||
'@electron/rebuild':
|
||||
specifier: ^4.0.3
|
||||
|
|
@ -308,6 +314,9 @@ packages:
|
|||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@posthog/core@1.25.0':
|
||||
resolution: {integrity: sha512-XKaHvRFIIN7Dw84r1eKimV1rl9DS+9XMCPPZ7P3+l8fE+rDsmumebiTFsY+q40bVXflcGW9wB+57LH0lvcGmhw==}
|
||||
|
||||
'@sindresorhus/is@4.6.0':
|
||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -1194,6 +1203,9 @@ packages:
|
|||
resolution: {integrity: sha512-zR8SVCaN3WqV1xwWd04XVAdzm3UTdjbxciLrZtB0Cc7F2Kd34AJfhPD4hm1HU0YH3oGUZO4X9OBLY5ijSTHsGw==}
|
||||
os: [darwin]
|
||||
|
||||
node-machine-id@1.1.12:
|
||||
resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==}
|
||||
|
||||
nopt@8.1.0:
|
||||
resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
|
|
@ -1263,6 +1275,15 @@ packages:
|
|||
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
|
||||
engines: {node: '>=10.4.0'}
|
||||
|
||||
posthog-node@5.29.0:
|
||||
resolution: {integrity: sha512-po7N55haSKxV8VOulkBZJja938yILShl6+fFjoUV3iQgOBCg4Muu615/xRg8mpNiz+UASvL0EEiGvIxdhXfj6Q==}
|
||||
engines: {node: ^20.20.0 || >=22.22.0}
|
||||
peerDependencies:
|
||||
rxjs: ^7.0.0
|
||||
peerDependenciesMeta:
|
||||
rxjs:
|
||||
optional: true
|
||||
|
||||
postject@1.0.0-alpha.6:
|
||||
resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
|
@ -1876,6 +1897,8 @@ snapshots:
|
|||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@posthog/core@1.25.0': {}
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
|
@ -2940,6 +2963,8 @@ snapshots:
|
|||
bindings: 1.5.0
|
||||
node-addon-api: 7.1.1
|
||||
|
||||
node-machine-id@1.1.12: {}
|
||||
|
||||
nopt@8.1.0:
|
||||
dependencies:
|
||||
abbrev: 3.0.1
|
||||
|
|
@ -3002,6 +3027,12 @@ snapshots:
|
|||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
posthog-node@5.29.0(rxjs@7.8.2):
|
||||
dependencies:
|
||||
'@posthog/core': 1.25.0
|
||||
optionalDependencies:
|
||||
rxjs: 7.8.2
|
||||
|
||||
postject@1.0.0-alpha.6:
|
||||
dependencies:
|
||||
commander: 9.5.0
|
||||
|
|
|
|||
|
|
@ -111,6 +111,12 @@ async function buildElectron() {
|
|||
'process.env.HOSTED_FRONTEND_URL': JSON.stringify(
|
||||
process.env.HOSTED_FRONTEND_URL || desktopEnv.HOSTED_FRONTEND_URL || 'https://surfsense.net'
|
||||
),
|
||||
'process.env.POSTHOG_KEY': JSON.stringify(
|
||||
process.env.POSTHOG_KEY || desktopEnv.POSTHOG_KEY || ''
|
||||
),
|
||||
'process.env.POSTHOG_HOST': JSON.stringify(
|
||||
process.env.POSTHOG_HOST || desktopEnv.POSTHOG_HOST || 'https://assets.surfsense.com'
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { registerAutocomplete, unregisterAutocomplete } from './modules/autocomp
|
|||
import { registerFolderWatcher, unregisterFolderWatcher } from './modules/folder-watcher';
|
||||
import { registerIpcHandlers } from './ipc/handlers';
|
||||
import { createTray, destroyTray } from './modules/tray';
|
||||
import { initAnalytics, shutdownAnalytics, trackEvent } from './modules/analytics';
|
||||
|
||||
registerGlobalErrorHandlers();
|
||||
|
||||
|
|
@ -22,6 +23,8 @@ if (!setupDeepLinks()) {
|
|||
registerIpcHandlers();
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
initAnalytics();
|
||||
trackEvent('desktop_app_launched');
|
||||
setupMenu();
|
||||
try {
|
||||
await startNextServer();
|
||||
|
|
@ -70,9 +73,15 @@ app.on('before-quit', () => {
|
|||
isQuitting = true;
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
let didCleanup = false;
|
||||
app.on('will-quit', async (e) => {
|
||||
if (didCleanup) return;
|
||||
didCleanup = true;
|
||||
e.preventDefault();
|
||||
unregisterQuickAsk();
|
||||
unregisterAutocomplete();
|
||||
unregisterFolderWatcher();
|
||||
destroyTray();
|
||||
await shutdownAnalytics();
|
||||
app.exit();
|
||||
});
|
||||
|
|
|
|||
50
surfsense_desktop/src/modules/analytics.ts
Normal file
50
surfsense_desktop/src/modules/analytics.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { PostHog } from 'posthog-node';
|
||||
import { machineIdSync } from 'node-machine-id';
|
||||
import { app } from 'electron';
|
||||
|
||||
let client: PostHog | null = null;
|
||||
let distinctId = '';
|
||||
|
||||
export function initAnalytics(): void {
|
||||
const key = process.env.POSTHOG_KEY;
|
||||
if (!key) return;
|
||||
|
||||
try {
|
||||
distinctId = machineIdSync(true);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
client = new PostHog(key, {
|
||||
host: process.env.POSTHOG_HOST || 'https://assets.surfsense.com',
|
||||
flushAt: 20,
|
||||
flushInterval: 10000,
|
||||
});
|
||||
}
|
||||
|
||||
export function trackEvent(event: string, properties?: Record<string, unknown>): void {
|
||||
if (!client) return;
|
||||
|
||||
try {
|
||||
client.capture({
|
||||
distinctId,
|
||||
event,
|
||||
properties: {
|
||||
platform: 'desktop',
|
||||
app_version: app.getVersion(),
|
||||
os: process.platform,
|
||||
...properties,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
// Analytics should never break the app
|
||||
}
|
||||
}
|
||||
|
||||
export async function shutdownAnalytics(): Promise<void> {
|
||||
if (!client) return;
|
||||
|
||||
const timeout = new Promise<void>((resolve) => setTimeout(resolve, 3000));
|
||||
await Promise.race([client.shutdown(), timeout]);
|
||||
client = null;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { captureScreen } from './screenshot';
|
|||
import { createSuggestionWindow, destroySuggestion, getSuggestionWindow } from './suggestion-window';
|
||||
import { getShortcuts } from '../shortcuts';
|
||||
import { getActiveSearchSpaceId } from '../active-search-space';
|
||||
import { trackEvent } from '../analytics';
|
||||
|
||||
let currentShortcut = '';
|
||||
let autocompleteEnabled = true;
|
||||
|
|
@ -41,6 +42,7 @@ async function triggerAutocomplete(): Promise<void> {
|
|||
console.warn('[autocomplete] No active search space. Select a search space first.');
|
||||
return;
|
||||
}
|
||||
trackEvent('desktop_autocomplete_triggered', { search_space_id: searchSpaceId });
|
||||
const cursor = screen.getCursorScreenPoint();
|
||||
const win = createSuggestionWindow(cursor.x, cursor.y);
|
||||
|
||||
|
|
@ -87,9 +89,11 @@ function registerIpcHandlers(): void {
|
|||
ipcRegistered = true;
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.ACCEPT_SUGGESTION, async (_event, text: string) => {
|
||||
trackEvent('desktop_autocomplete_accepted');
|
||||
await acceptAndInject(text);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.DISMISS_SUGGESTION, () => {
|
||||
trackEvent('desktop_autocomplete_dismissed');
|
||||
destroySuggestion();
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.SET_AUTOCOMPLETE_ENABLED, (_event, enabled: boolean) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { checkAccessibilityPermission, getFrontmostApp, simulateCopy, simulatePa
|
|||
import { getServerPort } from './server';
|
||||
import { getShortcuts } from './shortcuts';
|
||||
import { getActiveSearchSpaceId } from './active-search-space';
|
||||
import { trackEvent } from './analytics';
|
||||
|
||||
let currentShortcut = '';
|
||||
let quickAskWindow: BrowserWindow | null = null;
|
||||
|
|
@ -121,6 +122,7 @@ async function quickAskHandler(): Promise<void> {
|
|||
|
||||
sourceApp = getFrontmostApp();
|
||||
console.log('[quick-ask] Source app:', sourceApp, '| Opening Quick Assist with', text.length, 'chars', selected ? '(selected)' : text ? '(clipboard fallback)' : '(empty)');
|
||||
trackEvent('desktop_quick_ask_opened', { has_selected_text: !!selected });
|
||||
openQuickAsk(text);
|
||||
}
|
||||
|
||||
|
|
@ -152,6 +154,7 @@ function registerIpcHandlers(): void {
|
|||
|
||||
if (!checkAccessibilityPermission()) return;
|
||||
|
||||
trackEvent('desktop_quick_ask_replaced');
|
||||
clipboard.writeText(text);
|
||||
destroyQuickAsk();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue