diff --git a/README.md b/README.md index a05a8bcb..db05c9de 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ > This project is a fork of [CheatingDaddy](https://github.com/sohzm/cheating-daddy) with modifications and enhancements. Thanks to [Soham](https://x.com/soham_btw) and all the open-source contributors who made this possible! +> Currently, we're working on a full code refactor and modularization. Once that's completed, we'll jump into addressing the major issues. You can find WIP issues & changelog below this document. + πŸ€– **Fast, light & open-source**β€”Glass lives on your desktop, sees what you see, listens in real time, understands your context, and turns every moment into structured knowledge. πŸ’¬ **Proactive in meetings**β€”it surfaces action items, summaries, and answers the instant you need them. @@ -62,11 +64,14 @@ npm run setup booking-screen -### Use your own OpenAI API key, or sign up to use ours (free) +### Use your own API key, or sign up to use ours (free) booking-screen -You can visit [here](https://platform.openai.com/api-keys) to get your OpenAI API Key. +**Currently Supporting:** +- OpenAI API: Get OpenAI API Key [here](https://platform.openai.com/api-keys) +- Gemini API: Get Gemini API Key [here](https://aistudio.google.com/apikey) +- Local LLM (WIP) ### Liquid Glass Design (coming soon) @@ -88,21 +93,41 @@ You can visit [here](https://platform.openai.com/api-keys) to get your OpenAI AP `Ctrl/Cmd + Arrows` : move main window position +## Repo Activity + +![Alt](https://repobeats.axiom.co/api/embed/a23e342faafa84fa8797fa57762885d82fac1180.svg "Repobeats analytics image") + ## Contributing We love contributions! Feel free to open issues for bugs or feature requests. +> Currently, we're working on a full code refactor and modularization. Once that's completed, we'll jump into addressing the major issues. + +### Contributors + + + + + +### Help Wanted Issues -## πŸ›  Current Issues & Improvements +We have a list of [help wanted](https://github.com/pickle-com/glass/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%99%8B%E2%80%8D%E2%99%82%EF%B8%8Fhelp%20wanted%22) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process. + + +### πŸ›  Current Issues & Improvements | Status | Issue | Description | |--------|--------------------------------|---------------------------------------------------| -| 🚧 WIP | AEC Improvement | Transcription is not working occasionally | | 🚧 WIP | Code Refactoring | Refactoring the entire codebase for better maintainability. | +| 🚧 WIP | Windows Build | Make Glass buildable & runnable in Windows | +| 🚧 WIP | Local LLM Support | Supporting Local LLM to power AI answers | +| 🚧 WIP | AEC Improvement | Transcription is not working occasionally | | 🚧 WIP | Firebase Data Storage Issue | Session & ask should be saved in firebase for signup users | | 🚧 WIP | Login Issue | Currently breaking when switching between local and sign-in mode | | 🚧 WIP | Liquid Glass | Liquid Glass UI for MacOS 26 | -| 🚧 WIP | Permission Issue | Mic & system audio & display capture permission sometimes not working| +### Changelog + +- Jul 5: Now support Gemini, Intel Mac supported ## About Pickle @@ -110,4 +135,4 @@ We love contributions! Feel free to open issues for bugs or feature requests. **Our mission is to build a living digital clone for everyone.** Glass is part of Step 1β€”a trusted pipeline that transforms your daily data into a scalable clone. Visit [pickle.com](https://pickle.com) to learn more. ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=pickle-com/glass&type=Date)](https://www.star-history.com/#pickle-com/glass&Date) \ No newline at end of file +[![Star History Chart](https://api.star-history.com/svg?repos=pickle-com/glass&type=Date)](https://www.star-history.com/#pickle-com/glass&Date) diff --git a/src/app/ApiKeyHeader.js b/src/app/ApiKeyHeader.js index b884c137..75b52015 100644 --- a/src/app/ApiKeyHeader.js +++ b/src/app/ApiKeyHeader.js @@ -48,7 +48,7 @@ export class ApiKeyHeader extends LitElement { width: 285px; min-height: 260px; padding: 18px 20px; - background: rgba(0, 0, 0, 0.3); + background: rgba(0, 0, 0, 0.6); border-radius: 16px; overflow: visible; position: relative; diff --git a/src/app/AppHeader.js b/src/app/AppHeader.js index faf633be..e3edb747 100644 --- a/src/app/AppHeader.js +++ b/src/app/AppHeader.js @@ -100,8 +100,8 @@ export class AppHeader extends LitElement { .header { width: 100%; - height: 47px; - padding: 2px 10px 2px 13px; + height: 40px; + padding: 2px 6px 2px 8px; background: transparent; overflow: hidden; border-radius: 9000px; @@ -198,8 +198,8 @@ export class AppHeader extends LitElement { align-items: center; gap: 9px; display: flex; - padding: 0 8px; - border-radius: 6px; + padding: 0 5px 0 8px; + border-radius: 4px; transition: background 0.15s ease; } @@ -234,7 +234,7 @@ export class AppHeader extends LitElement { color: white; font-size: 12px; font-family: 'Helvetica Neue', sans-serif; - font-weight: 500; /* Medium */ + font-weight: 400; /* Medium */ word-wrap: break-word; } diff --git a/src/app/content.html b/src/app/content.html index 48c76912..1e787a6a 100644 --- a/src/app/content.html +++ b/src/app/content.html @@ -100,13 +100,13 @@ } .window-sliding-down { - animation: slideDownFromHeader 0.25s cubic-bezier(0.23, 1, 0.32, 1) forwards; + animation: slideDownFromHeader 0.12s cubic-bezier(0.23, 1, 0.32, 1) forwards; will-change: transform, opacity; transform-style: preserve-3d; } .window-sliding-up { - animation: slideUpToHeader 0.18s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards; + animation: slideUpToHeader 0.10s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards; will-change: transform, opacity; transform-style: preserve-3d; } @@ -156,14 +156,14 @@ } .settings-window-show { - animation: settingsPopFromButton 0.22s cubic-bezier(0.23, 1, 0.32, 1) forwards; + animation: settingsPopFromButton 0.12s cubic-bezier(0.23, 1, 0.32, 1) forwards; transform-origin: 85% 0%; will-change: transform, opacity; transform-style: preserve-3d; } .settings-window-hide { - animation: settingsCollapseToButton 0.18s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards; + animation: settingsCollapseToButton 0.10s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards; transform-origin: 85% 0%; will-change: transform, opacity; transform-style: preserve-3d; @@ -250,7 +250,7 @@ if (animationTimeout) clearTimeout(animationTimeout); animationTimeout = setTimeout(() => { app.classList.remove('window-sliding-down'); - }, 250); + }, 120); }); ipcRenderer.on('settings-window-show-animation', () => { @@ -261,7 +261,7 @@ if (animationTimeout) clearTimeout(animationTimeout); animationTimeout = setTimeout(() => { app.classList.remove('settings-window-show'); - }, 220); + }, 120); }); ipcRenderer.on('window-hide-animation', () => { @@ -273,7 +273,7 @@ animationTimeout = setTimeout(() => { app.classList.remove('window-sliding-up'); app.classList.add('window-hidden'); - }, 180); + }, 100); }); ipcRenderer.on('settings-window-hide-animation', () => { @@ -285,7 +285,7 @@ animationTimeout = setTimeout(() => { app.classList.remove('settings-window-hide'); app.classList.add('window-hidden'); - }, 180); + }, 100); }); ipcRenderer.on('listen-window-move-to-center', () => { @@ -309,6 +309,20 @@ app.classList.remove('listen-window-moving'); }, 350); }); + + ipcRenderer.on('cancel-animations', () => { + if (animationTimeout) clearTimeout(animationTimeout); + + // μ• λ‹ˆλ©”μ΄μ…˜ κ΄€λ ¨ class μ „λΆ€ 제거 + app.classList.remove( + 'window-sliding-down', + 'window-sliding-up', + 'settings-window-show', + 'settings-window-hide' + ); + // λ°”λ‘œ 투λͺ… + pointer-off μƒνƒœλ‘œ + app.classList.add('window-hidden'); + }); } }); diff --git a/src/electron/windowManager.js b/src/electron/windowManager.js index 9098d7c9..d5b67d4b 100644 --- a/src/electron/windowManager.js +++ b/src/electron/windowManager.js @@ -85,7 +85,7 @@ function createFeatureWindows(header) { windowPool.set('listen', listen); // ask - const ask = new BrowserWindow({ ...commonChildOptions, width:600, height:350 }); + const ask = new BrowserWindow({ ...commonChildOptions, width:554, height:350 }); ask.setContentProtection(isContentProtectionOn); ask.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true}); ask.loadFile(path.join(__dirname,'../app/content.html'),{query:{view:'ask'}}); @@ -118,6 +118,14 @@ function destroyFeatureWindows() { }); } +function instantHideWindow(win) { + if (!win || win.isDestroyed() || !win.isVisible()) return; + try { + win.webContents.send('cancel-animations'); // β‘  λ Œλ”λŸ¬ μ• λ‹ˆλ©”μ΄μ…˜ μ •μ§€ + } catch (_) {} + win.hide(); // β‘‘ μ§€μ—° 없이 λ°”λ‘œ hide + } + function isAllowed(name) { const def = windowDefinitions[name]; return def && def.allowedStates.includes(currentHeaderState); @@ -231,7 +239,8 @@ class WindowLayoutManager { if (!askVisible && !listenVisible) return; - const PAD = 8; + const PAD_H = 12; + const PAD_V = -6; /* β‘  헀더 쀑심 Xλ₯Ό β€œλ””μŠ€ν”Œλ ˆμ΄ κΈ°μ€€ μƒλŒ€μ’Œν‘œβ€λ‘œ λ³€ν™˜ */ const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2; @@ -243,34 +252,34 @@ class WindowLayoutManager { /* 두 μ°½ λͺ¨λ‘ λ³΄μ΄λŠ” 경우 */ /* ------------------------------------------------- */ if (askVisible && listenVisible) { - const combinedWidth = listenBounds.width + PAD + askBounds.width; + const combinedWidth = listenBounds.width + PAD_H + askBounds.width; /* β‘‘ λͺ¨λ“  X μ’Œν‘œλ₯Ό μƒλŒ€μ’Œν‘œλ‘œ 계산 */ let groupStartXRel = headerCenterXRel - combinedWidth / 2; let listenXRel = groupStartXRel; - let askXRel = groupStartXRel + listenBounds.width + PAD; + let askXRel = groupStartXRel + listenBounds.width + PAD_H; /* 쒌우 ν™”λ©΄ μ—¬λ°± ν΄λž¨ν”„ – μ—­μ‹œ μƒλŒ€μ’Œν‘œλ‘œ */ - if (listenXRel < PAD) { - listenXRel = PAD; - askXRel = listenXRel + listenBounds.width + PAD; + if (listenXRel < PAD_H) { + listenXRel = PAD_H; + askXRel = listenXRel + listenBounds.width + PAD_H; } - if (askXRel + askBounds.width > screenWidth - PAD) { - askXRel = screenWidth - PAD - askBounds.width; - listenXRel = askXRel - listenBounds.width - PAD; + if (askXRel + askBounds.width > screenWidth - PAD_H) { + askXRel = screenWidth - PAD_H - askBounds.width; + listenXRel = askXRel - listenBounds.width - PAD_H; } /* Y μ’Œν‘œλŠ” 이미 μƒλŒ€κ°’μœΌλ‘œ 계산돼 있음 */ let yRel; switch (strategy.primary) { case 'below': - yRel = headerBounds.y - workAreaY + headerBounds.height + PAD; + yRel = headerBounds.y - workAreaY + headerBounds.height + PAD_V; break; case 'above': - yRel = headerBounds.y - workAreaY - Math.max(askBounds.height, listenBounds.height) - PAD; + yRel = headerBounds.y - workAreaY - Math.max(askBounds.height, listenBounds.height) - PAD_V; break; default: - yRel = headerBounds.y - workAreaY + headerBounds.height + PAD; + yRel = headerBounds.y - workAreaY + headerBounds.height + PAD_V; break; } @@ -300,19 +309,19 @@ class WindowLayoutManager { let yRel; switch (strategy.primary) { case 'below': - yRel = headerBounds.y - workAreaY + headerBounds.height + PAD; + yRel = headerBounds.y - workAreaY + headerBounds.height + PAD_V; break; case 'above': - yRel = headerBounds.y - workAreaY - winBounds.height - PAD; + yRel = headerBounds.y - workAreaY - winBounds.height - PAD_V; break; default: - yRel = headerBounds.y - workAreaY + headerBounds.height + PAD; + yRel = headerBounds.y - workAreaY + headerBounds.height + PAD_V; break; } /* ν™”λ©΄ 경계 ν΄λž¨ν”„ */ - xRel = Math.max(PAD, Math.min(screenWidth - winBounds.width - PAD, xRel)); - yRel = Math.max(PAD, Math.min(screenHeight - winBounds.height - PAD, yRel)); + xRel = Math.max(PAD_H, Math.min(screenWidth - winBounds.width - PAD_H, xRel)); + yRel = Math.max(PAD_V, Math.min(screenHeight - winBounds.height - PAD_V, yRel)); /* μ ˆλŒ€μ’Œν‘œλ‘œ λ³€ν™˜ ν›„ 배치 */ win.setBounds({ @@ -465,170 +474,61 @@ class SmoothMovementManager { this.currentDisplayId = targetDisplay.id; } - hideToEdge(edge, callback) { + hideToEdge(edge, callback, { instant = false } = {}) { const header = windowPool.get('header'); - if (!header || !header.isVisible() || this.isAnimating) return; - - console.log(`[Movement] Hiding to ${edge} edge`); - - const currentBounds = header.getBounds(); - this.lastVisiblePosition = { x: currentBounds.x, y: currentBounds.y }; - this.headerPosition = { x: currentBounds.x, y: currentBounds.y }; - - const display = getCurrentDisplay(header); - const { width: screenWidth, height: screenHeight } = display.workAreaSize; - const { x: workAreaX, y: workAreaY } = display.workArea; - const headerBounds = header.getBounds(); - - let targetX = this.headerPosition.x; - let targetY = this.headerPosition.y; - - switch (edge) { - case 'top': - targetY = workAreaY - headerBounds.height - 20; - break; - case 'bottom': - targetY = workAreaY + screenHeight + 20; - break; - case 'left': - targetX = workAreaX - headerBounds.width - 20; - break; - case 'right': - targetX = workAreaX + screenWidth + 20; - break; + if (!header || header.isDestroyed()) { + if (typeof callback === 'function') callback(); + return; + } + + const { x, y } = header.getBounds(); + this.lastVisiblePosition = { x, y }; + this.hiddenPosition = { edge }; + + // header.webContents.send('window-hide-animation'); + + // setTimeout(() => { + // if (!header.isDestroyed()) header.hide(); + // if (typeof callback === 'function') callback(); + // }, 5); + if (instant) { + header.hide(); + callback?.(); + return; } - this.hiddenPosition = { x: targetX, y: targetY, edge }; - - this.isAnimating = true; - const startX = this.headerPosition.x; - const startY = this.headerPosition.y; - const duration = 400; - const startTime = Date.now(); - - const animate = () => { - if (!header || typeof header.setPosition !== 'function' || header.isDestroyed()) { - this.isAnimating = false; - return; - } - - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / duration, 1); - const eased = progress * progress * progress; - - const currentX = startX + (targetX - startX) * eased; - const currentY = startY + (targetY - startY) * eased; - - // Validate computed positions before using - if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) { - console.error('[Movement] Invalid animation values for hide:', { - currentX, currentY, progress, eased, startX, startY, targetX, targetY - }); - this.isAnimating = false; - return; - } - - // Safely call setPosition - try { - header.setPosition(Math.round(currentX), Math.round(currentY)); - } catch (err) { - console.error('[Movement] Failed to set position:', err); - this.isAnimating = false; - return; - } - - if (progress < 1) { - setTimeout(animate, 8); - } else { - this.headerPosition = { x: targetX, y: targetY }; - - if (Number.isFinite(targetX) && Number.isFinite(targetY)) { - try { - header.setPosition(Math.round(targetX), Math.round(targetY)); - } catch (err) { - console.error('[Movement] Failed to set final position:', err); - } - } - - this.isAnimating = false; - - if (typeof callback === 'function') { - try { - callback(); - } catch (err) { - console.error('[Movement] Callback error:', err); - } - } - - console.log(`[Movement] Hide to ${edge} completed`); - } - }; - - animate(); + header.webContents.send('window-hide-animation'); + + setTimeout(() => { + if (!header.isDestroyed()) header.hide(); + if (typeof callback === 'function') callback(); + }, 5); } - - showFromEdge(callback) { + + showFromEdge(callback) { const header = windowPool.get('header'); - if (!header || this.isAnimating || !this.hiddenPosition || !this.lastVisiblePosition) return; - - console.log(`[Movement] Showing from ${this.hiddenPosition.edge} edge`); - - header.setPosition(this.hiddenPosition.x, this.hiddenPosition.y); - this.headerPosition = { x: this.hiddenPosition.x, y: this.hiddenPosition.y }; - - const targetX = this.lastVisiblePosition.x; - const targetY = this.lastVisiblePosition.y; - - this.isAnimating = true; - const startX = this.headerPosition.x; - const startY = this.headerPosition.y; - const duration = 500; - const startTime = Date.now(); - - const animate = () => { - if (!header || header.isDestroyed()) { - this.isAnimating = false; - return; - } - - const elapsed = Date.now() - startTime; - const progress = Math.min(elapsed / duration, 1); - - const c1 = 1.70158; - const c3 = c1 + 1; - const eased = 1 + c3 * Math.pow(progress - 1, 3) + c1 * Math.pow(progress - 1, 2); - - const currentX = startX + (targetX - startX) * eased; - const currentY = startY + (targetY - startY) * eased; - - if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) { - console.error('[Movement] Invalid animation values for show:', { currentX, currentY, progress, eased }); - this.isAnimating = false; - return; - } - - header.setPosition(Math.round(currentX), Math.round(currentY)); - - if (progress < 1) { - setTimeout(animate, 8); - } else { - this.headerPosition = { x: targetX, y: targetY }; - this.headerPosition = { x: targetX, y: targetY }; - if (Number.isFinite(targetX) && Number.isFinite(targetY)) { - header.setPosition(Math.round(targetX), Math.round(targetY)); - } - this.isAnimating = false; - - this.hiddenPosition = null; - this.lastVisiblePosition = null; - - if (callback) callback(); - - console.log(`[Movement] Show from edge completed`); - } - }; - - animate(); + if (!header || header.isDestroyed()) { + if (typeof callback === 'function') callback(); + return; + } + + // 숨기기 전에 κΈ°μ–΅ν•΄λ‘” μœ„μΉ˜ 볡ꡬ + if (this.lastVisiblePosition) { + header.setPosition( + this.lastVisiblePosition.x, + this.lastVisiblePosition.y, + false // animate: false + ); + } + + header.show(); + header.webContents.send('window-show-animation'); + + // λ‚΄λΆ€ μƒνƒœ μ΄ˆκΈ°ν™” + this.hiddenPosition = null; + this.lastVisiblePosition = null; + + if (typeof callback === 'function') callback(); } moveStep(direction) { @@ -878,12 +778,13 @@ function toggleAllWindowsVisibility() { if (win.isVisible()) { lastVisibleWindows.add(name); if (name !== 'header') { - win.webContents.send('window-hide-animation'); - setTimeout(() => { - if (!win.isDestroyed()) { - win.hide(); - } - }, 200); + // win.webContents.send('window-hide-animation'); + // setTimeout(() => { + // if (!win.isDestroyed()) { + // win.hide(); + // } + // }, 200); + instantHideWindow(win); } } }); @@ -893,8 +794,13 @@ function toggleAllWindowsVisibility() { movementManager.hideToEdge(nearestEdge, () => { header.hide(); console.log('[Visibility] Smart hide completed'); - }); + }, { instant: true }); } else { + header.webContents.send('cancel-animations'); + windowPool.forEach((win, name) => { + if (name !== 'header') win.webContents.send('cancel-animations'); + }); + console.log('[Visibility] Smart showing from hidden position'); console.log('[Visibility] Restoring windows:', Array.from(lastVisibleWindows)); @@ -1304,7 +1210,7 @@ function setupIpcHandlers(openaiSessionRef) { const { x: waX, y: waY, width: waW, height: waH } = disp.workArea; let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2); - let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31); + let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 21); x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x)); y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y)); @@ -2329,6 +2235,13 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, openaiSessi globalShortcut.register(keybinds.nextStep, () => { console.log('⌘/Ctrl+Enter Ask shortcut triggered'); + const headerWin = windowPool.get('header'); + + if (!headerWin || headerWin.isDestroyed() || !headerWin.isVisible()) { + console.error('Header window not found or destroyed'); + return; + } + const askWindow = windowPool.get('ask'); if (!askWindow || askWindow.isDestroyed()) { console.error('Ask window not found or destroyed'); @@ -2341,6 +2254,8 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, openaiSessi try { askWindow.show(); + askWindow.webContents.send('window-did-show'); + const header = windowPool.get('header'); if (header) { const currentHeaderPosition = header.getBounds(); @@ -2563,4 +2478,4 @@ module.exports = { isFirebaseLoggedIn, setCurrentFirebaseUser, getVirtualKeyByEmail, -}; +}; \ No newline at end of file diff --git a/src/features/ask/AskView.js b/src/features/ask/AskView.js index 437aa91c..6fe18a1c 100644 --- a/src/features/ask/AskView.js +++ b/src/features/ask/AskView.js @@ -482,8 +482,8 @@ export class AskView extends LitElement { display: flex; align-items: center; gap: 8px; - padding: 12px 16px; - background: rgba(0, 0, 0, 0.1); + padding: 4px; + background: rgba(0, 0, 0, 0.01); border-top: 1px solid rgba(255, 255, 255, 0.1); flex-shrink: 0; transition: all 0.3s ease-in-out; @@ -505,8 +505,8 @@ export class AskView extends LitElement { #textInput { flex: 1; padding: 10px 14px; - background: rgba(0, 0, 0, 0.2); - border-radius: 20px; + background: rgba(0, 0, 0, 0.01); + border-radius: 6px; outline: none; border: none; color: white; @@ -1206,7 +1206,7 @@ export class AskView extends LitElement { const textInput = this.shadowRoot?.getElementById('textInput'); if (!textInput) return; const text = textInput.value.trim(); - if (!text) return; + // if (!text) return; textInput.value = ''; @@ -1261,7 +1261,7 @@ export class AskView extends LitElement { textInput.focus(); - if (!textInput.value.trim()) return; + // if (!textInput.value.trim()) return; this.handleSendText(); } diff --git a/src/features/listen/renderer.js b/src/features/listen/renderer.js index 1701d775..42727c87 100644 --- a/src/features/listen/renderer.js +++ b/src/features/listen/renderer.js @@ -949,7 +949,34 @@ async function sendMessage(userPrompt, options = {}) { const conversationHistory = formatRealtimeConversationHistory(); console.log(`πŸ“ Using conversation history: ${realtimeConversationHistory.length} texts`); - const systemPrompt = PICKLE_GLASS_SYSTEM_PROMPT.replace('{{CONVERSATION_HISTORY}}', conversationHistory); + // Get custom prompt from user settings + let customPrompt = ""; + try { + customPrompt = localStorage.getItem("customPrompt") || ""; + if (!customPrompt && window.require) { + const { ipcRenderer } = window.require("electron"); + const userPresets = await ipcRenderer.invoke("get-user-presets"); + if (userPresets && userPresets.length > 0) { + let preset = userPresets.find(p => p.id === "personalized" || p._id === "personalized"); + if (!preset) preset = userPresets[0]; + if (preset && preset.prompt) { + customPrompt = preset.prompt; + console.log("πŸ“ Using custom prompt from user presets"); + } + } + } + } catch (error) { + console.error("Failed to get custom prompt:", error); + } + + // Build system prompt with custom context + let systemPrompt = PICKLE_GLASS_SYSTEM_PROMPT; + if (customPrompt && customPrompt.trim()) { + console.log("πŸ“ Applying custom prompt to system message"); + const customSection = `nnnThe user has provided the following custom context and instructions:nn${customPrompt.trim()}nnPlease incorporate this context into your responses and follow any specific instructions provided.nnn`; + systemPrompt = systemPrompt.replace("{{CONVERSATION_HISTORY}}", customSection + "{{CONVERSATION_HISTORY}}"); + } + systemPrompt = systemPrompt.replace("{{CONVERSATION_HISTORY}}", conversationHistory); let API_KEY = localStorage.getItem('openai_api_key');