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
-### 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)
-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
+
+
+
## 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
-[](https://www.star-history.com/#pickle-com/glass&Date)
\ No newline at end of file
+[](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');