Refactor window management and global shortcuts handling

This commit is contained in:
Илья Глазунов 2026-02-15 00:34:37 +03:00
parent 494e692738
commit 4cf48ee0af
7 changed files with 5415 additions and 4431 deletions

View File

@ -1,12 +1,12 @@
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js";
import { MainView } from '../views/MainView.js'; import { MainView } from "../views/MainView.js";
import { CustomizeView } from '../views/CustomizeView.js'; import { CustomizeView } from "../views/CustomizeView.js";
import { HelpView } from '../views/HelpView.js'; import { HelpView } from "../views/HelpView.js";
import { HistoryView } from '../views/HistoryView.js'; import { HistoryView } from "../views/HistoryView.js";
import { AssistantView } from '../views/AssistantView.js'; import { AssistantView } from "../views/AssistantView.js";
import { OnboardingView } from '../views/OnboardingView.js'; import { OnboardingView } from "../views/OnboardingView.js";
import { AICustomizeView } from '../views/AICustomizeView.js'; import { AICustomizeView } from "../views/AICustomizeView.js";
import { FeedbackView } from '../views/FeedbackView.js'; import { FeedbackView } from "../views/FeedbackView.js";
export class CheatingDaddyApp extends LitElement { export class CheatingDaddyApp extends LitElement {
static styles = css` static styles = css`
@ -81,15 +81,15 @@ export class CheatingDaddyApp extends LitElement {
} }
.traffic-light.close { .traffic-light.close {
background: #FF5F57; background: #ff5f57;
} }
.traffic-light.minimize { .traffic-light.minimize {
background: #FEBC2E; background: #febc2e;
} }
.traffic-light.maximize { .traffic-light.maximize {
background: #28C840; background: #28c840;
} }
.sidebar { .sidebar {
@ -100,7 +100,10 @@ export class CheatingDaddyApp extends LitElement {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 42px 0 var(--space-md) 0; padding: 42px 0 var(--space-md) 0;
transition: width var(--transition), min-width var(--transition), opacity var(--transition); transition:
width var(--transition),
min-width var(--transition),
opacity var(--transition);
} }
.sidebar.hidden { .sidebar.hidden {
@ -144,7 +147,9 @@ export class CheatingDaddyApp extends LitElement {
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
cursor: pointer; cursor: pointer;
transition: color var(--transition), background var(--transition); transition:
color var(--transition),
background var(--transition);
border: none; border: none;
background: none; background: none;
width: 100%; width: 100%;
@ -187,7 +192,9 @@ export class CheatingDaddyApp extends LitElement {
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
transition: background var(--transition), border-color var(--transition); transition:
background var(--transition),
border-color var(--transition);
animation: update-wobble 5s ease-in-out infinite; animation: update-wobble 5s ease-in-out infinite;
} }
@ -197,11 +204,23 @@ export class CheatingDaddyApp extends LitElement {
} }
@keyframes update-wobble { @keyframes update-wobble {
0%, 90%, 100% { transform: rotate(0deg); } 0%,
92% { transform: rotate(-2deg); } 90%,
94% { transform: rotate(2deg); } 100% {
96% { transform: rotate(-1.5deg); } transform: rotate(0deg);
98% { transform: rotate(1.5deg); } }
92% {
transform: rotate(-2deg);
}
94% {
transform: rotate(2deg);
}
96% {
transform: rotate(-1.5deg);
}
98% {
transform: rotate(1.5deg);
}
} }
.update-btn svg { .update-btn svg {
@ -367,16 +386,16 @@ export class CheatingDaddyApp extends LitElement {
constructor() { constructor() {
super(); super();
this.currentView = 'main'; this.currentView = "main";
this.statusText = ''; this.statusText = "";
this.startTime = null; this.startTime = null;
this.isRecording = false; this.isRecording = false;
this.sessionActive = false; this.sessionActive = false;
this.selectedProfile = 'interview'; this.selectedProfile = "interview";
this.selectedLanguage = 'en-US'; this.selectedLanguage = "en-US";
this.selectedScreenshotInterval = '5'; this.selectedScreenshotInterval = "5";
this.selectedImageQuality = 'medium'; this.selectedImageQuality = "medium";
this.layoutMode = 'normal'; this.layoutMode = "normal";
this.responses = []; this.responses = [];
this.currentResponseIndex = -1; this.currentResponseIndex = -1;
this._viewInstances = new Map(); this._viewInstances = new Map();
@ -388,7 +407,7 @@ export class CheatingDaddyApp extends LitElement {
this._timerInterval = null; this._timerInterval = null;
this._updateAvailable = false; this._updateAvailable = false;
this._whisperDownloading = false; this._whisperDownloading = false;
this._localVersion = ''; this._localVersion = "";
this._loadFromStorage(); this._loadFromStorage();
this._checkForUpdates(); this._checkForUpdates();
@ -399,16 +418,22 @@ export class CheatingDaddyApp extends LitElement {
this._localVersion = await cheatingDaddy.getVersion(); this._localVersion = await cheatingDaddy.getVersion();
this.requestUpdate(); this.requestUpdate();
const res = await fetch('https://raw.githubusercontent.com/ShiftyX1/Mastermind/refs/heads/master/package.json'); const res = await fetch(
"https://raw.githubusercontent.com/ShiftyX1/Mastermind/refs/heads/master/package.json",
);
if (!res.ok) return; if (!res.ok) return;
const remote = await res.json(); const remote = await res.json();
const remoteVersion = remote.version; const remoteVersion = remote.version;
const toNum = v => v.split('.').map(Number); const toNum = (v) => v.split(".").map(Number);
const [rMaj, rMin, rPatch] = toNum(remoteVersion); const [rMaj, rMin, rPatch] = toNum(remoteVersion);
const [lMaj, lMin, lPatch] = toNum(this._localVersion); const [lMaj, lMin, lPatch] = toNum(this._localVersion);
if (rMaj > lMaj || (rMaj === lMaj && rMin > lMin) || (rMaj === lMaj && rMin === lMin && rPatch > lPatch)) { if (
rMaj > lMaj ||
(rMaj === lMaj && rMin > lMin) ||
(rMaj === lMaj && rMin === lMin && rPatch > lPatch)
) {
this._updateAvailable = true; this._updateAvailable = true;
this.requestUpdate(); this.requestUpdate();
} }
@ -421,20 +446,20 @@ export class CheatingDaddyApp extends LitElement {
try { try {
const [config, prefs] = await Promise.all([ const [config, prefs] = await Promise.all([
cheatingDaddy.storage.getConfig(), cheatingDaddy.storage.getConfig(),
cheatingDaddy.storage.getPreferences() cheatingDaddy.storage.getPreferences(),
]); ]);
this.currentView = config.onboarded ? 'main' : 'onboarding'; this.currentView = config.onboarded ? "main" : "onboarding";
this.selectedProfile = prefs.selectedProfile || 'interview'; this.selectedProfile = prefs.selectedProfile || "interview";
this.selectedLanguage = prefs.selectedLanguage || 'en-US'; this.selectedLanguage = prefs.selectedLanguage || "en-US";
this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || '5'; this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || "5";
this.selectedImageQuality = prefs.selectedImageQuality || 'medium'; this.selectedImageQuality = prefs.selectedImageQuality || "medium";
this.layoutMode = config.layout || 'normal'; this.layoutMode = config.layout || "normal";
this._storageLoaded = true; this._storageLoaded = true;
this.requestUpdate(); this.requestUpdate();
} catch (error) { } catch (error) {
console.error('Error loading from storage:', error); console.error("Error loading from storage:", error);
this._storageLoaded = true; this._storageLoaded = true;
this.requestUpdate(); this.requestUpdate();
} }
@ -444,13 +469,23 @@ export class CheatingDaddyApp extends LitElement {
super.connectedCallback(); super.connectedCallback();
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.on('new-response', (_, response) => this.addNewResponse(response)); ipcRenderer.on("new-response", (_, response) =>
ipcRenderer.on('update-response', (_, response) => this.updateCurrentResponse(response)); this.addNewResponse(response),
ipcRenderer.on('update-status', (_, status) => this.setStatus(status)); );
ipcRenderer.on('click-through-toggled', (_, isEnabled) => { this._isClickThrough = isEnabled; }); ipcRenderer.on("update-response", (_, response) =>
ipcRenderer.on('reconnect-failed', (_, data) => this.addNewResponse(data.message)); this.updateCurrentResponse(response),
ipcRenderer.on('whisper-downloading', (_, downloading) => { this._whisperDownloading = downloading; }); );
ipcRenderer.on("update-status", (_, status) => this.setStatus(status));
ipcRenderer.on("click-through-toggled", (_, isEnabled) => {
this._isClickThrough = isEnabled;
});
ipcRenderer.on("reconnect-failed", (_, data) =>
this.addNewResponse(data.message),
);
ipcRenderer.on("whisper-downloading", (_, downloading) => {
this._whisperDownloading = downloading;
});
} }
} }
@ -458,13 +493,13 @@ export class CheatingDaddyApp extends LitElement {
super.disconnectedCallback(); super.disconnectedCallback();
this._stopTimer(); this._stopTimer();
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.removeAllListeners('new-response'); ipcRenderer.removeAllListeners("new-response");
ipcRenderer.removeAllListeners('update-response'); ipcRenderer.removeAllListeners("update-response");
ipcRenderer.removeAllListeners('update-status'); ipcRenderer.removeAllListeners("update-status");
ipcRenderer.removeAllListeners('click-through-toggled'); ipcRenderer.removeAllListeners("click-through-toggled");
ipcRenderer.removeAllListeners('reconnect-failed'); ipcRenderer.removeAllListeners("reconnect-failed");
ipcRenderer.removeAllListeners('whisper-downloading'); ipcRenderer.removeAllListeners("whisper-downloading");
} }
} }
@ -485,12 +520,12 @@ export class CheatingDaddyApp extends LitElement {
} }
getElapsedTime() { getElapsedTime() {
if (!this.startTime) return '0:00'; if (!this.startTime) return "0:00";
const elapsed = Math.floor((Date.now() - this.startTime) / 1000); const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
const h = Math.floor(elapsed / 3600); const h = Math.floor(elapsed / 3600);
const m = Math.floor((elapsed % 3600) / 60); const m = Math.floor((elapsed % 3600) / 60);
const s = elapsed % 60; const s = elapsed % 60;
const pad = n => String(n).padStart(2, '0'); const pad = (n) => String(n).padStart(2, "0");
if (h > 0) return `${h}:${pad(m)}:${pad(s)}`; if (h > 0) return `${h}:${pad(m)}:${pad(s)}`;
return `${m}:${pad(s)}`; return `${m}:${pad(s)}`;
} }
@ -499,7 +534,11 @@ export class CheatingDaddyApp extends LitElement {
setStatus(text) { setStatus(text) {
this.statusText = text; this.statusText = text;
if (text.includes('Ready') || text.includes('Listening') || text.includes('Error')) { if (
text.includes("Ready") ||
text.includes("Listening") ||
text.includes("Error")
) {
this._currentResponseIsComplete = true; this._currentResponseIsComplete = true;
} }
} }
@ -531,34 +570,34 @@ export class CheatingDaddyApp extends LitElement {
} }
async handleClose() { async handleClose() {
if (this.currentView === 'assistant') { if (this.currentView === "assistant") {
cheatingDaddy.stopCapture(); cheatingDaddy.stopCapture();
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('close-session'); await ipcRenderer.invoke("close-session");
} }
this.sessionActive = false; this.sessionActive = false;
this._stopTimer(); this._stopTimer();
this.currentView = 'main'; this.currentView = "main";
} else { } else {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('quit-application'); await ipcRenderer.invoke("quit-application");
} }
} }
} }
async _handleMinimize() { async _handleMinimize() {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('window-minimize'); await ipcRenderer.invoke("window-minimize");
} }
} }
async handleHideToggle() { async handleHideToggle() {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('toggle-window-visibility'); await ipcRenderer.invoke("toggle-window-visibility");
} }
} }
@ -566,12 +605,12 @@ export class CheatingDaddyApp extends LitElement {
async handleStart() { async handleStart() {
const prefs = await cheatingDaddy.storage.getPreferences(); const prefs = await cheatingDaddy.storage.getPreferences();
const providerMode = prefs.providerMode || 'byok'; const providerMode = prefs.providerMode || "byok";
if (providerMode === 'local') { if (providerMode === "local") {
const success = await cheatingDaddy.initializeLocal(this.selectedProfile); const success = await cheatingDaddy.initializeLocal(this.selectedProfile);
if (!success) { if (!success) {
const mainView = this.shadowRoot.querySelector('main-view'); const mainView = this.shadowRoot.querySelector("main-view");
if (mainView && mainView.triggerApiKeyError) { if (mainView && mainView.triggerApiKeyError) {
mainView.triggerApiKeyError(); mainView.triggerApiKeyError();
} }
@ -579,37 +618,49 @@ export class CheatingDaddyApp extends LitElement {
} }
} else { } else {
const apiKey = await cheatingDaddy.storage.getApiKey(); const apiKey = await cheatingDaddy.storage.getApiKey();
if (!apiKey || apiKey === '') { if (!apiKey || apiKey === "") {
const mainView = this.shadowRoot.querySelector('main-view'); const mainView = this.shadowRoot.querySelector("main-view");
if (mainView && mainView.triggerApiKeyError) { if (mainView && mainView.triggerApiKeyError) {
mainView.triggerApiKeyError(); mainView.triggerApiKeyError();
} }
return; return;
} }
await cheatingDaddy.initializeGemini(this.selectedProfile, this.selectedLanguage); await cheatingDaddy.initializeGemini(
this.selectedProfile,
this.selectedLanguage,
);
} }
cheatingDaddy.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality); cheatingDaddy.startCapture(
this.selectedScreenshotInterval,
this.selectedImageQuality,
);
this.responses = []; this.responses = [];
this.currentResponseIndex = -1; this.currentResponseIndex = -1;
this.startTime = Date.now(); this.startTime = Date.now();
this.sessionActive = true; this.sessionActive = true;
this.currentView = 'assistant'; this.currentView = "assistant";
this._startTimer(); this._startTimer();
} }
async handleAPIKeyHelp() { async handleAPIKeyHelp() {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('open-external', 'https://cheatingdaddy.com/help/api-key'); await ipcRenderer.invoke(
"open-external",
"https://cheatingdaddy.com/help/api-key",
);
} }
} }
async handleGroqAPIKeyHelp() { async handleGroqAPIKeyHelp() {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('open-external', 'https://console.groq.com/keys'); await ipcRenderer.invoke(
"open-external",
"https://console.groq.com/keys",
);
} }
} }
@ -617,33 +668,39 @@ export class CheatingDaddyApp extends LitElement {
async handleProfileChange(profile) { async handleProfileChange(profile) {
this.selectedProfile = profile; this.selectedProfile = profile;
await cheatingDaddy.storage.updatePreference('selectedProfile', profile); await cheatingDaddy.storage.updatePreference("selectedProfile", profile);
} }
async handleLanguageChange(language) { async handleLanguageChange(language) {
this.selectedLanguage = language; this.selectedLanguage = language;
await cheatingDaddy.storage.updatePreference('selectedLanguage', language); await cheatingDaddy.storage.updatePreference("selectedLanguage", language);
} }
async handleScreenshotIntervalChange(interval) { async handleScreenshotIntervalChange(interval) {
this.selectedScreenshotInterval = interval; this.selectedScreenshotInterval = interval;
await cheatingDaddy.storage.updatePreference('selectedScreenshotInterval', interval); await cheatingDaddy.storage.updatePreference(
"selectedScreenshotInterval",
interval,
);
} }
async handleImageQualityChange(quality) { async handleImageQualityChange(quality) {
this.selectedImageQuality = quality; this.selectedImageQuality = quality;
await cheatingDaddy.storage.updatePreference('selectedImageQuality', quality); await cheatingDaddy.storage.updatePreference(
"selectedImageQuality",
quality,
);
} }
async handleLayoutModeChange(layoutMode) { async handleLayoutModeChange(layoutMode) {
this.layoutMode = layoutMode; this.layoutMode = layoutMode;
await cheatingDaddy.storage.updateConfig('layout', layoutMode); await cheatingDaddy.storage.updateConfig("layout", layoutMode);
if (window.require) { if (window.require) {
try { try {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('update-sizes'); await ipcRenderer.invoke("update-sizes");
} catch (error) { } catch (error) {
console.error('Failed to update sizes:', error); console.error("Failed to update sizes:", error);
} }
} }
this.requestUpdate(); this.requestUpdate();
@ -651,17 +708,27 @@ export class CheatingDaddyApp extends LitElement {
async handleExternalLinkClick(url) { async handleExternalLinkClick(url) {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('open-external', url); await ipcRenderer.invoke("open-external", url);
} }
} }
async handleSendText(message) { async handleSendText(message) {
const result = await window.cheatingDaddy.sendTextMessage(message); const result = await window.cheatingDaddy.sendTextMessage(message);
if (!result.success) { if (!result.success) {
this.setStatus('Error sending message: ' + result.error); this.setStatus("Error sending message: " + result.error);
} else { } else {
this.setStatus('Message sent...'); this.setStatus("Message sent...");
this._awaitingNewResponse = true;
}
}
async handleExpandResponse() {
const result = await window.cheatingDaddy.expandLastResponse();
if (!result.success) {
this.setStatus("Error expanding: " + (result.error || "Unknown error"));
} else {
this.setStatus("Expanding response...");
this._awaitingNewResponse = true; this._awaitingNewResponse = true;
} }
} }
@ -673,29 +740,29 @@ export class CheatingDaddyApp extends LitElement {
} }
handleOnboardingComplete() { handleOnboardingComplete() {
this.currentView = 'main'; this.currentView = "main";
} }
updated(changedProperties) { updated(changedProperties) {
super.updated(changedProperties); super.updated(changedProperties);
if (changedProperties.has('currentView') && window.require) { if (changedProperties.has("currentView") && window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.send('view-changed', this.currentView); ipcRenderer.send("view-changed", this.currentView);
} }
} }
// ── Helpers ── // ── Helpers ──
_isLiveMode() { _isLiveMode() {
return this.currentView === 'assistant'; return this.currentView === "assistant";
} }
// ── Render ── // ── Render ──
renderCurrentView() { renderCurrentView() {
switch (this.currentView) { switch (this.currentView) {
case 'onboarding': case "onboarding":
return html` return html`
<onboarding-view <onboarding-view
.onComplete=${() => this.handleOnboardingComplete()} .onComplete=${() => this.handleOnboardingComplete()}
@ -703,26 +770,26 @@ export class CheatingDaddyApp extends LitElement {
></onboarding-view> ></onboarding-view>
`; `;
case 'main': case "main":
return html` return html`
<main-view <main-view
.selectedProfile=${this.selectedProfile} .selectedProfile=${this.selectedProfile}
.onProfileChange=${p => this.handleProfileChange(p)} .onProfileChange=${(p) => this.handleProfileChange(p)}
.onStart=${() => this.handleStart()} .onStart=${() => this.handleStart()}
.onExternalLink=${url => this.handleExternalLinkClick(url)} .onExternalLink=${(url) => this.handleExternalLinkClick(url)}
.whisperDownloading=${this._whisperDownloading} .whisperDownloading=${this._whisperDownloading}
></main-view> ></main-view>
`; `;
case 'ai-customize': case "ai-customize":
return html` return html`
<ai-customize-view <ai-customize-view
.selectedProfile=${this.selectedProfile} .selectedProfile=${this.selectedProfile}
.onProfileChange=${p => this.handleProfileChange(p)} .onProfileChange=${(p) => this.handleProfileChange(p)}
></ai-customize-view> ></ai-customize-view>
`; `;
case 'customize': case "customize":
return html` return html`
<customize-view <customize-view
.selectedProfile=${this.selectedProfile} .selectedProfile=${this.selectedProfile}
@ -730,30 +797,34 @@ export class CheatingDaddyApp extends LitElement {
.selectedScreenshotInterval=${this.selectedScreenshotInterval} .selectedScreenshotInterval=${this.selectedScreenshotInterval}
.selectedImageQuality=${this.selectedImageQuality} .selectedImageQuality=${this.selectedImageQuality}
.layoutMode=${this.layoutMode} .layoutMode=${this.layoutMode}
.onProfileChange=${p => this.handleProfileChange(p)} .onProfileChange=${(p) => this.handleProfileChange(p)}
.onLanguageChange=${l => this.handleLanguageChange(l)} .onLanguageChange=${(l) => this.handleLanguageChange(l)}
.onScreenshotIntervalChange=${i => this.handleScreenshotIntervalChange(i)} .onScreenshotIntervalChange=${(i) =>
.onImageQualityChange=${q => this.handleImageQualityChange(q)} this.handleScreenshotIntervalChange(i)}
.onLayoutModeChange=${lm => this.handleLayoutModeChange(lm)} .onImageQualityChange=${(q) => this.handleImageQualityChange(q)}
.onLayoutModeChange=${(lm) => this.handleLayoutModeChange(lm)}
></customize-view> ></customize-view>
`; `;
case 'feedback': case "feedback":
return html`<feedback-view></feedback-view>`; return html`<feedback-view></feedback-view>`;
case 'help': case "help":
return html`<help-view .onExternalLinkClick=${url => this.handleExternalLinkClick(url)}></help-view>`; return html`<help-view
.onExternalLinkClick=${(url) => this.handleExternalLinkClick(url)}
></help-view>`;
case 'history': case "history":
return html`<history-view></history-view>`; return html`<history-view></history-view>`;
case 'assistant': case "assistant":
return html` return html`
<assistant-view <assistant-view
.responses=${this.responses} .responses=${this.responses}
.currentResponseIndex=${this.currentResponseIndex} .currentResponseIndex=${this.currentResponseIndex}
.selectedProfile=${this.selectedProfile} .selectedProfile=${this.selectedProfile}
.onSendText=${msg => this.handleSendText(msg)} .onSendText=${(msg) => this.handleSendText(msg)}
.onExpandResponse=${() => this.handleExpandResponse()}
.shouldAnimateResponse=${this.shouldAnimateResponse} .shouldAnimateResponse=${this.shouldAnimateResponse}
@response-index-changed=${this.handleResponseIndexChanged} @response-index-changed=${this.handleResponseIndexChanged}
@response-animation-complete=${() => { @response-animation-complete=${() => {
@ -771,74 +842,238 @@ export class CheatingDaddyApp extends LitElement {
renderSidebar() { renderSidebar() {
const items = [ const items = [
{ id: 'main', label: 'Home', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m19 8.71l-5.333-4.148a2.666 2.666 0 0 0-3.274 0L5.059 8.71a2.67 2.67 0 0 0-1.029 2.105v7.2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.2c0-.823-.38-1.6-1.03-2.105"/><path d="M16 15c-2.21 1.333-5.792 1.333-8 0"/></g></svg>` }, {
{ id: 'ai-customize', label: 'AI Customization', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 3v7h6l-8 11v-7H5z" /></svg>` }, id: "main",
{ id: 'history', label: 'History', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M10 20.777a9 9 0 0 1-2.48-.969M14 3.223a9.003 9.003 0 0 1 0 17.554m-9.421-3.684a9 9 0 0 1-1.227-2.592M3.124 10.5c.16-.95.468-1.85.9-2.675l.169-.305m2.714-2.941A9 9 0 0 1 10 3.223"/><path d="M12 8v4l3 3"/></g></svg>` }, label: "Home",
{ id: 'customize', label: 'Settings', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M19.875 6.27A2.23 2.23 0 0 1 21 8.218v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 4.27a2.27 2.27 0 0 1-2.184 0l-6.75-4.27A2.23 2.23 0 0 1 3 15.502V8.217c0-.809.443-1.554 1.158-1.947l6.75-3.98a2.33 2.33 0 0 1 2.25 0l6.75 3.98z"/><path d="M9 12a3 3 0 1 0 6 0a3 3 0 1 0-6 0"/></g></svg>` }, icon: html`<svg
{ id: 'feedback', label: 'Feedback', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-5l-5 3v-3H6a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3zM9.5 9h.01m4.99 0h.01"/><path d="M9.5 13a3.5 3.5 0 0 0 5 0"/></g></svg>` }, xmlns="http://www.w3.org/2000/svg"
{ id: 'help', label: 'Help', icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9s-9-1.8-9-9s1.8-9 9-9m0 13v.01"/><path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483"/></g></svg>` }, width="16"
height="16"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
d="m19 8.71l-5.333-4.148a2.666 2.666 0 0 0-3.274 0L5.059 8.71a2.67 2.67 0 0 0-1.029 2.105v7.2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.2c0-.823-.38-1.6-1.03-2.105"
/>
<path d="M16 15c-2.21 1.333-5.792 1.333-8 0" />
</g>
</svg>`,
},
{
id: "ai-customize",
label: "AI Customization",
icon: html`<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 3v7h6l-8 11v-7H5z"
/>
</svg>`,
},
{
id: "history",
label: "History",
icon: html`<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
d="M10 20.777a9 9 0 0 1-2.48-.969M14 3.223a9.003 9.003 0 0 1 0 17.554m-9.421-3.684a9 9 0 0 1-1.227-2.592M3.124 10.5c.16-.95.468-1.85.9-2.675l.169-.305m2.714-2.941A9 9 0 0 1 10 3.223"
/>
<path d="M12 8v4l3 3" />
</g>
</svg>`,
},
{
id: "customize",
label: "Settings",
icon: html`<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
d="M19.875 6.27A2.23 2.23 0 0 1 21 8.218v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 4.27a2.27 2.27 0 0 1-2.184 0l-6.75-4.27A2.23 2.23 0 0 1 3 15.502V8.217c0-.809.443-1.554 1.158-1.947l6.75-3.98a2.33 2.33 0 0 1 2.25 0l6.75 3.98z"
/>
<path d="M9 12a3 3 0 1 0 6 0a3 3 0 1 0-6 0" />
</g>
</svg>`,
},
{
id: "feedback",
label: "Feedback",
icon: html`<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3h-5l-5 3v-3H6a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3zM9.5 9h.01m4.99 0h.01"
/>
<path d="M9.5 13a3.5 3.5 0 0 0 5 0" />
</g>
</svg>`,
},
{
id: "help",
label: "Help",
icon: html`<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
d="M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9s-9-1.8-9-9s1.8-9 9-9m0 13v.01"
/>
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
</g>
</svg>`,
},
]; ];
return html` return html`
<div class="sidebar ${this._isLiveMode() ? 'hidden' : ''}"> <div class="sidebar ${this._isLiveMode() ? "hidden" : ""}">
<div class="sidebar-brand"> <div class="sidebar-brand">
<h1>Mastermind</h1> <h1>Mastermind</h1>
</div> </div>
<nav class="sidebar-nav"> <nav class="sidebar-nav">
${items.map(item => html` ${items.map(
(item) => html`
<button <button
class="nav-item ${this.currentView === item.id ? 'active' : ''}" class="nav-item ${this.currentView === item.id ? "active" : ""}"
@click=${() => this.navigate(item.id)} @click=${() => this.navigate(item.id)}
title=${item.label} title=${item.label}
> >
${item.icon} ${item.icon} ${item.label}
${item.label}
</button> </button>
`)} `,
)}
</nav> </nav>
<div class="sidebar-footer"> <div class="sidebar-footer">
${this._updateAvailable ? html` ${this._updateAvailable
<button class="update-btn" @click=${() => this.handleExternalLinkClick('https://cheatingdaddy.com/download')}> ? html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 11l5 5l5-5m-5-7v12" /></svg> <button
class="update-btn"
@click=${() =>
this.handleExternalLinkClick(
"https://cheatingdaddy.com/download",
)}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2M7 11l5 5l5-5m-5-7v12"
/>
</svg>
Update available Update available
</button> </button>
` : html` `
<div class="version-text">v${this._localVersion}</div> : html` <div class="version-text">v${this._localVersion}</div> `}
`}
</div> </div>
</div> </div>
`; `;
} }
renderLiveBar() { renderLiveBar() {
if (!this._isLiveMode()) return ''; if (!this._isLiveMode()) return "";
const profileLabels = { const profileLabels = {
interview: 'Interview', interview: "Interview",
sales: 'Sales Call', sales: "Sales Call",
meeting: 'Meeting', meeting: "Meeting",
presentation: 'Presentation', presentation: "Presentation",
negotiation: 'Negotiation', negotiation: "Negotiation",
exam: 'Exam', exam: "Exam",
}; };
return html` return html`
<div class="live-bar"> <div class="live-bar">
<div class="live-bar-left"> <div class="live-bar-left">
<button class="live-bar-back" @click=${() => this.handleClose()} title="End session"> <button
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> class="live-bar-back"
<path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 0 1-.02 1.06L8.832 10l3.938 3.71a.75.75 0 1 1-1.04 1.08l-4.5-4.25a.75.75 0 0 1 0-1.08l4.5-4.25a.75.75 0 0 1 1.06.02Z" clip-rule="evenodd" /> @click=${() => this.handleClose()}
title="End session"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M12.79 5.23a.75.75 0 0 1-.02 1.06L8.832 10l3.938 3.71a.75.75 0 1 1-1.04 1.08l-4.5-4.25a.75.75 0 0 1 0-1.08l4.5-4.25a.75.75 0 0 1 1.06.02Z"
clip-rule="evenodd"
/>
</svg> </svg>
</button> </button>
</div> </div>
<div class="live-bar-center"> <div class="live-bar-center">
${profileLabels[this.selectedProfile] || 'Session'} ${profileLabels[this.selectedProfile] || "Session"}
</div> </div>
<div class="live-bar-right"> <div class="live-bar-right">
${this.statusText ? html`<span class="live-bar-text">${this.statusText}</span>` : ''} ${this.statusText
? html`<span class="live-bar-text">${this.statusText}</span>`
: ""}
<span class="live-bar-text">${this.getElapsedTime()}</span> <span class="live-bar-text">${this.getElapsedTime()}</span>
${this._isClickThrough ? html`<span class="live-bar-text">[click through]</span>` : ''} ${this._isClickThrough
<span class="live-bar-text clickable" @click=${() => this.handleHideToggle()}>[hide]</span> ? html`<span class="live-bar-text">[click through]</span>`
: ""}
<span
class="live-bar-text clickable"
@click=${() => this.handleHideToggle()}
>[hide]</span
>
</div> </div>
</div> </div>
`; `;
@ -846,30 +1081,34 @@ export class CheatingDaddyApp extends LitElement {
render() { render() {
// Onboarding is fullscreen, no sidebar // Onboarding is fullscreen, no sidebar
if (this.currentView === 'onboarding') { if (this.currentView === "onboarding") {
return html` return html` <div class="fullscreen">${this.renderCurrentView()}</div> `;
<div class="fullscreen">
${this.renderCurrentView()}
</div>
`;
} }
const isLive = this._isLiveMode(); const isLive = this._isLiveMode();
return html` return html`
<div class="app-shell"> <div class="app-shell">
<div class="top-drag-bar ${isLive ? 'hidden' : ''}"> <div class="top-drag-bar ${isLive ? "hidden" : ""}">
<div class="traffic-lights"> <div class="traffic-lights">
<button class="traffic-light close" @click=${() => this.handleClose()} title="Close"></button> <button
<button class="traffic-light minimize" @click=${() => this._handleMinimize()} title="Minimize"></button> class="traffic-light close"
@click=${() => this.handleClose()}
title="Close"
></button>
<button
class="traffic-light minimize"
@click=${() => this._handleMinimize()}
title="Minimize"
></button>
<button class="traffic-light maximize" title="Maximize"></button> <button class="traffic-light maximize" title="Maximize"></button>
</div> </div>
<div class="drag-region"></div> <div class="drag-region"></div>
</div> </div>
${this.renderSidebar()} ${this.renderSidebar()}
<div class="content"> <div class="content">
${isLive ? this.renderLiveBar() : ''} ${isLive ? this.renderLiveBar() : ""}
<div class="content-inner ${isLive ? 'live' : ''}"> <div class="content-inner ${isLive ? "live" : ""}">
${this.renderCurrentView()} ${this.renderCurrentView()}
</div> </div>
</div> </div>
@ -878,4 +1117,4 @@ export class CheatingDaddyApp extends LitElement {
} }
} }
customElements.define('cheating-daddy-app', CheatingDaddyApp); customElements.define("cheating-daddy-app", CheatingDaddyApp);

View File

@ -1,4 +1,4 @@
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js";
export class AssistantView extends LitElement { export class AssistantView extends LitElement {
static styles = css` static styles = css`
@ -54,12 +54,22 @@ export class AssistantView extends LitElement {
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
} }
.response-container h1 { font-size: 1.5em; } .response-container h1 {
.response-container h2 { font-size: 1.3em; } font-size: 1.5em;
.response-container h3 { font-size: 1.15em; } }
.response-container h4 { font-size: 1.05em; } .response-container h2 {
font-size: 1.3em;
}
.response-container h3 {
font-size: 1.15em;
}
.response-container h4 {
font-size: 1.05em;
}
.response-container h5, .response-container h5,
.response-container h6 { font-size: 1em; } .response-container h6 {
font-size: 1em;
}
.response-container p { .response-container p {
margin: 0.6em 0; margin: 0.6em 0;
@ -263,7 +273,9 @@ export class AssistantView extends LitElement {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
transition: border-color 0.4s ease, background var(--transition); transition:
border-color 0.4s ease,
background var(--transition);
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
} }
@ -298,6 +310,52 @@ export class AssistantView extends LitElement {
height: calc(100% + 2px); height: calc(100% + 2px);
pointer-events: none; pointer-events: none;
} }
/* ── Expand button ── */
.expand-bar {
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-xs) var(--space-md);
border-top: 1px solid var(--border);
background: var(--bg-app);
}
.expand-btn {
display: flex;
align-items: center;
gap: 4px;
background: none;
border: 1px solid var(--border);
color: var(--text-secondary);
cursor: pointer;
font-size: var(--font-size-xs);
font-family: var(--font-mono);
padding: var(--space-xs) var(--space-md);
border-radius: 100px;
height: 26px;
transition:
color var(--transition),
border-color var(--transition),
background var(--transition);
}
.expand-btn:hover:not(:disabled) {
color: var(--text-primary);
border-color: var(--accent);
background: var(--bg-surface);
}
.expand-btn:disabled {
opacity: 0.4;
cursor: default;
}
.expand-btn svg {
width: 12px;
height: 12px;
}
`; `;
static properties = { static properties = {
@ -305,28 +363,32 @@ export class AssistantView extends LitElement {
currentResponseIndex: { type: Number }, currentResponseIndex: { type: Number },
selectedProfile: { type: String }, selectedProfile: { type: String },
onSendText: { type: Function }, onSendText: { type: Function },
onExpandResponse: { type: Function },
shouldAnimateResponse: { type: Boolean }, shouldAnimateResponse: { type: Boolean },
isAnalyzing: { type: Boolean, state: true }, isAnalyzing: { type: Boolean, state: true },
isExpanding: { type: Boolean, state: true },
}; };
constructor() { constructor() {
super(); super();
this.responses = []; this.responses = [];
this.currentResponseIndex = -1; this.currentResponseIndex = -1;
this.selectedProfile = 'interview'; this.selectedProfile = "interview";
this.onSendText = () => {}; this.onSendText = () => {};
this.onExpandResponse = () => {};
this.isAnalyzing = false; this.isAnalyzing = false;
this.isExpanding = false;
this._animFrame = null; this._animFrame = null;
} }
getProfileNames() { getProfileNames() {
return { return {
interview: 'Job Interview', interview: "Job Interview",
sales: 'Sales Call', sales: "Sales Call",
meeting: 'Business Meeting', meeting: "Business Meeting",
presentation: 'Presentation', presentation: "Presentation",
negotiation: 'Negotiation', negotiation: "Negotiation",
exam: 'Exam Assistant', exam: "Exam Assistant",
}; };
} }
@ -334,11 +396,11 @@ export class AssistantView extends LitElement {
const profileNames = this.getProfileNames(); const profileNames = this.getProfileNames();
return this.responses.length > 0 && this.currentResponseIndex >= 0 return this.responses.length > 0 && this.currentResponseIndex >= 0
? this.responses[this.currentResponseIndex] ? this.responses[this.currentResponseIndex]
: `Listening to your ${profileNames[this.selectedProfile] || 'session'}...`; : `Listening to your ${profileNames[this.selectedProfile] || "session"}...`;
} }
renderMarkdown(content) { renderMarkdown(content) {
if (typeof window !== 'undefined' && window.marked) { if (typeof window !== "undefined" && window.marked) {
try { try {
window.marked.setOptions({ window.marked.setOptions({
breaks: true, breaks: true,
@ -349,7 +411,7 @@ export class AssistantView extends LitElement {
rendered = this.wrapWordsInSpans(rendered); rendered = this.wrapWordsInSpans(rendered);
return rendered; return rendered;
} catch (error) { } catch (error) {
console.warn('Error parsing markdown:', error); console.warn("Error parsing markdown:", error);
return content; return content;
} }
} }
@ -358,17 +420,21 @@ export class AssistantView extends LitElement {
wrapWordsInSpans(html) { wrapWordsInSpans(html) {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html'); const doc = parser.parseFromString(html, "text/html");
const tagsToSkip = ['PRE']; const tagsToSkip = ["PRE"];
function wrap(node) { function wrap(node) {
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && !tagsToSkip.includes(node.parentNode.tagName)) { if (
node.nodeType === Node.TEXT_NODE &&
node.textContent.trim() &&
!tagsToSkip.includes(node.parentNode.tagName)
) {
const words = node.textContent.split(/(\s+)/); const words = node.textContent.split(/(\s+)/);
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
words.forEach(word => { words.forEach((word) => {
if (word.trim()) { if (word.trim()) {
const span = document.createElement('span'); const span = document.createElement("span");
span.setAttribute('data-word', ''); span.setAttribute("data-word", "");
span.textContent = word; span.textContent = word;
frag.appendChild(span); frag.appendChild(span);
} else { } else {
@ -376,7 +442,10 @@ export class AssistantView extends LitElement {
} }
}); });
node.parentNode.replaceChild(frag, node); node.parentNode.replaceChild(frag, node);
} else if (node.nodeType === Node.ELEMENT_NODE && !tagsToSkip.includes(node.tagName)) { } else if (
node.nodeType === Node.ELEMENT_NODE &&
!tagsToSkip.includes(node.tagName)
) {
Array.from(node.childNodes).forEach(wrap); Array.from(node.childNodes).forEach(wrap);
} }
} }
@ -388,9 +457,9 @@ export class AssistantView extends LitElement {
if (this.currentResponseIndex > 0) { if (this.currentResponseIndex > 0) {
this.currentResponseIndex--; this.currentResponseIndex--;
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('response-index-changed', { new CustomEvent("response-index-changed", {
detail: { index: this.currentResponseIndex }, detail: { index: this.currentResponseIndex },
}) }),
); );
this.requestUpdate(); this.requestUpdate();
} }
@ -400,16 +469,16 @@ export class AssistantView extends LitElement {
if (this.currentResponseIndex < this.responses.length - 1) { if (this.currentResponseIndex < this.responses.length - 1) {
this.currentResponseIndex++; this.currentResponseIndex++;
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('response-index-changed', { new CustomEvent("response-index-changed", {
detail: { index: this.currentResponseIndex }, detail: { index: this.currentResponseIndex },
}) }),
); );
this.requestUpdate(); this.requestUpdate();
} }
} }
scrollResponseUp() { scrollResponseUp() {
const container = this.shadowRoot.querySelector('.response-container'); const container = this.shadowRoot.querySelector(".response-container");
if (container) { if (container) {
const scrollAmount = container.clientHeight * 0.3; const scrollAmount = container.clientHeight * 0.3;
container.scrollTop = Math.max(0, container.scrollTop - scrollAmount); container.scrollTop = Math.max(0, container.scrollTop - scrollAmount);
@ -417,10 +486,13 @@ export class AssistantView extends LitElement {
} }
scrollResponseDown() { scrollResponseDown() {
const container = this.shadowRoot.querySelector('.response-container'); const container = this.shadowRoot.querySelector(".response-container");
if (container) { if (container) {
const scrollAmount = container.clientHeight * 0.3; const scrollAmount = container.clientHeight * 0.3;
container.scrollTop = Math.min(container.scrollHeight - container.clientHeight, container.scrollTop + scrollAmount); container.scrollTop = Math.min(
container.scrollHeight - container.clientHeight,
container.scrollTop + scrollAmount,
);
} }
} }
@ -428,17 +500,19 @@ export class AssistantView extends LitElement {
super.connectedCallback(); super.connectedCallback();
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
this.handlePreviousResponse = () => this.navigateToPreviousResponse(); this.handlePreviousResponse = () => this.navigateToPreviousResponse();
this.handleNextResponse = () => this.navigateToNextResponse(); this.handleNextResponse = () => this.navigateToNextResponse();
this.handleScrollUp = () => this.scrollResponseUp(); this.handleScrollUp = () => this.scrollResponseUp();
this.handleScrollDown = () => this.scrollResponseDown(); this.handleScrollDown = () => this.scrollResponseDown();
this.handleExpandHotkey = () => this.handleExpandResponse();
ipcRenderer.on('navigate-previous-response', this.handlePreviousResponse); ipcRenderer.on("navigate-previous-response", this.handlePreviousResponse);
ipcRenderer.on('navigate-next-response', this.handleNextResponse); ipcRenderer.on("navigate-next-response", this.handleNextResponse);
ipcRenderer.on('scroll-response-up', this.handleScrollUp); ipcRenderer.on("scroll-response-up", this.handleScrollUp);
ipcRenderer.on('scroll-response-down', this.handleScrollDown); ipcRenderer.on("scroll-response-down", this.handleScrollDown);
ipcRenderer.on("expand-response", this.handleExpandHotkey);
} }
} }
@ -447,25 +521,40 @@ export class AssistantView extends LitElement {
this._stopWaveformAnimation(); this._stopWaveformAnimation();
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
if (this.handlePreviousResponse) ipcRenderer.removeListener('navigate-previous-response', this.handlePreviousResponse); if (this.handlePreviousResponse)
if (this.handleNextResponse) ipcRenderer.removeListener('navigate-next-response', this.handleNextResponse); ipcRenderer.removeListener(
if (this.handleScrollUp) ipcRenderer.removeListener('scroll-response-up', this.handleScrollUp); "navigate-previous-response",
if (this.handleScrollDown) ipcRenderer.removeListener('scroll-response-down', this.handleScrollDown); this.handlePreviousResponse,
);
if (this.handleNextResponse)
ipcRenderer.removeListener(
"navigate-next-response",
this.handleNextResponse,
);
if (this.handleScrollUp)
ipcRenderer.removeListener("scroll-response-up", this.handleScrollUp);
if (this.handleScrollDown)
ipcRenderer.removeListener(
"scroll-response-down",
this.handleScrollDown,
);
if (this.handleExpandHotkey)
ipcRenderer.removeListener("expand-response", this.handleExpandHotkey);
} }
} }
async handleSendText() { async handleSendText() {
const textInput = this.shadowRoot.querySelector('#textInput'); const textInput = this.shadowRoot.querySelector("#textInput");
if (textInput && textInput.value.trim()) { if (textInput && textInput.value.trim()) {
const message = textInput.value.trim(); const message = textInput.value.trim();
textInput.value = ''; textInput.value = "";
await this.onSendText(message); await this.onSendText(message);
} }
} }
handleTextKeydown(e) { handleTextKeydown(e) {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
this.handleSendText(); this.handleSendText();
} }
@ -480,10 +569,22 @@ export class AssistantView extends LitElement {
} }
} }
async handleExpandResponse() {
if (
this.isExpanding ||
this.responses.length === 0 ||
this.currentResponseIndex < 0
)
return;
this.isExpanding = true;
this._responseCountWhenStarted = this.responses.length;
await this.onExpandResponse();
}
_startWaveformAnimation() { _startWaveformAnimation() {
const canvas = this.shadowRoot.querySelector('.analyze-canvas'); const canvas = this.shadowRoot.querySelector(".analyze-canvas");
if (!canvas) return; if (!canvas) return;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
const dpr = window.devicePixelRatio || 1; const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();
@ -491,7 +592,8 @@ export class AssistantView extends LitElement {
canvas.height = rect.height * dpr; canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr); ctx.scale(dpr, dpr);
const dangerColor = getComputedStyle(this).getPropertyValue('--danger').trim() || '#EF4444'; const dangerColor =
getComputedStyle(this).getPropertyValue("--danger").trim() || "#EF4444";
const startTime = performance.now(); const startTime = performance.now();
const FADE_IN = 0.5; // seconds const FADE_IN = 0.5; // seconds
const PARTICLE_SPREAD = 4; // px inward from border const PARTICLE_SPREAD = 4; // px inward from border
@ -542,7 +644,11 @@ export class AssistantView extends LitElement {
// Pre-seed random offsets for stable particles // Pre-seed random offsets for stable particles
const seeds = []; const seeds = [];
for (let i = 0; i < PARTICLE_COUNT; i++) { for (let i = 0; i < PARTICLE_COUNT; i++) {
seeds.push({ pos: Math.random(), drift: Math.random(), depthSeed: Math.random() }); seeds.push({
pos: Math.random(),
drift: Math.random(),
depthSeed: Math.random(),
});
} }
const draw = (now) => { const draw = (now) => {
@ -585,13 +691,17 @@ export class AssistantView extends LitElement {
ctx.strokeStyle = dangerColor; ctx.strokeStyle = dangerColor;
ctx.globalAlpha = wave.opacity * fade; ctx.globalAlpha = wave.opacity * fade;
ctx.lineWidth = wave.width; ctx.lineWidth = wave.width;
ctx.lineCap = 'round'; ctx.lineCap = "round";
ctx.lineJoin = 'round'; ctx.lineJoin = "round";
for (let x = 0; x <= w; x++) { for (let x = 0; x <= w; x++) {
const norm = x / w; const norm = x / w;
const envelope = Math.sin(norm * Math.PI); const envelope = Math.sin(norm * Math.PI);
const y = midY + Math.sin(norm * Math.PI * 2 * wave.freq + elapsed * wave.speed) * (midY * wave.amp) * envelope; const y =
midY +
Math.sin(norm * Math.PI * 2 * wave.freq + elapsed * wave.speed) *
(midY * wave.amp) *
envelope;
if (x === 0) ctx.moveTo(x, y); if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y); else ctx.lineTo(x, y);
} }
@ -610,16 +720,16 @@ export class AssistantView extends LitElement {
cancelAnimationFrame(this._animFrame); cancelAnimationFrame(this._animFrame);
this._animFrame = null; this._animFrame = null;
} }
const canvas = this.shadowRoot.querySelector('.analyze-canvas'); const canvas = this.shadowRoot.querySelector(".analyze-canvas");
if (canvas) { if (canvas) {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
} }
} }
scrollToBottom() { scrollToBottom() {
setTimeout(() => { setTimeout(() => {
const container = this.shadowRoot.querySelector('.response-container'); const container = this.shadowRoot.querySelector(".response-container");
if (container) { if (container) {
container.scrollTop = container.scrollHeight; container.scrollTop = container.scrollHeight;
} }
@ -633,11 +743,14 @@ export class AssistantView extends LitElement {
updated(changedProperties) { updated(changedProperties) {
super.updated(changedProperties); super.updated(changedProperties);
if (changedProperties.has('responses') || changedProperties.has('currentResponseIndex')) { if (
changedProperties.has("responses") ||
changedProperties.has("currentResponseIndex")
) {
this.updateResponseContent(); this.updateResponseContent();
} }
if (changedProperties.has('isAnalyzing')) { if (changedProperties.has("isAnalyzing")) {
if (this.isAnalyzing) { if (this.isAnalyzing) {
this._startWaveformAnimation(); this._startWaveformAnimation();
} else { } else {
@ -645,46 +758,116 @@ export class AssistantView extends LitElement {
} }
} }
if (changedProperties.has('responses') && this.isAnalyzing) { if (
changedProperties.has("responses") &&
(this.isAnalyzing || this.isExpanding)
) {
if (this.responses.length > this._responseCountWhenStarted) { if (this.responses.length > this._responseCountWhenStarted) {
this.isAnalyzing = false; this.isAnalyzing = false;
this.isExpanding = false;
} }
} }
} }
updateResponseContent() { updateResponseContent() {
const container = this.shadowRoot.querySelector('#responseContainer'); const container = this.shadowRoot.querySelector("#responseContainer");
if (container) { if (container) {
const currentResponse = this.getCurrentResponse(); const currentResponse = this.getCurrentResponse();
const renderedResponse = this.renderMarkdown(currentResponse); const renderedResponse = this.renderMarkdown(currentResponse);
container.innerHTML = renderedResponse; container.innerHTML = renderedResponse;
if (this.shouldAnimateResponse) { if (this.shouldAnimateResponse) {
this.dispatchEvent(new CustomEvent('response-animation-complete', { bubbles: true, composed: true })); this.dispatchEvent(
new CustomEvent("response-animation-complete", {
bubbles: true,
composed: true,
}),
);
} }
} }
} }
render() { render() {
const hasMultipleResponses = this.responses.length > 1; const hasMultipleResponses = this.responses.length > 1;
const hasResponse =
this.responses.length > 0 && this.currentResponseIndex >= 0;
return html` return html`
<div class="response-container" id="responseContainer"></div> <div class="response-container" id="responseContainer"></div>
${hasMultipleResponses ? html` ${hasMultipleResponses || hasResponse
? html`
<div class="response-nav"> <div class="response-nav">
<button class="nav-btn" @click=${this.navigateToPreviousResponse} ?disabled=${this.currentResponseIndex <= 0} title="Previous response"> ${hasMultipleResponses
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> ? html`
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" /> <button
class="nav-btn"
@click=${this.navigateToPreviousResponse}
?disabled=${this.currentResponseIndex <= 0}
title="Previous response"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg> </svg>
</button> </button>
<span class="response-counter">${this.currentResponseIndex + 1} of ${this.responses.length}</span> <span class="response-counter"
<button class="nav-btn" @click=${this.navigateToNextResponse} ?disabled=${this.currentResponseIndex >= this.responses.length - 1} title="Next response"> >${this.currentResponseIndex + 1} of
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> ${this.responses.length}</span
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" /> >
<button
class="nav-btn"
@click=${this.navigateToNextResponse}
?disabled=${this.currentResponseIndex >=
this.responses.length - 1}
title="Next response"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg> </svg>
</button> </button>
`
: ""}
${hasResponse
? html`
<button
class="expand-btn"
@click=${this.handleExpandResponse}
?disabled=${this.isExpanding}
title="Expand this response with more detail"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06Z"
clip-rule="evenodd"
/>
</svg>
${this.isExpanding ? "Expanding..." : "Expand"}
</button>
`
: ""}
</div> </div>
` : ''} `
: ""}
<div class="input-bar"> <div class="input-bar">
<div class="input-bar-inner"> <div class="input-bar-inner">
@ -695,11 +878,26 @@ export class AssistantView extends LitElement {
@keydown=${this.handleTextKeydown} @keydown=${this.handleTextKeydown}
/> />
</div> </div>
<button class="analyze-btn ${this.isAnalyzing ? 'analyzing' : ''}" @click=${this.handleScreenAnswer}> <button
class="analyze-btn ${this.isAnalyzing ? "analyzing" : ""}"
@click=${this.handleScreenAnswer}
>
<canvas class="analyze-canvas"></canvas> <canvas class="analyze-canvas"></canvas>
<span class="analyze-btn-content"> <span class="analyze-btn-content">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"> <svg
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 3v7h6l-8 11v-7H5z" /> xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 3v7h6l-8 11v-7H5z"
/>
</svg> </svg>
Analyze Screen Analyze Screen
</span> </span>
@ -709,4 +907,4 @@ export class AssistantView extends LitElement {
} }
} }
customElements.define('assistant-view', AssistantView); customElements.define("assistant-view", AssistantView);

View File

@ -1,5 +1,5 @@
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js";
import { unifiedPageStyles } from './sharedPageStyles.js'; import { unifiedPageStyles } from "./sharedPageStyles.js";
export class CustomizeView extends LitElement { export class CustomizeView extends LitElement {
static styles = [ static styles = [
@ -22,7 +22,7 @@ export class CustomizeView extends LitElement {
} }
.warning-callout::before { .warning-callout::before {
content: ''; content: "";
position: absolute; position: absolute;
top: -6px; top: -6px;
left: 16px; left: 16px;
@ -198,26 +198,26 @@ export class CustomizeView extends LitElement {
constructor() { constructor() {
super(); super();
this.selectedProfile = 'interview'; this.selectedProfile = "interview";
this.selectedLanguage = 'en-US'; this.selectedLanguage = "en-US";
this.selectedImageQuality = 'medium'; this.selectedImageQuality = "medium";
this.layoutMode = 'normal'; this.layoutMode = "normal";
this.keybinds = this.getDefaultKeybinds(); this.keybinds = this.getDefaultKeybinds();
this.onProfileChange = () => {}; this.onProfileChange = () => {};
this.onLanguageChange = () => {}; this.onLanguageChange = () => {};
this.onImageQualityChange = () => {}; this.onImageQualityChange = () => {};
this.onLayoutModeChange = () => {}; this.onLayoutModeChange = () => {};
this.googleSearchEnabled = true; this.googleSearchEnabled = true;
this.providerMode = 'byok'; this.providerMode = "byok";
this.isClearing = false; this.isClearing = false;
this.isRestoring = false; this.isRestoring = false;
this.clearStatusMessage = ''; this.clearStatusMessage = "";
this.clearStatusType = ''; this.clearStatusType = "";
this.backgroundTransparency = 0.8; this.backgroundTransparency = 0.8;
this.fontSize = 20; this.fontSize = 20;
this.audioMode = 'speaker_only'; this.audioMode = "speaker_only";
this.customPrompt = ''; this.customPrompt = "";
this.theme = 'dark'; this.theme = "dark";
this._loadFromStorage(); this._loadFromStorage();
} }
@ -227,14 +227,17 @@ export class CustomizeView extends LitElement {
async _loadFromStorage() { async _loadFromStorage() {
try { try {
const [prefs, keybinds] = await Promise.all([cheatingDaddy.storage.getPreferences(), cheatingDaddy.storage.getKeybinds()]); const [prefs, keybinds] = await Promise.all([
cheatingDaddy.storage.getPreferences(),
cheatingDaddy.storage.getKeybinds(),
]);
this.googleSearchEnabled = prefs.googleSearchEnabled ?? true; this.googleSearchEnabled = prefs.googleSearchEnabled ?? true;
this.providerMode = prefs.providerMode || 'byok'; this.providerMode = prefs.providerMode || "byok";
this.backgroundTransparency = prefs.backgroundTransparency ?? 0.8; this.backgroundTransparency = prefs.backgroundTransparency ?? 0.8;
this.fontSize = prefs.fontSize ?? 20; this.fontSize = prefs.fontSize ?? 20;
this.audioMode = prefs.audioMode ?? 'speaker_only'; this.audioMode = prefs.audioMode ?? "speaker_only";
this.customPrompt = prefs.customPrompt ?? ''; this.customPrompt = prefs.customPrompt ?? "";
this.theme = prefs.theme ?? 'dark'; this.theme = prefs.theme ?? "dark";
if (keybinds) { if (keybinds) {
this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds }; this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds };
} }
@ -242,94 +245,144 @@ export class CustomizeView extends LitElement {
this.updateFontSize(); this.updateFontSize();
this.requestUpdate(); this.requestUpdate();
} catch (error) { } catch (error) {
console.error('Error loading settings:', error); console.error("Error loading settings:", error);
} }
} }
getProfiles() { getProfiles() {
return [ return [
{ value: 'interview', name: 'Job Interview' }, { value: "interview", name: "Job Interview" },
{ value: 'sales', name: 'Sales Call' }, { value: "sales", name: "Sales Call" },
{ value: 'meeting', name: 'Business Meeting' }, { value: "meeting", name: "Business Meeting" },
{ value: 'presentation', name: 'Presentation' }, { value: "presentation", name: "Presentation" },
{ value: 'negotiation', name: 'Negotiation' }, { value: "negotiation", name: "Negotiation" },
{ value: 'exam', name: 'Exam Assistant' }, { value: "exam", name: "Exam Assistant" },
]; ];
} }
getLanguages() { getLanguages() {
return [ return [
{ value: 'en-US', name: 'English (US)' }, { value: "en-US", name: "English (US)" },
{ value: 'en-GB', name: 'English (UK)' }, { value: "en-GB", name: "English (UK)" },
{ value: 'en-AU', name: 'English (Australia)' }, { value: "en-AU", name: "English (Australia)" },
{ value: 'en-IN', name: 'English (India)' }, { value: "en-IN", name: "English (India)" },
{ value: 'de-DE', name: 'German (Germany)' }, { value: "de-DE", name: "German (Germany)" },
{ value: 'es-US', name: 'Spanish (US)' }, { value: "es-US", name: "Spanish (US)" },
{ value: 'es-ES', name: 'Spanish (Spain)' }, { value: "es-ES", name: "Spanish (Spain)" },
{ value: 'fr-FR', name: 'French (France)' }, { value: "fr-FR", name: "French (France)" },
{ value: 'fr-CA', name: 'French (Canada)' }, { value: "fr-CA", name: "French (Canada)" },
{ value: 'hi-IN', name: 'Hindi (India)' }, { value: "hi-IN", name: "Hindi (India)" },
{ value: 'pt-BR', name: 'Portuguese (Brazil)' }, { value: "pt-BR", name: "Portuguese (Brazil)" },
{ value: 'ar-XA', name: 'Arabic (Generic)' }, { value: "ar-XA", name: "Arabic (Generic)" },
{ value: 'id-ID', name: 'Indonesian (Indonesia)' }, { value: "id-ID", name: "Indonesian (Indonesia)" },
{ value: 'it-IT', name: 'Italian (Italy)' }, { value: "it-IT", name: "Italian (Italy)" },
{ value: 'ja-JP', name: 'Japanese (Japan)' }, { value: "ja-JP", name: "Japanese (Japan)" },
{ value: 'tr-TR', name: 'Turkish (Turkey)' }, { value: "tr-TR", name: "Turkish (Turkey)" },
{ value: 'vi-VN', name: 'Vietnamese (Vietnam)' }, { value: "vi-VN", name: "Vietnamese (Vietnam)" },
{ value: 'bn-IN', name: 'Bengali (India)' }, { value: "bn-IN", name: "Bengali (India)" },
{ value: 'gu-IN', name: 'Gujarati (India)' }, { value: "gu-IN", name: "Gujarati (India)" },
{ value: 'kn-IN', name: 'Kannada (India)' }, { value: "kn-IN", name: "Kannada (India)" },
{ value: 'ml-IN', name: 'Malayalam (India)' }, { value: "ml-IN", name: "Malayalam (India)" },
{ value: 'mr-IN', name: 'Marathi (India)' }, { value: "mr-IN", name: "Marathi (India)" },
{ value: 'ta-IN', name: 'Tamil (India)' }, { value: "ta-IN", name: "Tamil (India)" },
{ value: 'te-IN', name: 'Telugu (India)' }, { value: "te-IN", name: "Telugu (India)" },
{ value: 'nl-NL', name: 'Dutch (Netherlands)' }, { value: "nl-NL", name: "Dutch (Netherlands)" },
{ value: 'ko-KR', name: 'Korean (South Korea)' }, { value: "ko-KR", name: "Korean (South Korea)" },
{ value: 'cmn-CN', name: 'Mandarin Chinese (China)' }, { value: "cmn-CN", name: "Mandarin Chinese (China)" },
{ value: 'pl-PL', name: 'Polish (Poland)' }, { value: "pl-PL", name: "Polish (Poland)" },
{ value: 'ru-RU', name: 'Russian (Russia)' }, { value: "ru-RU", name: "Russian (Russia)" },
{ value: 'th-TH', name: 'Thai (Thailand)' }, { value: "th-TH", name: "Thai (Thailand)" },
]; ];
} }
getDefaultKeybinds() { getDefaultKeybinds() {
const isMac = cheatingDaddy.isMacOS || navigator.platform.includes('Mac'); const isMac = cheatingDaddy.isMacOS || navigator.platform.includes("Mac");
return { return {
moveUp: isMac ? 'Alt+Up' : 'Ctrl+Up', moveUp: isMac ? "Alt+Up" : "Ctrl+Up",
moveDown: isMac ? 'Alt+Down' : 'Ctrl+Down', moveDown: isMac ? "Alt+Down" : "Ctrl+Down",
moveLeft: isMac ? 'Alt+Left' : 'Ctrl+Left', moveLeft: isMac ? "Alt+Left" : "Ctrl+Left",
moveRight: isMac ? 'Alt+Right' : 'Ctrl+Right', moveRight: isMac ? "Alt+Right" : "Ctrl+Right",
toggleVisibility: isMac ? 'Cmd+\\' : 'Ctrl+\\', toggleVisibility: isMac ? "Cmd+\\" : "Ctrl+\\",
toggleClickThrough: isMac ? 'Cmd+M' : 'Ctrl+M', toggleClickThrough: isMac ? "Cmd+M" : "Ctrl+M",
nextStep: isMac ? 'Cmd+Enter' : 'Ctrl+Enter', nextStep: isMac ? "Cmd+Enter" : "Ctrl+Enter",
previousResponse: isMac ? 'Cmd+[' : 'Ctrl+[', previousResponse: isMac ? "Cmd+[" : "Ctrl+[",
nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]', nextResponse: isMac ? "Cmd+]" : "Ctrl+]",
scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up', scrollUp: isMac ? "Cmd+Shift+Up" : "Ctrl+Shift+Up",
scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down', scrollDown: isMac ? "Cmd+Shift+Down" : "Ctrl+Shift+Down",
expandResponse: isMac ? "Cmd+E" : "Ctrl+E",
}; };
} }
getKeybindActions() { getKeybindActions() {
return [ return [
{ key: 'moveUp', name: 'Move Window Up', description: 'Move the app window up' }, {
{ key: 'moveDown', name: 'Move Window Down', description: 'Move the app window down' }, key: "moveUp",
{ key: 'moveLeft', name: 'Move Window Left', description: 'Move the app window left' }, name: "Move Window Up",
{ key: 'moveRight', name: 'Move Window Right', description: 'Move the app window right' }, description: "Move the app window up",
{ key: 'toggleVisibility', name: 'Toggle Visibility', description: 'Show or hide the app window' }, },
{ key: 'toggleClickThrough', name: 'Toggle Click-through', description: 'Enable or disable click-through mode' }, {
{ key: 'nextStep', name: 'Ask Next Step', description: 'Take screenshot and ask for next step' }, key: "moveDown",
{ key: 'previousResponse', name: 'Previous Response', description: 'Move to previous AI response' }, name: "Move Window Down",
{ key: 'nextResponse', name: 'Next Response', description: 'Move to next AI response' }, description: "Move the app window down",
{ key: 'scrollUp', name: 'Scroll Response Up', description: 'Scroll response content upward' }, },
{ key: 'scrollDown', name: 'Scroll Response Down', description: 'Scroll response content downward' }, {
key: "moveLeft",
name: "Move Window Left",
description: "Move the app window left",
},
{
key: "moveRight",
name: "Move Window Right",
description: "Move the app window right",
},
{
key: "toggleVisibility",
name: "Toggle Visibility",
description: "Show or hide the app window",
},
{
key: "toggleClickThrough",
name: "Toggle Click-through",
description: "Enable or disable click-through mode",
},
{
key: "nextStep",
name: "Ask Next Step",
description: "Take screenshot and ask for next step",
},
{
key: "previousResponse",
name: "Previous Response",
description: "Move to previous AI response",
},
{
key: "nextResponse",
name: "Next Response",
description: "Move to next AI response",
},
{
key: "scrollUp",
name: "Scroll Response Up",
description: "Scroll response content upward",
},
{
key: "scrollDown",
name: "Scroll Response Down",
description: "Scroll response content downward",
},
{
key: "expandResponse",
name: "Expand Response",
description: "Expand the current response with more detail",
},
]; ];
} }
async saveKeybinds() { async saveKeybinds() {
await cheatingDaddy.storage.setKeybinds(this.keybinds); await cheatingDaddy.storage.setKeybinds(this.keybinds);
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.send('update-keybinds', this.keybinds); ipcRenderer.send("update-keybinds", this.keybinds);
} }
} }
@ -355,18 +408,24 @@ export class CustomizeView extends LitElement {
async handleCustomPromptInput(e) { async handleCustomPromptInput(e) {
this.customPrompt = e.target.value; this.customPrompt = e.target.value;
await cheatingDaddy.storage.updatePreference('customPrompt', this.customPrompt); await cheatingDaddy.storage.updatePreference(
"customPrompt",
this.customPrompt,
);
} }
async handleAudioModeSelect(e) { async handleAudioModeSelect(e) {
this.audioMode = e.target.value; this.audioMode = e.target.value;
await cheatingDaddy.storage.updatePreference('audioMode', this.audioMode); await cheatingDaddy.storage.updatePreference("audioMode", this.audioMode);
this.requestUpdate(); this.requestUpdate();
} }
async handleProviderModeChange(e) { async handleProviderModeChange(e) {
this.providerMode = e.target.value; this.providerMode = e.target.value;
await cheatingDaddy.storage.updatePreference('providerMode', this.providerMode); await cheatingDaddy.storage.updatePreference(
"providerMode",
this.providerMode,
);
this.requestUpdate(); this.requestUpdate();
} }
@ -379,13 +438,19 @@ export class CustomizeView extends LitElement {
async handleGoogleSearchChange(e) { async handleGoogleSearchChange(e) {
this.googleSearchEnabled = e.target.checked; this.googleSearchEnabled = e.target.checked;
await cheatingDaddy.storage.updatePreference('googleSearchEnabled', this.googleSearchEnabled); await cheatingDaddy.storage.updatePreference(
"googleSearchEnabled",
this.googleSearchEnabled,
);
if (window.require) { if (window.require) {
try { try {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('update-google-search-setting', this.googleSearchEnabled); await ipcRenderer.invoke(
"update-google-search-setting",
this.googleSearchEnabled,
);
} catch (error) { } catch (error) {
console.error('Failed to notify main process:', error); console.error("Failed to notify main process:", error);
} }
} }
this.requestUpdate(); this.requestUpdate();
@ -393,25 +458,34 @@ export class CustomizeView extends LitElement {
async handleBackgroundTransparencyChange(e) { async handleBackgroundTransparencyChange(e) {
this.backgroundTransparency = parseFloat(e.target.value); this.backgroundTransparency = parseFloat(e.target.value);
await cheatingDaddy.storage.updatePreference('backgroundTransparency', this.backgroundTransparency); await cheatingDaddy.storage.updatePreference(
"backgroundTransparency",
this.backgroundTransparency,
);
this.updateBackgroundAppearance(); this.updateBackgroundAppearance();
this.requestUpdate(); this.requestUpdate();
} }
updateBackgroundAppearance() { updateBackgroundAppearance() {
const colors = cheatingDaddy.theme.get(this.theme); const colors = cheatingDaddy.theme.get(this.theme);
cheatingDaddy.theme.applyBackgrounds(colors.background, this.backgroundTransparency); cheatingDaddy.theme.applyBackgrounds(
colors.background,
this.backgroundTransparency,
);
} }
async handleFontSizeChange(e) { async handleFontSizeChange(e) {
this.fontSize = parseInt(e.target.value, 10); this.fontSize = parseInt(e.target.value, 10);
await cheatingDaddy.storage.updatePreference('fontSize', this.fontSize); await cheatingDaddy.storage.updatePreference("fontSize", this.fontSize);
this.updateFontSize(); this.updateFontSize();
this.requestUpdate(); this.requestUpdate();
} }
updateFontSize() { updateFontSize() {
document.documentElement.style.setProperty('--response-font-size', `${this.fontSize}px`); document.documentElement.style.setProperty(
"--response-font-size",
`${this.fontSize}px`,
);
} }
handleKeybindChange(action, value) { handleKeybindChange(action, value) {
@ -421,50 +495,50 @@ export class CustomizeView extends LitElement {
} }
handleKeybindFocus(e) { handleKeybindFocus(e) {
e.target.placeholder = 'Press key combination...'; e.target.placeholder = "Press key combination...";
e.target.select(); e.target.select();
} }
handleKeybindInput(e) { handleKeybindInput(e) {
e.preventDefault(); e.preventDefault();
const modifiers = []; const modifiers = [];
if (e.ctrlKey) modifiers.push('Ctrl'); if (e.ctrlKey) modifiers.push("Ctrl");
if (e.metaKey) modifiers.push('Cmd'); if (e.metaKey) modifiers.push("Cmd");
if (e.altKey) modifiers.push('Alt'); if (e.altKey) modifiers.push("Alt");
if (e.shiftKey) modifiers.push('Shift'); if (e.shiftKey) modifiers.push("Shift");
let mainKey = e.key; let mainKey = e.key;
switch (e.code) { switch (e.code) {
case 'ArrowUp': case "ArrowUp":
mainKey = 'Up'; mainKey = "Up";
break; break;
case 'ArrowDown': case "ArrowDown":
mainKey = 'Down'; mainKey = "Down";
break; break;
case 'ArrowLeft': case "ArrowLeft":
mainKey = 'Left'; mainKey = "Left";
break; break;
case 'ArrowRight': case "ArrowRight":
mainKey = 'Right'; mainKey = "Right";
break; break;
case 'Enter': case "Enter":
mainKey = 'Enter'; mainKey = "Enter";
break; break;
case 'Space': case "Space":
mainKey = 'Space'; mainKey = "Space";
break; break;
case 'Backslash': case "Backslash":
mainKey = '\\'; mainKey = "\\";
break; break;
default: default:
if (e.key.length === 1) mainKey = e.key.toUpperCase(); if (e.key.length === 1) mainKey = e.key.toUpperCase();
break; break;
} }
if (['Control', 'Meta', 'Alt', 'Shift'].includes(e.key)) return; if (["Control", "Meta", "Alt", "Shift"].includes(e.key)) return;
const action = e.target.dataset.action; const action = e.target.dataset.action;
const keybind = [...modifiers, mainKey].join('+'); const keybind = [...modifiers, mainKey].join("+");
this.handleKeybindChange(action, keybind); this.handleKeybindChange(action, keybind);
e.target.value = keybind; e.target.value = keybind;
e.target.blur(); e.target.blur();
@ -474,8 +548,8 @@ export class CustomizeView extends LitElement {
this.keybinds = this.getDefaultKeybinds(); this.keybinds = this.getDefaultKeybinds();
await cheatingDaddy.storage.setKeybinds(null); await cheatingDaddy.storage.setKeybinds(null);
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.send('update-keybinds', this.keybinds); ipcRenderer.send("update-keybinds", this.keybinds);
} }
this.requestUpdate(); this.requestUpdate();
} }
@ -483,22 +557,22 @@ export class CustomizeView extends LitElement {
async restoreAllSettings() { async restoreAllSettings() {
if (this.isRestoring) return; if (this.isRestoring) return;
this.isRestoring = true; this.isRestoring = true;
this.clearStatusMessage = ''; this.clearStatusMessage = "";
this.clearStatusType = ''; this.clearStatusType = "";
this.requestUpdate(); this.requestUpdate();
try { try {
// Restore all preferences to defaults // Restore all preferences to defaults
const defaults = { const defaults = {
customPrompt: '', customPrompt: "",
selectedProfile: 'interview', selectedProfile: "interview",
selectedLanguage: 'en-US', selectedLanguage: "en-US",
selectedScreenshotInterval: '5', selectedScreenshotInterval: "5",
selectedImageQuality: 'medium', selectedImageQuality: "medium",
audioMode: 'speaker_only', audioMode: "speaker_only",
fontSize: 20, fontSize: 20,
backgroundTransparency: 0.8, backgroundTransparency: 0.8,
googleSearchEnabled: false, googleSearchEnabled: false,
theme: 'dark', theme: "dark",
}; };
for (const [key, value] of Object.entries(defaults)) { for (const [key, value] of Object.entries(defaults)) {
await cheatingDaddy.storage.updatePreference(key, value); await cheatingDaddy.storage.updatePreference(key, value);
@ -508,8 +582,8 @@ export class CustomizeView extends LitElement {
this.keybinds = this.getDefaultKeybinds(); this.keybinds = this.getDefaultKeybinds();
await cheatingDaddy.storage.setKeybinds(null); await cheatingDaddy.storage.setKeybinds(null);
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
ipcRenderer.send('update-keybinds', this.keybinds); ipcRenderer.send("update-keybinds", this.keybinds);
} }
// Apply to local state // Apply to local state
@ -533,12 +607,12 @@ export class CustomizeView extends LitElement {
this.updateFontSize(); this.updateFontSize();
await cheatingDaddy.theme.save(defaults.theme); await cheatingDaddy.theme.save(defaults.theme);
this.clearStatusMessage = 'All settings restored to defaults'; this.clearStatusMessage = "All settings restored to defaults";
this.clearStatusType = 'success'; this.clearStatusType = "success";
} catch (error) { } catch (error) {
console.error('Error restoring settings:', error); console.error("Error restoring settings:", error);
this.clearStatusMessage = `Error restoring settings: ${error.message}`; this.clearStatusMessage = `Error restoring settings: ${error.message}`;
this.clearStatusType = 'error'; this.clearStatusType = "error";
} finally { } finally {
this.isRestoring = false; this.isRestoring = false;
this.requestUpdate(); this.requestUpdate();
@ -548,28 +622,28 @@ export class CustomizeView extends LitElement {
async clearLocalData() { async clearLocalData() {
if (this.isClearing) return; if (this.isClearing) return;
this.isClearing = true; this.isClearing = true;
this.clearStatusMessage = ''; this.clearStatusMessage = "";
this.clearStatusType = ''; this.clearStatusType = "";
this.requestUpdate(); this.requestUpdate();
try { try {
await cheatingDaddy.storage.clearAll(); await cheatingDaddy.storage.clearAll();
this.clearStatusMessage = 'Successfully cleared all local data'; this.clearStatusMessage = "Successfully cleared all local data";
this.clearStatusType = 'success'; this.clearStatusType = "success";
this.requestUpdate(); this.requestUpdate();
setTimeout(() => { setTimeout(() => {
this.clearStatusMessage = 'Closing application...'; this.clearStatusMessage = "Closing application...";
this.requestUpdate(); this.requestUpdate();
setTimeout(async () => { setTimeout(async () => {
if (window.require) { if (window.require) {
const { ipcRenderer } = window.require('electron'); const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke('quit-application'); await ipcRenderer.invoke("quit-application");
} }
}, 1000); }, 1000);
}, 2000); }, 2000);
} catch (error) { } catch (error) {
console.error('Error clearing data:', error); console.error("Error clearing data:", error);
this.clearStatusMessage = `Error clearing data: ${error.message}`; this.clearStatusMessage = `Error clearing data: ${error.message}`;
this.clearStatusType = 'error'; this.clearStatusType = "error";
} finally { } finally {
this.isClearing = false; this.isClearing = false;
this.requestUpdate(); this.requestUpdate();
@ -583,7 +657,11 @@ export class CustomizeView extends LitElement {
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label class="form-label">Regime</label> <label class="form-label">Regime</label>
<select class="control" .value=${this.providerMode} @change=${this.handleProviderModeChange}> <select
class="control"
.value=${this.providerMode}
@change=${this.handleProviderModeChange}
>
<option value="byok">BYOK (API Keys)</option> <option value="byok">BYOK (API Keys)</option>
<option value="local">Local AI (Ollama)</option> <option value="local">Local AI (Ollama)</option>
</select> </select>
@ -600,18 +678,31 @@ export class CustomizeView extends LitElement {
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label class="form-label">Audio Mode</label> <label class="form-label">Audio Mode</label>
<select class="control" .value=${this.audioMode} @change=${this.handleAudioModeSelect}> <select
class="control"
.value=${this.audioMode}
@change=${this.handleAudioModeSelect}
>
<option value="speaker_only">Speaker Only (Interviewer)</option> <option value="speaker_only">Speaker Only (Interviewer)</option>
<option value="mic_only">Microphone Only (Me)</option> <option value="mic_only">Microphone Only (Me)</option>
<option value="both">Both Speaker and Microphone</option> <option value="both">Both Speaker and Microphone</option>
</select> </select>
</div> </div>
${this.audioMode !== 'speaker_only' ? html` ${this.audioMode !== "speaker_only"
<div class="warning-callout">May cause unexpected behavior. Only change this if you know what you're doing.</div> ? html`
` : ''} <div class="warning-callout">
May cause unexpected behavior. Only change this if you know
what you're doing.
</div>
`
: ""}
<div class="form-group"> <div class="form-group">
<label class="form-label">Image Quality</label> <label class="form-label">Image Quality</label>
<select class="control" .value=${this.selectedImageQuality} @change=${this.handleImageQualitySelect}> <select
class="control"
.value=${this.selectedImageQuality}
@change=${this.handleImageQualitySelect}
>
<option value="high">High Quality</option> <option value="high">High Quality</option>
<option value="medium">Medium Quality</option> <option value="medium">Medium Quality</option>
<option value="low">Low Quality</option> <option value="low">Low Quality</option>
@ -629,8 +720,17 @@ export class CustomizeView extends LitElement {
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label class="form-label">Speech Language</label> <label class="form-label">Speech Language</label>
<select class="control" .value=${this.selectedLanguage} @change=${this.handleLanguageSelect}> <select
${this.getLanguages().map(language => html`<option value=${language.value}>${language.name}</option>`)} class="control"
.value=${this.selectedLanguage}
@change=${this.handleLanguageSelect}
>
${this.getLanguages().map(
(language) =>
html`<option value=${language.value}>
${language.name}
</option>`,
)}
</select> </select>
</div> </div>
</div> </div>
@ -645,14 +745,23 @@ export class CustomizeView extends LitElement {
<div class="form-grid"> <div class="form-grid">
<div class="form-group"> <div class="form-group">
<label class="form-label">Theme</label> <label class="form-label">Theme</label>
<select class="control" .value=${this.theme} @change=${this.handleThemeChange}> <select
${this.getThemes().map(theme => html`<option value=${theme.value}>${theme.name}</option>`)} class="control"
.value=${this.theme}
@change=${this.handleThemeChange}
>
${this.getThemes().map(
(theme) =>
html`<option value=${theme.value}>${theme.name}</option>`,
)}
</select> </select>
</div> </div>
<div class="form-group slider-wrap"> <div class="form-group slider-wrap">
<div class="slider-header"> <div class="slider-header">
<label class="form-label">Background Transparency</label> <label class="form-label">Background Transparency</label>
<span class="slider-value">${Math.round(this.backgroundTransparency * 100)}%</span> <span class="slider-value"
>${Math.round(this.backgroundTransparency * 100)}%</span
>
</div> </div>
<input <input
class="slider-input" class="slider-input"
@ -688,7 +797,8 @@ export class CustomizeView extends LitElement {
return html` return html`
<section class="surface"> <section class="surface">
<div class="surface-title">Keyboard Shortcuts</div> <div class="surface-title">Keyboard Shortcuts</div>
${this.getKeybindActions().map(action => html` ${this.getKeybindActions().map(
(action) => html`
<div class="keybind-row"> <div class="keybind-row">
<span class="keybind-name">${action.name}</span> <span class="keybind-name">${action.name}</span>
<input <input
@ -701,9 +811,16 @@ export class CustomizeView extends LitElement {
readonly readonly
/> />
</div> </div>
`)} `,
)}
<div style="margin-top: var(--space-sm);"> <div style="margin-top: var(--space-sm);">
<button class="control" style="width:auto;padding:8px 10px;" @click=${this.resetKeybinds}>Reset to defaults</button> <button
class="control"
style="width:auto;padding:8px 10px;"
@click=${this.resetKeybinds}
>
Reset to defaults
</button>
</div> </div>
</section> </section>
`; `;
@ -714,16 +831,32 @@ export class CustomizeView extends LitElement {
<section class="surface danger-surface"> <section class="surface danger-surface">
<div class="surface-title danger">Privacy and Data</div> <div class="surface-title danger">Privacy and Data</div>
<div style="display:flex;gap:var(--space-sm);flex-wrap:wrap;"> <div style="display:flex;gap:var(--space-sm);flex-wrap:wrap;">
<button class="danger-button" @click=${this.restoreAllSettings} ?disabled=${this.isRestoring}> <button
${this.isRestoring ? 'Restoring...' : 'Restore all settings'} class="danger-button"
@click=${this.restoreAllSettings}
?disabled=${this.isRestoring}
>
${this.isRestoring ? "Restoring..." : "Restore all settings"}
</button> </button>
<button class="danger-button" @click=${this.clearLocalData} ?disabled=${this.isClearing}> <button
${this.isClearing ? 'Clearing...' : 'Delete all data'} class="danger-button"
@click=${this.clearLocalData}
?disabled=${this.isClearing}
>
${this.isClearing ? "Clearing..." : "Delete all data"}
</button> </button>
</div> </div>
${this.clearStatusMessage ? html` ${this.clearStatusMessage
<div class="status ${this.clearStatusType === 'success' ? 'success' : 'error'}">${this.clearStatusMessage}</div> ? html`
` : ''} <div
class="status ${this.clearStatusType === "success"
? "success"
: "error"}"
>
${this.clearStatusMessage}
</div>
`
: ""}
</section> </section>
`; `;
} }
@ -733,16 +866,13 @@ export class CustomizeView extends LitElement {
<div class="unified-page"> <div class="unified-page">
<div class="unified-wrap"> <div class="unified-wrap">
<div class="page-title">Settings</div> <div class="page-title">Settings</div>
${this.renderAISection()} ${this.renderAISection()} ${this.renderAudioSection()}
${this.renderAudioSection()} ${this.renderLanguageSection()} ${this.renderAppearanceSection()}
${this.renderLanguageSection()} ${this.renderKeyboardSection()} ${this.renderPrivacySection()}
${this.renderAppearanceSection()}
${this.renderKeyboardSection()}
${this.renderPrivacySection()}
</div> </div>
</div> </div>
`; `;
} }
} }
customElements.define('customize-view', CustomizeView); customElements.define("customize-view", CustomizeView);

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,37 @@
const profilePrompts = { const responseModeFormats = {
interview: { brief: `**RESPONSE FORMAT REQUIREMENTS:**
intro: `You are an AI-powered interview assistant, designed to act as a discreet on-screen teleprompter. Your mission is to help the user excel in their job interview by providing concise, impactful, and ready-to-speak answers or key talking points. Analyze the ongoing interview dialogue and, crucially, the 'User-provided context' below.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-3 sentences max) - Keep responses SHORT and CONCISE (1-3 sentences max)
- Use **markdown formatting** for better readability - Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis - Use **bold** for key points and emphasis
- Use bullet points (-) for lists when appropriate - Use bullet points (-) for lists when appropriate
- Focus on the most essential information only`, - Focus on the most essential information only
- EXCEPTION: If a coding/algorithm task is detected, ALWAYS provide the complete working code (see CODING TASKS below)`,
detailed: `**RESPONSE FORMAT REQUIREMENTS:**
- Provide a THOROUGH and COMPREHENSIVE response with full explanations
- Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis
- Use headers (##) to organize sections when appropriate
- Use bullet points (-) for lists when appropriate
- Include relevant context, edge cases, and reasoning
- For technical topics, explain the "why" behind each point
- No length restriction be as detailed as needed to fully answer the question`,
};
const codingAwareness = `**CODING TASKS — CRITICAL INSTRUCTION:**
When the interviewer/questioner asks to solve a coding problem, implement an algorithm, debug code, do a live coding exercise, open an IDE and write code, or any task that requires a code solution:
- You MUST provide the ACTUAL COMPLETE WORKING CODE SOLUTION
- NEVER respond with meta-advice like "now you should write code" or "prepare to implement" or "think about the approach"
- NEVER say "open your IDE" or "start coding" instead, GIVE THE CODE
- In brief mode: provide 2-3 bullet approach points, then the FULL working code with comments
- In detailed mode: explain approach, time/space complexity, edge cases, then the FULL working code with comments
- Include the programming language name in the code fence (e.g. \`\`\`python, \`\`\`javascript)
- If the language is not specified, default to Python
- The code must be complete, runnable, and correct`;
const profilePrompts = {
interview: {
intro: `You are an AI-powered interview assistant, designed to act as a discreet on-screen teleprompter. Your mission is to help the user excel in their job interview by providing concise, impactful, and ready-to-speak answers or key talking points. Analyze the ongoing interview dialogue and, crucially, the 'User-provided context' below.`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If the interviewer mentions **recent events, news, or current trends** (anything from the last 6 months), **ALWAYS use Google search** to get up-to-date information - If the interviewer mentions **recent events, news, or current trends** (anything from the last 6 months), **ALWAYS use Google search** to get up-to-date information
@ -39,13 +63,6 @@ Provide only the exact words to say in **markdown format**. No coaching, no "you
sales: { sales: {
intro: `You are a sales call assistant. Your job is to provide the exact words the salesperson should say to prospects during sales calls. Give direct, ready-to-speak responses that are persuasive and professional.`, intro: `You are a sales call assistant. Your job is to provide the exact words the salesperson should say to prospects during sales calls. Give direct, ready-to-speak responses that are persuasive and professional.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-3 sentences max)
- Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis
- Use bullet points (-) for lists when appropriate
- Focus on the most essential information only`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If the prospect mentions **recent industry trends, market changes, or current events**, **ALWAYS use Google search** to get up-to-date information - If the prospect mentions **recent industry trends, market changes, or current events**, **ALWAYS use Google search** to get up-to-date information
- If they reference **competitor information, recent funding news, or market data**, search for the latest information first - If they reference **competitor information, recent funding news, or market data**, search for the latest information first
@ -70,13 +87,6 @@ Provide only the exact words to say in **markdown format**. Be persuasive but no
meeting: { meeting: {
intro: `You are a meeting assistant. Your job is to provide the exact words to say during professional meetings, presentations, and discussions. Give direct, ready-to-speak responses that are clear and professional.`, intro: `You are a meeting assistant. Your job is to provide the exact words to say during professional meetings, presentations, and discussions. Give direct, ready-to-speak responses that are clear and professional.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-3 sentences max)
- Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis
- Use bullet points (-) for lists when appropriate
- Focus on the most essential information only`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If participants mention **recent industry news, regulatory changes, or market updates**, **ALWAYS use Google search** for current information - If participants mention **recent industry news, regulatory changes, or market updates**, **ALWAYS use Google search** for current information
- If they reference **competitor activities, recent reports, or current statistics**, search for the latest data first - If they reference **competitor activities, recent reports, or current statistics**, search for the latest data first
@ -101,13 +111,6 @@ Provide only the exact words to say in **markdown format**. Be clear, concise, a
presentation: { presentation: {
intro: `You are a presentation coach. Your job is to provide the exact words the presenter should say during presentations, pitches, and public speaking events. Give direct, ready-to-speak responses that are engaging and confident.`, intro: `You are a presentation coach. Your job is to provide the exact words the presenter should say during presentations, pitches, and public speaking events. Give direct, ready-to-speak responses that are engaging and confident.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-3 sentences max)
- Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis
- Use bullet points (-) for lists when appropriate
- Focus on the most essential information only`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If the audience asks about **recent market trends, current statistics, or latest industry data**, **ALWAYS use Google search** for up-to-date information - If the audience asks about **recent market trends, current statistics, or latest industry data**, **ALWAYS use Google search** for up-to-date information
- If they reference **recent events, new competitors, or current market conditions**, search for the latest information first - If they reference **recent events, new competitors, or current market conditions**, search for the latest information first
@ -132,13 +135,6 @@ Provide only the exact words to say in **markdown format**. Be confident, engagi
negotiation: { negotiation: {
intro: `You are a negotiation assistant. Your job is to provide the exact words to say during business negotiations, contract discussions, and deal-making conversations. Give direct, ready-to-speak responses that are strategic and professional.`, intro: `You are a negotiation assistant. Your job is to provide the exact words to say during business negotiations, contract discussions, and deal-making conversations. Give direct, ready-to-speak responses that are strategic and professional.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-3 sentences max)
- Use **markdown formatting** for better readability
- Use **bold** for key points and emphasis
- Use bullet points (-) for lists when appropriate
- Focus on the most essential information only`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If they mention **recent market pricing, current industry standards, or competitor offers**, **ALWAYS use Google search** for current benchmarks - If they mention **recent market pricing, current industry standards, or competitor offers**, **ALWAYS use Google search** for current benchmarks
- If they reference **recent legal changes, new regulations, or market conditions**, search for the latest information first - If they reference **recent legal changes, new regulations, or market conditions**, search for the latest information first
@ -163,13 +159,6 @@ Provide only the exact words to say in **markdown format**. Focus on finding win
exam: { exam: {
intro: `You are an exam assistant designed to help students pass tests efficiently. Your role is to provide direct, accurate answers to exam questions with minimal explanation - just enough to confirm the answer is correct.`, intro: `You are an exam assistant designed to help students pass tests efficiently. Your role is to provide direct, accurate answers to exam questions with minimal explanation - just enough to confirm the answer is correct.`,
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
- Keep responses SHORT and CONCISE (1-2 sentences max)
- Use **markdown formatting** for better readability
- Use **bold** for the answer choice/result
- Focus on the most essential information only
- Provide only brief justification for correctness`,
searchUsage: `**SEARCH TOOL USAGE:** searchUsage: `**SEARCH TOOL USAGE:**
- If the question involves **recent information, current events, or updated facts**, **ALWAYS use Google search** for the latest data - If the question involves **recent information, current events, or updated facts**, **ALWAYS use Google search** for the latest data
- If they reference **specific dates, statistics, or factual information** that might be outdated, search for current information - If they reference **specific dates, statistics, or factual information** that might be outdated, search for current information
@ -201,25 +190,57 @@ Provide direct exam answers in **markdown format**. Include the question text, t
}, },
}; };
function buildSystemPrompt(promptParts, customPrompt = '', googleSearchEnabled = true) { function buildSystemPrompt(
const sections = [promptParts.intro, '\n\n', promptParts.formatRequirements]; promptParts,
customPrompt = "",
googleSearchEnabled = true,
responseMode = "brief",
) {
const formatReqs =
responseModeFormats[responseMode] || responseModeFormats.brief;
const sections = [
promptParts.intro,
"\n\n",
formatReqs,
"\n\n",
codingAwareness,
];
// Only add search usage section if Google Search is enabled // Only add search usage section if Google Search is enabled
if (googleSearchEnabled) { if (googleSearchEnabled) {
sections.push('\n\n', promptParts.searchUsage); sections.push("\n\n", promptParts.searchUsage);
} }
sections.push('\n\n', promptParts.content, '\n\nUser-provided context\n-----\n', customPrompt, '\n-----\n\n', promptParts.outputInstructions); sections.push(
"\n\n",
promptParts.content,
"\n\nUser-provided context\n-----\n",
customPrompt,
"\n-----\n\n",
promptParts.outputInstructions,
);
return sections.join(''); return sections.join("");
} }
function getSystemPrompt(profile, customPrompt = '', googleSearchEnabled = true) { function getSystemPrompt(
profile,
customPrompt = "",
googleSearchEnabled = true,
responseMode = "brief",
) {
const promptParts = profilePrompts[profile] || profilePrompts.interview; const promptParts = profilePrompts[profile] || profilePrompts.interview;
return buildSystemPrompt(promptParts, customPrompt, googleSearchEnabled); return buildSystemPrompt(
promptParts,
customPrompt,
googleSearchEnabled,
responseMode,
);
} }
module.exports = { module.exports = {
profilePrompts, profilePrompts,
responseModeFormats,
codingAwareness,
getSystemPrompt, getSystemPrompt,
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
const { BrowserWindow, globalShortcut, ipcMain, screen } = require('electron'); const { BrowserWindow, globalShortcut, ipcMain, screen } = require("electron");
const path = require('node:path'); const path = require("node:path");
const storage = require('../storage'); const storage = require("../storage");
let mouseEventsIgnored = false; let mouseEventsIgnored = false;
@ -20,21 +20,21 @@ function createWindow(sendToRenderer, geminiSessionRef) {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, // TODO: change to true contextIsolation: false, // TODO: change to true
backgroundThrottling: false, backgroundThrottling: false,
enableBlinkFeatures: 'GetDisplayMedia', enableBlinkFeatures: "GetDisplayMedia",
webSecurity: true, webSecurity: true,
allowRunningInsecureContent: false, allowRunningInsecureContent: false,
}, },
backgroundColor: '#00000000', backgroundColor: "#00000000",
}); });
const { session, desktopCapturer } = require('electron'); const { session, desktopCapturer } = require("electron");
session.defaultSession.setDisplayMediaRequestHandler( session.defaultSession.setDisplayMediaRequestHandler(
(request, callback) => { (request, callback) => {
desktopCapturer.getSources({ types: ['screen'] }).then(sources => { desktopCapturer.getSources({ types: ["screen"] }).then((sources) => {
callback({ video: sources[0], audio: 'loopback' }); callback({ video: sources[0], audio: "loopback" });
}); });
}, },
{ useSystemPicker: true } { useSystemPicker: true },
); );
mainWindow.setResizable(false); mainWindow.setResizable(false);
@ -42,20 +42,20 @@ function createWindow(sendToRenderer, geminiSessionRef) {
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
// Hide from Windows taskbar // Hide from Windows taskbar
if (process.platform === 'win32') { if (process.platform === "win32") {
try { try {
mainWindow.setSkipTaskbar(true); mainWindow.setSkipTaskbar(true);
} catch (error) { } catch (error) {
console.warn('Could not hide from taskbar:', error.message); console.warn("Could not hide from taskbar:", error.message);
} }
} }
// Hide from Mission Control on macOS // Hide from Mission Control on macOS
if (process.platform === 'darwin') { if (process.platform === "darwin") {
try { try {
mainWindow.setHiddenInMissionControl(true); mainWindow.setHiddenInMissionControl(true);
} catch (error) { } catch (error) {
console.warn('Could not hide from Mission Control:', error.message); console.warn("Could not hide from Mission Control:", error.message);
} }
} }
@ -66,14 +66,14 @@ function createWindow(sendToRenderer, geminiSessionRef) {
const y = 0; const y = 0;
mainWindow.setPosition(x, y); mainWindow.setPosition(x, y);
if (process.platform === 'win32') { if (process.platform === "win32") {
mainWindow.setAlwaysOnTop(true, 'screen-saver', 1); mainWindow.setAlwaysOnTop(true, "screen-saver", 1);
} }
mainWindow.loadFile(path.join(__dirname, '../index.html')); mainWindow.loadFile(path.join(__dirname, "../index.html"));
// After window is created, initialize keybinds // After window is created, initialize keybinds
mainWindow.webContents.once('dom-ready', () => { mainWindow.webContents.once("dom-ready", () => {
setTimeout(() => { setTimeout(() => {
const defaultKeybinds = getDefaultKeybinds(); const defaultKeybinds = getDefaultKeybinds();
let keybinds = defaultKeybinds; let keybinds = defaultKeybinds;
@ -84,7 +84,12 @@ function createWindow(sendToRenderer, geminiSessionRef) {
keybinds = { ...defaultKeybinds, ...savedKeybinds }; keybinds = { ...defaultKeybinds, ...savedKeybinds };
} }
updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef); updateGlobalShortcuts(
keybinds,
mainWindow,
sendToRenderer,
geminiSessionRef,
);
}, 150); }, 150);
}); });
@ -94,25 +99,31 @@ function createWindow(sendToRenderer, geminiSessionRef) {
} }
function getDefaultKeybinds() { function getDefaultKeybinds() {
const isMac = process.platform === 'darwin'; const isMac = process.platform === "darwin";
return { return {
moveUp: isMac ? 'Alt+Up' : 'Ctrl+Up', moveUp: isMac ? "Alt+Up" : "Ctrl+Up",
moveDown: isMac ? 'Alt+Down' : 'Ctrl+Down', moveDown: isMac ? "Alt+Down" : "Ctrl+Down",
moveLeft: isMac ? 'Alt+Left' : 'Ctrl+Left', moveLeft: isMac ? "Alt+Left" : "Ctrl+Left",
moveRight: isMac ? 'Alt+Right' : 'Ctrl+Right', moveRight: isMac ? "Alt+Right" : "Ctrl+Right",
toggleVisibility: isMac ? 'Cmd+\\' : 'Ctrl+\\', toggleVisibility: isMac ? "Cmd+\\" : "Ctrl+\\",
toggleClickThrough: isMac ? 'Cmd+M' : 'Ctrl+M', toggleClickThrough: isMac ? "Cmd+M" : "Ctrl+M",
nextStep: isMac ? 'Cmd+Enter' : 'Ctrl+Enter', nextStep: isMac ? "Cmd+Enter" : "Ctrl+Enter",
previousResponse: isMac ? 'Cmd+[' : 'Ctrl+[', previousResponse: isMac ? "Cmd+[" : "Ctrl+[",
nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]', nextResponse: isMac ? "Cmd+]" : "Ctrl+]",
scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up', scrollUp: isMac ? "Cmd+Shift+Up" : "Ctrl+Shift+Up",
scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down', scrollDown: isMac ? "Cmd+Shift+Down" : "Ctrl+Shift+Down",
emergencyErase: isMac ? 'Cmd+Shift+E' : 'Ctrl+Shift+E', expandResponse: isMac ? "Cmd+E" : "Ctrl+E",
emergencyErase: isMac ? "Cmd+Shift+E" : "Ctrl+Shift+E",
}; };
} }
function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef) { function updateGlobalShortcuts(
console.log('Updating global shortcuts with:', keybinds); keybinds,
mainWindow,
sendToRenderer,
geminiSessionRef,
) {
console.log("Updating global shortcuts with:", keybinds);
// Unregister all existing shortcuts // Unregister all existing shortcuts
globalShortcut.unregisterAll(); globalShortcut.unregisterAll();
@ -146,7 +157,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
}; };
// Register each movement shortcut // Register each movement shortcut
Object.keys(movementActions).forEach(action => { Object.keys(movementActions).forEach((action) => {
const keybind = keybinds[action]; const keybind = keybinds[action];
if (keybind) { if (keybind) {
try { try {
@ -170,7 +181,10 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
}); });
console.log(`Registered toggleVisibility: ${keybinds.toggleVisibility}`); console.log(`Registered toggleVisibility: ${keybinds.toggleVisibility}`);
} catch (error) { } catch (error) {
console.error(`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`, error); console.error(
`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`,
error,
);
} }
} }
@ -181,16 +195,24 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
mouseEventsIgnored = !mouseEventsIgnored; mouseEventsIgnored = !mouseEventsIgnored;
if (mouseEventsIgnored) { if (mouseEventsIgnored) {
mainWindow.setIgnoreMouseEvents(true, { forward: true }); mainWindow.setIgnoreMouseEvents(true, { forward: true });
console.log('Mouse events ignored'); console.log("Mouse events ignored");
} else { } else {
mainWindow.setIgnoreMouseEvents(false); mainWindow.setIgnoreMouseEvents(false);
console.log('Mouse events enabled'); console.log("Mouse events enabled");
} }
mainWindow.webContents.send('click-through-toggled', mouseEventsIgnored); mainWindow.webContents.send(
"click-through-toggled",
mouseEventsIgnored,
);
}); });
console.log(`Registered toggleClickThrough: ${keybinds.toggleClickThrough}`); console.log(
`Registered toggleClickThrough: ${keybinds.toggleClickThrough}`,
);
} catch (error) { } catch (error) {
console.error(`Failed to register toggleClickThrough (${keybinds.toggleClickThrough}):`, error); console.error(
`Failed to register toggleClickThrough (${keybinds.toggleClickThrough}):`,
error,
);
} }
} }
@ -198,23 +220,26 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.nextStep) { if (keybinds.nextStep) {
try { try {
globalShortcut.register(keybinds.nextStep, async () => { globalShortcut.register(keybinds.nextStep, async () => {
console.log('Next step shortcut triggered'); console.log("Next step shortcut triggered");
try { try {
// Determine the shortcut key format // Determine the shortcut key format
const isMac = process.platform === 'darwin'; const isMac = process.platform === "darwin";
const shortcutKey = isMac ? 'cmd+enter' : 'ctrl+enter'; const shortcutKey = isMac ? "cmd+enter" : "ctrl+enter";
// Use the new handleShortcut function // Use the new handleShortcut function
mainWindow.webContents.executeJavaScript(` mainWindow.webContents.executeJavaScript(`
cheatingDaddy.handleShortcut('${shortcutKey}'); cheatingDaddy.handleShortcut('${shortcutKey}');
`); `);
} catch (error) { } catch (error) {
console.error('Error handling next step shortcut:', error); console.error("Error handling next step shortcut:", error);
} }
}); });
console.log(`Registered nextStep: ${keybinds.nextStep}`); console.log(`Registered nextStep: ${keybinds.nextStep}`);
} catch (error) { } catch (error) {
console.error(`Failed to register nextStep (${keybinds.nextStep}):`, error); console.error(
`Failed to register nextStep (${keybinds.nextStep}):`,
error,
);
} }
} }
@ -222,12 +247,15 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.previousResponse) { if (keybinds.previousResponse) {
try { try {
globalShortcut.register(keybinds.previousResponse, () => { globalShortcut.register(keybinds.previousResponse, () => {
console.log('Previous response shortcut triggered'); console.log("Previous response shortcut triggered");
sendToRenderer('navigate-previous-response'); sendToRenderer("navigate-previous-response");
}); });
console.log(`Registered previousResponse: ${keybinds.previousResponse}`); console.log(`Registered previousResponse: ${keybinds.previousResponse}`);
} catch (error) { } catch (error) {
console.error(`Failed to register previousResponse (${keybinds.previousResponse}):`, error); console.error(
`Failed to register previousResponse (${keybinds.previousResponse}):`,
error,
);
} }
} }
@ -235,12 +263,15 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.nextResponse) { if (keybinds.nextResponse) {
try { try {
globalShortcut.register(keybinds.nextResponse, () => { globalShortcut.register(keybinds.nextResponse, () => {
console.log('Next response shortcut triggered'); console.log("Next response shortcut triggered");
sendToRenderer('navigate-next-response'); sendToRenderer("navigate-next-response");
}); });
console.log(`Registered nextResponse: ${keybinds.nextResponse}`); console.log(`Registered nextResponse: ${keybinds.nextResponse}`);
} catch (error) { } catch (error) {
console.error(`Failed to register nextResponse (${keybinds.nextResponse}):`, error); console.error(
`Failed to register nextResponse (${keybinds.nextResponse}):`,
error,
);
} }
} }
@ -248,12 +279,15 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.scrollUp) { if (keybinds.scrollUp) {
try { try {
globalShortcut.register(keybinds.scrollUp, () => { globalShortcut.register(keybinds.scrollUp, () => {
console.log('Scroll up shortcut triggered'); console.log("Scroll up shortcut triggered");
sendToRenderer('scroll-response-up'); sendToRenderer("scroll-response-up");
}); });
console.log(`Registered scrollUp: ${keybinds.scrollUp}`); console.log(`Registered scrollUp: ${keybinds.scrollUp}`);
} catch (error) { } catch (error) {
console.error(`Failed to register scrollUp (${keybinds.scrollUp}):`, error); console.error(
`Failed to register scrollUp (${keybinds.scrollUp}):`,
error,
);
} }
} }
@ -261,12 +295,31 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.scrollDown) { if (keybinds.scrollDown) {
try { try {
globalShortcut.register(keybinds.scrollDown, () => { globalShortcut.register(keybinds.scrollDown, () => {
console.log('Scroll down shortcut triggered'); console.log("Scroll down shortcut triggered");
sendToRenderer('scroll-response-down'); sendToRenderer("scroll-response-down");
}); });
console.log(`Registered scrollDown: ${keybinds.scrollDown}`); console.log(`Registered scrollDown: ${keybinds.scrollDown}`);
} catch (error) { } catch (error) {
console.error(`Failed to register scrollDown (${keybinds.scrollDown}):`, error); console.error(
`Failed to register scrollDown (${keybinds.scrollDown}):`,
error,
);
}
}
// Register expand response shortcut
if (keybinds.expandResponse) {
try {
globalShortcut.register(keybinds.expandResponse, () => {
console.log("Expand response shortcut triggered");
sendToRenderer("expand-response");
});
console.log(`Registered expandResponse: ${keybinds.expandResponse}`);
} catch (error) {
console.error(
`Failed to register expandResponse (${keybinds.expandResponse}):`,
error,
);
} }
} }
@ -274,7 +327,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
if (keybinds.emergencyErase) { if (keybinds.emergencyErase) {
try { try {
globalShortcut.register(keybinds.emergencyErase, () => { globalShortcut.register(keybinds.emergencyErase, () => {
console.log('Emergency Erase triggered!'); console.log("Emergency Erase triggered!");
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.hide(); mainWindow.hide();
@ -283,28 +336,31 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
geminiSessionRef.current = null; geminiSessionRef.current = null;
} }
sendToRenderer('clear-sensitive-data'); sendToRenderer("clear-sensitive-data");
setTimeout(() => { setTimeout(() => {
const { app } = require('electron'); const { app } = require("electron");
app.quit(); app.quit();
}, 300); }, 300);
} }
}); });
console.log(`Registered emergencyErase: ${keybinds.emergencyErase}`); console.log(`Registered emergencyErase: ${keybinds.emergencyErase}`);
} catch (error) { } catch (error) {
console.error(`Failed to register emergencyErase (${keybinds.emergencyErase}):`, error); console.error(
`Failed to register emergencyErase (${keybinds.emergencyErase}):`,
error,
);
} }
} }
} }
function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) { function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) {
ipcMain.on('view-changed', (event, view) => { ipcMain.on("view-changed", (event, view) => {
if (!mainWindow.isDestroyed()) { if (!mainWindow.isDestroyed()) {
const primaryDisplay = screen.getPrimaryDisplay(); const primaryDisplay = screen.getPrimaryDisplay();
const { width: screenWidth } = primaryDisplay.workAreaSize; const { width: screenWidth } = primaryDisplay.workAreaSize;
if (view === 'assistant') { if (view === "assistant") {
// Shrink window for live view // Shrink window for live view
const liveWidth = 850; const liveWidth = 850;
const liveHeight = 400; const liveHeight = 400;
@ -323,22 +379,27 @@ function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) {
} }
}); });
ipcMain.handle('window-minimize', () => { ipcMain.handle("window-minimize", () => {
if (!mainWindow.isDestroyed()) { if (!mainWindow.isDestroyed()) {
mainWindow.minimize(); mainWindow.minimize();
} }
}); });
ipcMain.on('update-keybinds', (event, newKeybinds) => { ipcMain.on("update-keybinds", (event, newKeybinds) => {
if (!mainWindow.isDestroyed()) { if (!mainWindow.isDestroyed()) {
updateGlobalShortcuts(newKeybinds, mainWindow, sendToRenderer, geminiSessionRef); updateGlobalShortcuts(
newKeybinds,
mainWindow,
sendToRenderer,
geminiSessionRef,
);
} }
}); });
ipcMain.handle('toggle-window-visibility', async event => { ipcMain.handle("toggle-window-visibility", async (event) => {
try { try {
if (mainWindow.isDestroyed()) { if (mainWindow.isDestroyed()) {
return { success: false, error: 'Window has been destroyed' }; return { success: false, error: "Window has been destroyed" };
} }
if (mainWindow.isVisible()) { if (mainWindow.isVisible()) {
@ -348,12 +409,12 @@ function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) {
} }
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error('Error toggling window visibility:', error); console.error("Error toggling window visibility:", error);
return { success: false, error: error.message }; return { success: false, error: error.message };
} }
}); });
ipcMain.handle('update-sizes', async event => { ipcMain.handle("update-sizes", async (event) => {
// With the sidebar layout, the window size is user-controlled. // With the sidebar layout, the window size is user-controlled.
// This handler is kept for compatibility but is a no-op now. // This handler is kept for compatibility but is a no-op now.
return { success: true }; return { success: true };