Refactor window management and global shortcuts handling
This commit is contained in:
parent
494e692738
commit
4cf48ee0af
@ -1,12 +1,12 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { MainView } from '../views/MainView.js';
|
||||
import { CustomizeView } from '../views/CustomizeView.js';
|
||||
import { HelpView } from '../views/HelpView.js';
|
||||
import { HistoryView } from '../views/HistoryView.js';
|
||||
import { AssistantView } from '../views/AssistantView.js';
|
||||
import { OnboardingView } from '../views/OnboardingView.js';
|
||||
import { AICustomizeView } from '../views/AICustomizeView.js';
|
||||
import { FeedbackView } from '../views/FeedbackView.js';
|
||||
import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js";
|
||||
import { MainView } from "../views/MainView.js";
|
||||
import { CustomizeView } from "../views/CustomizeView.js";
|
||||
import { HelpView } from "../views/HelpView.js";
|
||||
import { HistoryView } from "../views/HistoryView.js";
|
||||
import { AssistantView } from "../views/AssistantView.js";
|
||||
import { OnboardingView } from "../views/OnboardingView.js";
|
||||
import { AICustomizeView } from "../views/AICustomizeView.js";
|
||||
import { FeedbackView } from "../views/FeedbackView.js";
|
||||
|
||||
export class CheatingDaddyApp extends LitElement {
|
||||
static styles = css`
|
||||
@ -81,15 +81,15 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
|
||||
.traffic-light.close {
|
||||
background: #FF5F57;
|
||||
background: #ff5f57;
|
||||
}
|
||||
|
||||
.traffic-light.minimize {
|
||||
background: #FEBC2E;
|
||||
background: #febc2e;
|
||||
}
|
||||
|
||||
.traffic-light.maximize {
|
||||
background: #28C840;
|
||||
background: #28c840;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@ -100,7 +100,10 @@ export class CheatingDaddyApp extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
@ -144,7 +147,9 @@ export class CheatingDaddyApp extends LitElement {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
cursor: pointer;
|
||||
transition: color var(--transition), background var(--transition);
|
||||
transition:
|
||||
color var(--transition),
|
||||
background var(--transition);
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
@ -187,7 +192,9 @@ export class CheatingDaddyApp extends LitElement {
|
||||
font-weight: var(--font-weight-medium);
|
||||
cursor: pointer;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -197,11 +204,23 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
|
||||
@keyframes update-wobble {
|
||||
0%, 90%, 100% { transform: rotate(0deg); }
|
||||
92% { transform: rotate(-2deg); }
|
||||
94% { transform: rotate(2deg); }
|
||||
96% { transform: rotate(-1.5deg); }
|
||||
98% { transform: rotate(1.5deg); }
|
||||
0%,
|
||||
90%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
92% {
|
||||
transform: rotate(-2deg);
|
||||
}
|
||||
94% {
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
96% {
|
||||
transform: rotate(-1.5deg);
|
||||
}
|
||||
98% {
|
||||
transform: rotate(1.5deg);
|
||||
}
|
||||
}
|
||||
|
||||
.update-btn svg {
|
||||
@ -367,16 +386,16 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.currentView = 'main';
|
||||
this.statusText = '';
|
||||
this.currentView = "main";
|
||||
this.statusText = "";
|
||||
this.startTime = null;
|
||||
this.isRecording = false;
|
||||
this.sessionActive = false;
|
||||
this.selectedProfile = 'interview';
|
||||
this.selectedLanguage = 'en-US';
|
||||
this.selectedScreenshotInterval = '5';
|
||||
this.selectedImageQuality = 'medium';
|
||||
this.layoutMode = 'normal';
|
||||
this.selectedProfile = "interview";
|
||||
this.selectedLanguage = "en-US";
|
||||
this.selectedScreenshotInterval = "5";
|
||||
this.selectedImageQuality = "medium";
|
||||
this.layoutMode = "normal";
|
||||
this.responses = [];
|
||||
this.currentResponseIndex = -1;
|
||||
this._viewInstances = new Map();
|
||||
@ -388,7 +407,7 @@ export class CheatingDaddyApp extends LitElement {
|
||||
this._timerInterval = null;
|
||||
this._updateAvailable = false;
|
||||
this._whisperDownloading = false;
|
||||
this._localVersion = '';
|
||||
this._localVersion = "";
|
||||
|
||||
this._loadFromStorage();
|
||||
this._checkForUpdates();
|
||||
@ -399,16 +418,22 @@ export class CheatingDaddyApp extends LitElement {
|
||||
this._localVersion = await cheatingDaddy.getVersion();
|
||||
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;
|
||||
const remote = await res.json();
|
||||
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 [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.requestUpdate();
|
||||
}
|
||||
@ -421,20 +446,20 @@ export class CheatingDaddyApp extends LitElement {
|
||||
try {
|
||||
const [config, prefs] = await Promise.all([
|
||||
cheatingDaddy.storage.getConfig(),
|
||||
cheatingDaddy.storage.getPreferences()
|
||||
cheatingDaddy.storage.getPreferences(),
|
||||
]);
|
||||
|
||||
this.currentView = config.onboarded ? 'main' : 'onboarding';
|
||||
this.selectedProfile = prefs.selectedProfile || 'interview';
|
||||
this.selectedLanguage = prefs.selectedLanguage || 'en-US';
|
||||
this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || '5';
|
||||
this.selectedImageQuality = prefs.selectedImageQuality || 'medium';
|
||||
this.layoutMode = config.layout || 'normal';
|
||||
this.currentView = config.onboarded ? "main" : "onboarding";
|
||||
this.selectedProfile = prefs.selectedProfile || "interview";
|
||||
this.selectedLanguage = prefs.selectedLanguage || "en-US";
|
||||
this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || "5";
|
||||
this.selectedImageQuality = prefs.selectedImageQuality || "medium";
|
||||
this.layoutMode = config.layout || "normal";
|
||||
|
||||
this._storageLoaded = true;
|
||||
this.requestUpdate();
|
||||
} catch (error) {
|
||||
console.error('Error loading from storage:', error);
|
||||
console.error("Error loading from storage:", error);
|
||||
this._storageLoaded = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
@ -444,13 +469,23 @@ export class CheatingDaddyApp extends LitElement {
|
||||
super.connectedCallback();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('new-response', (_, response) => this.addNewResponse(response));
|
||||
ipcRenderer.on('update-response', (_, response) => this.updateCurrentResponse(response));
|
||||
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; });
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.on("new-response", (_, response) =>
|
||||
this.addNewResponse(response),
|
||||
);
|
||||
ipcRenderer.on("update-response", (_, response) =>
|
||||
this.updateCurrentResponse(response),
|
||||
);
|
||||
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();
|
||||
this._stopTimer();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('new-response');
|
||||
ipcRenderer.removeAllListeners('update-response');
|
||||
ipcRenderer.removeAllListeners('update-status');
|
||||
ipcRenderer.removeAllListeners('click-through-toggled');
|
||||
ipcRenderer.removeAllListeners('reconnect-failed');
|
||||
ipcRenderer.removeAllListeners('whisper-downloading');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.removeAllListeners("new-response");
|
||||
ipcRenderer.removeAllListeners("update-response");
|
||||
ipcRenderer.removeAllListeners("update-status");
|
||||
ipcRenderer.removeAllListeners("click-through-toggled");
|
||||
ipcRenderer.removeAllListeners("reconnect-failed");
|
||||
ipcRenderer.removeAllListeners("whisper-downloading");
|
||||
}
|
||||
}
|
||||
|
||||
@ -485,12 +520,12 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
|
||||
getElapsedTime() {
|
||||
if (!this.startTime) return '0:00';
|
||||
if (!this.startTime) return "0:00";
|
||||
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
||||
const h = Math.floor(elapsed / 3600);
|
||||
const m = Math.floor((elapsed % 3600) / 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)}`;
|
||||
return `${m}:${pad(s)}`;
|
||||
}
|
||||
@ -499,7 +534,11 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
setStatus(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;
|
||||
}
|
||||
}
|
||||
@ -531,34 +570,34 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
|
||||
async handleClose() {
|
||||
if (this.currentView === 'assistant') {
|
||||
if (this.currentView === "assistant") {
|
||||
cheatingDaddy.stopCapture();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('close-session');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("close-session");
|
||||
}
|
||||
this.sessionActive = false;
|
||||
this._stopTimer();
|
||||
this.currentView = 'main';
|
||||
this.currentView = "main";
|
||||
} else {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('quit-application');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("quit-application");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _handleMinimize() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('window-minimize');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("window-minimize");
|
||||
}
|
||||
}
|
||||
|
||||
async handleHideToggle() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('toggle-window-visibility');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("toggle-window-visibility");
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,12 +605,12 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
async handleStart() {
|
||||
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);
|
||||
if (!success) {
|
||||
const mainView = this.shadowRoot.querySelector('main-view');
|
||||
const mainView = this.shadowRoot.querySelector("main-view");
|
||||
if (mainView && mainView.triggerApiKeyError) {
|
||||
mainView.triggerApiKeyError();
|
||||
}
|
||||
@ -579,37 +618,49 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
} else {
|
||||
const apiKey = await cheatingDaddy.storage.getApiKey();
|
||||
if (!apiKey || apiKey === '') {
|
||||
const mainView = this.shadowRoot.querySelector('main-view');
|
||||
if (!apiKey || apiKey === "") {
|
||||
const mainView = this.shadowRoot.querySelector("main-view");
|
||||
if (mainView && mainView.triggerApiKeyError) {
|
||||
mainView.triggerApiKeyError();
|
||||
}
|
||||
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.currentResponseIndex = -1;
|
||||
this.startTime = Date.now();
|
||||
this.sessionActive = true;
|
||||
this.currentView = 'assistant';
|
||||
this.currentView = "assistant";
|
||||
this._startTimer();
|
||||
}
|
||||
|
||||
async handleAPIKeyHelp() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('open-external', 'https://cheatingdaddy.com/help/api-key');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke(
|
||||
"open-external",
|
||||
"https://cheatingdaddy.com/help/api-key",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleGroqAPIKeyHelp() {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('open-external', 'https://console.groq.com/keys');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke(
|
||||
"open-external",
|
||||
"https://console.groq.com/keys",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -617,33 +668,39 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
async handleProfileChange(profile) {
|
||||
this.selectedProfile = profile;
|
||||
await cheatingDaddy.storage.updatePreference('selectedProfile', profile);
|
||||
await cheatingDaddy.storage.updatePreference("selectedProfile", profile);
|
||||
}
|
||||
|
||||
async handleLanguageChange(language) {
|
||||
this.selectedLanguage = language;
|
||||
await cheatingDaddy.storage.updatePreference('selectedLanguage', language);
|
||||
await cheatingDaddy.storage.updatePreference("selectedLanguage", language);
|
||||
}
|
||||
|
||||
async handleScreenshotIntervalChange(interval) {
|
||||
this.selectedScreenshotInterval = interval;
|
||||
await cheatingDaddy.storage.updatePreference('selectedScreenshotInterval', interval);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"selectedScreenshotInterval",
|
||||
interval,
|
||||
);
|
||||
}
|
||||
|
||||
async handleImageQualityChange(quality) {
|
||||
this.selectedImageQuality = quality;
|
||||
await cheatingDaddy.storage.updatePreference('selectedImageQuality', quality);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"selectedImageQuality",
|
||||
quality,
|
||||
);
|
||||
}
|
||||
|
||||
async handleLayoutModeChange(layoutMode) {
|
||||
this.layoutMode = layoutMode;
|
||||
await cheatingDaddy.storage.updateConfig('layout', layoutMode);
|
||||
await cheatingDaddy.storage.updateConfig("layout", layoutMode);
|
||||
if (window.require) {
|
||||
try {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('update-sizes');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("update-sizes");
|
||||
} catch (error) {
|
||||
console.error('Failed to update sizes:', error);
|
||||
console.error("Failed to update sizes:", error);
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
@ -651,17 +708,27 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
async handleExternalLinkClick(url) {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('open-external', url);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("open-external", url);
|
||||
}
|
||||
}
|
||||
|
||||
async handleSendText(message) {
|
||||
const result = await window.cheatingDaddy.sendTextMessage(message);
|
||||
if (!result.success) {
|
||||
this.setStatus('Error sending message: ' + result.error);
|
||||
this.setStatus("Error sending message: " + result.error);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -673,29 +740,29 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
|
||||
handleOnboardingComplete() {
|
||||
this.currentView = 'main';
|
||||
this.currentView = "main";
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('currentView') && window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('view-changed', this.currentView);
|
||||
if (changedProperties.has("currentView") && window.require) {
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.send("view-changed", this.currentView);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
_isLiveMode() {
|
||||
return this.currentView === 'assistant';
|
||||
return this.currentView === "assistant";
|
||||
}
|
||||
|
||||
// ── Render ──
|
||||
|
||||
renderCurrentView() {
|
||||
switch (this.currentView) {
|
||||
case 'onboarding':
|
||||
case "onboarding":
|
||||
return html`
|
||||
<onboarding-view
|
||||
.onComplete=${() => this.handleOnboardingComplete()}
|
||||
@ -703,26 +770,26 @@ export class CheatingDaddyApp extends LitElement {
|
||||
></onboarding-view>
|
||||
`;
|
||||
|
||||
case 'main':
|
||||
case "main":
|
||||
return html`
|
||||
<main-view
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
.onProfileChange=${p => this.handleProfileChange(p)}
|
||||
.onProfileChange=${(p) => this.handleProfileChange(p)}
|
||||
.onStart=${() => this.handleStart()}
|
||||
.onExternalLink=${url => this.handleExternalLinkClick(url)}
|
||||
.onExternalLink=${(url) => this.handleExternalLinkClick(url)}
|
||||
.whisperDownloading=${this._whisperDownloading}
|
||||
></main-view>
|
||||
`;
|
||||
|
||||
case 'ai-customize':
|
||||
case "ai-customize":
|
||||
return html`
|
||||
<ai-customize-view
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
.onProfileChange=${p => this.handleProfileChange(p)}
|
||||
.onProfileChange=${(p) => this.handleProfileChange(p)}
|
||||
></ai-customize-view>
|
||||
`;
|
||||
|
||||
case 'customize':
|
||||
case "customize":
|
||||
return html`
|
||||
<customize-view
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
@ -730,30 +797,34 @@ export class CheatingDaddyApp extends LitElement {
|
||||
.selectedScreenshotInterval=${this.selectedScreenshotInterval}
|
||||
.selectedImageQuality=${this.selectedImageQuality}
|
||||
.layoutMode=${this.layoutMode}
|
||||
.onProfileChange=${p => this.handleProfileChange(p)}
|
||||
.onLanguageChange=${l => this.handleLanguageChange(l)}
|
||||
.onScreenshotIntervalChange=${i => this.handleScreenshotIntervalChange(i)}
|
||||
.onImageQualityChange=${q => this.handleImageQualityChange(q)}
|
||||
.onLayoutModeChange=${lm => this.handleLayoutModeChange(lm)}
|
||||
.onProfileChange=${(p) => this.handleProfileChange(p)}
|
||||
.onLanguageChange=${(l) => this.handleLanguageChange(l)}
|
||||
.onScreenshotIntervalChange=${(i) =>
|
||||
this.handleScreenshotIntervalChange(i)}
|
||||
.onImageQualityChange=${(q) => this.handleImageQualityChange(q)}
|
||||
.onLayoutModeChange=${(lm) => this.handleLayoutModeChange(lm)}
|
||||
></customize-view>
|
||||
`;
|
||||
|
||||
case 'feedback':
|
||||
case "feedback":
|
||||
return html`<feedback-view></feedback-view>`;
|
||||
|
||||
case 'help':
|
||||
return html`<help-view .onExternalLinkClick=${url => this.handleExternalLinkClick(url)}></help-view>`;
|
||||
case "help":
|
||||
return html`<help-view
|
||||
.onExternalLinkClick=${(url) => this.handleExternalLinkClick(url)}
|
||||
></help-view>`;
|
||||
|
||||
case 'history':
|
||||
case "history":
|
||||
return html`<history-view></history-view>`;
|
||||
|
||||
case 'assistant':
|
||||
case "assistant":
|
||||
return html`
|
||||
<assistant-view
|
||||
.responses=${this.responses}
|
||||
.currentResponseIndex=${this.currentResponseIndex}
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
.onSendText=${msg => this.handleSendText(msg)}
|
||||
.onSendText=${(msg) => this.handleSendText(msg)}
|
||||
.onExpandResponse=${() => this.handleExpandResponse()}
|
||||
.shouldAnimateResponse=${this.shouldAnimateResponse}
|
||||
@response-index-changed=${this.handleResponseIndexChanged}
|
||||
@response-animation-complete=${() => {
|
||||
@ -771,74 +842,238 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
renderSidebar() {
|
||||
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: '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>` },
|
||||
{
|
||||
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: "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`
|
||||
<div class="sidebar ${this._isLiveMode() ? 'hidden' : ''}">
|
||||
<div class="sidebar ${this._isLiveMode() ? "hidden" : ""}">
|
||||
<div class="sidebar-brand">
|
||||
<h1>Mastermind</h1>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
${items.map(item => html`
|
||||
${items.map(
|
||||
(item) => html`
|
||||
<button
|
||||
class="nav-item ${this.currentView === item.id ? 'active' : ''}"
|
||||
class="nav-item ${this.currentView === item.id ? "active" : ""}"
|
||||
@click=${() => this.navigate(item.id)}
|
||||
title=${item.label}
|
||||
>
|
||||
${item.icon}
|
||||
${item.label}
|
||||
${item.icon} ${item.label}
|
||||
</button>
|
||||
`)}
|
||||
`,
|
||||
)}
|
||||
</nav>
|
||||
<div class="sidebar-footer">
|
||||
${this._updateAvailable ? html`
|
||||
<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>
|
||||
${this._updateAvailable
|
||||
? html`
|
||||
<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
|
||||
</button>
|
||||
` : html`
|
||||
<div class="version-text">v${this._localVersion}</div>
|
||||
`}
|
||||
`
|
||||
: html` <div class="version-text">v${this._localVersion}</div> `}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderLiveBar() {
|
||||
if (!this._isLiveMode()) return '';
|
||||
if (!this._isLiveMode()) return "";
|
||||
|
||||
const profileLabels = {
|
||||
interview: 'Interview',
|
||||
sales: 'Sales Call',
|
||||
meeting: 'Meeting',
|
||||
presentation: 'Presentation',
|
||||
negotiation: 'Negotiation',
|
||||
exam: 'Exam',
|
||||
interview: "Interview",
|
||||
sales: "Sales Call",
|
||||
meeting: "Meeting",
|
||||
presentation: "Presentation",
|
||||
negotiation: "Negotiation",
|
||||
exam: "Exam",
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="live-bar">
|
||||
<div class="live-bar-left">
|
||||
<button class="live-bar-back" @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" />
|
||||
<button
|
||||
class="live-bar-back"
|
||||
@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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="live-bar-center">
|
||||
${profileLabels[this.selectedProfile] || 'Session'}
|
||||
${profileLabels[this.selectedProfile] || "Session"}
|
||||
</div>
|
||||
<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>
|
||||
${this._isClickThrough ? html`<span class="live-bar-text">[click through]</span>` : ''}
|
||||
<span class="live-bar-text clickable" @click=${() => this.handleHideToggle()}>[hide]</span>
|
||||
${this._isClickThrough
|
||||
? html`<span class="live-bar-text">[click through]</span>`
|
||||
: ""}
|
||||
<span
|
||||
class="live-bar-text clickable"
|
||||
@click=${() => this.handleHideToggle()}
|
||||
>[hide]</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -846,30 +1081,34 @@ export class CheatingDaddyApp extends LitElement {
|
||||
|
||||
render() {
|
||||
// Onboarding is fullscreen, no sidebar
|
||||
if (this.currentView === 'onboarding') {
|
||||
return html`
|
||||
<div class="fullscreen">
|
||||
${this.renderCurrentView()}
|
||||
</div>
|
||||
`;
|
||||
if (this.currentView === "onboarding") {
|
||||
return html` <div class="fullscreen">${this.renderCurrentView()}</div> `;
|
||||
}
|
||||
|
||||
const isLive = this._isLiveMode();
|
||||
|
||||
return html`
|
||||
<div class="app-shell">
|
||||
<div class="top-drag-bar ${isLive ? 'hidden' : ''}">
|
||||
<div class="top-drag-bar ${isLive ? "hidden" : ""}">
|
||||
<div class="traffic-lights">
|
||||
<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 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>
|
||||
</div>
|
||||
<div class="drag-region"></div>
|
||||
</div>
|
||||
${this.renderSidebar()}
|
||||
<div class="content">
|
||||
${isLive ? this.renderLiveBar() : ''}
|
||||
<div class="content-inner ${isLive ? 'live' : ''}">
|
||||
${isLive ? this.renderLiveBar() : ""}
|
||||
<div class="content-inner ${isLive ? "live" : ""}">
|
||||
${this.renderCurrentView()}
|
||||
</div>
|
||||
</div>
|
||||
@ -878,4 +1117,4 @@ export class CheatingDaddyApp extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('cheating-daddy-app', CheatingDaddyApp);
|
||||
customElements.define("cheating-daddy-app", CheatingDaddyApp);
|
||||
|
||||
@ -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 {
|
||||
static styles = css`
|
||||
@ -54,12 +54,22 @@ export class AssistantView extends LitElement {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.response-container h1 { font-size: 1.5em; }
|
||||
.response-container h2 { font-size: 1.3em; }
|
||||
.response-container h3 { font-size: 1.15em; }
|
||||
.response-container h4 { font-size: 1.05em; }
|
||||
.response-container h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.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 h6 { font-size: 1em; }
|
||||
.response-container h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.response-container p {
|
||||
margin: 0.6em 0;
|
||||
@ -263,7 +273,9 @@ export class AssistantView extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: border-color 0.4s ease, background var(--transition);
|
||||
transition:
|
||||
border-color 0.4s ease,
|
||||
background var(--transition);
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -298,6 +310,52 @@ export class AssistantView extends LitElement {
|
||||
height: calc(100% + 2px);
|
||||
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 = {
|
||||
@ -305,28 +363,32 @@ export class AssistantView extends LitElement {
|
||||
currentResponseIndex: { type: Number },
|
||||
selectedProfile: { type: String },
|
||||
onSendText: { type: Function },
|
||||
onExpandResponse: { type: Function },
|
||||
shouldAnimateResponse: { type: Boolean },
|
||||
isAnalyzing: { type: Boolean, state: true },
|
||||
isExpanding: { type: Boolean, state: true },
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.responses = [];
|
||||
this.currentResponseIndex = -1;
|
||||
this.selectedProfile = 'interview';
|
||||
this.selectedProfile = "interview";
|
||||
this.onSendText = () => {};
|
||||
this.onExpandResponse = () => {};
|
||||
this.isAnalyzing = false;
|
||||
this.isExpanding = false;
|
||||
this._animFrame = null;
|
||||
}
|
||||
|
||||
getProfileNames() {
|
||||
return {
|
||||
interview: 'Job Interview',
|
||||
sales: 'Sales Call',
|
||||
meeting: 'Business Meeting',
|
||||
presentation: 'Presentation',
|
||||
negotiation: 'Negotiation',
|
||||
exam: 'Exam Assistant',
|
||||
interview: "Job Interview",
|
||||
sales: "Sales Call",
|
||||
meeting: "Business Meeting",
|
||||
presentation: "Presentation",
|
||||
negotiation: "Negotiation",
|
||||
exam: "Exam Assistant",
|
||||
};
|
||||
}
|
||||
|
||||
@ -334,11 +396,11 @@ export class AssistantView extends LitElement {
|
||||
const profileNames = this.getProfileNames();
|
||||
return this.responses.length > 0 && this.currentResponseIndex >= 0
|
||||
? this.responses[this.currentResponseIndex]
|
||||
: `Listening to your ${profileNames[this.selectedProfile] || 'session'}...`;
|
||||
: `Listening to your ${profileNames[this.selectedProfile] || "session"}...`;
|
||||
}
|
||||
|
||||
renderMarkdown(content) {
|
||||
if (typeof window !== 'undefined' && window.marked) {
|
||||
if (typeof window !== "undefined" && window.marked) {
|
||||
try {
|
||||
window.marked.setOptions({
|
||||
breaks: true,
|
||||
@ -349,7 +411,7 @@ export class AssistantView extends LitElement {
|
||||
rendered = this.wrapWordsInSpans(rendered);
|
||||
return rendered;
|
||||
} catch (error) {
|
||||
console.warn('Error parsing markdown:', error);
|
||||
console.warn("Error parsing markdown:", error);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@ -358,17 +420,21 @@ export class AssistantView extends LitElement {
|
||||
|
||||
wrapWordsInSpans(html) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
const tagsToSkip = ['PRE'];
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
const tagsToSkip = ["PRE"];
|
||||
|
||||
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 frag = document.createDocumentFragment();
|
||||
words.forEach(word => {
|
||||
words.forEach((word) => {
|
||||
if (word.trim()) {
|
||||
const span = document.createElement('span');
|
||||
span.setAttribute('data-word', '');
|
||||
const span = document.createElement("span");
|
||||
span.setAttribute("data-word", "");
|
||||
span.textContent = word;
|
||||
frag.appendChild(span);
|
||||
} else {
|
||||
@ -376,7 +442,10 @@ export class AssistantView extends LitElement {
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -388,9 +457,9 @@ export class AssistantView extends LitElement {
|
||||
if (this.currentResponseIndex > 0) {
|
||||
this.currentResponseIndex--;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('response-index-changed', {
|
||||
new CustomEvent("response-index-changed", {
|
||||
detail: { index: this.currentResponseIndex },
|
||||
})
|
||||
}),
|
||||
);
|
||||
this.requestUpdate();
|
||||
}
|
||||
@ -400,16 +469,16 @@ export class AssistantView extends LitElement {
|
||||
if (this.currentResponseIndex < this.responses.length - 1) {
|
||||
this.currentResponseIndex++;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('response-index-changed', {
|
||||
new CustomEvent("response-index-changed", {
|
||||
detail: { index: this.currentResponseIndex },
|
||||
})
|
||||
}),
|
||||
);
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
scrollResponseUp() {
|
||||
const container = this.shadowRoot.querySelector('.response-container');
|
||||
const container = this.shadowRoot.querySelector(".response-container");
|
||||
if (container) {
|
||||
const scrollAmount = container.clientHeight * 0.3;
|
||||
container.scrollTop = Math.max(0, container.scrollTop - scrollAmount);
|
||||
@ -417,10 +486,13 @@ export class AssistantView extends LitElement {
|
||||
}
|
||||
|
||||
scrollResponseDown() {
|
||||
const container = this.shadowRoot.querySelector('.response-container');
|
||||
const container = this.shadowRoot.querySelector(".response-container");
|
||||
if (container) {
|
||||
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();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
|
||||
this.handlePreviousResponse = () => this.navigateToPreviousResponse();
|
||||
this.handleNextResponse = () => this.navigateToNextResponse();
|
||||
this.handleScrollUp = () => this.scrollResponseUp();
|
||||
this.handleScrollDown = () => this.scrollResponseDown();
|
||||
this.handleExpandHotkey = () => this.handleExpandResponse();
|
||||
|
||||
ipcRenderer.on('navigate-previous-response', this.handlePreviousResponse);
|
||||
ipcRenderer.on('navigate-next-response', this.handleNextResponse);
|
||||
ipcRenderer.on('scroll-response-up', this.handleScrollUp);
|
||||
ipcRenderer.on('scroll-response-down', this.handleScrollDown);
|
||||
ipcRenderer.on("navigate-previous-response", this.handlePreviousResponse);
|
||||
ipcRenderer.on("navigate-next-response", this.handleNextResponse);
|
||||
ipcRenderer.on("scroll-response-up", this.handleScrollUp);
|
||||
ipcRenderer.on("scroll-response-down", this.handleScrollDown);
|
||||
ipcRenderer.on("expand-response", this.handleExpandHotkey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,25 +521,40 @@ export class AssistantView extends LitElement {
|
||||
this._stopWaveformAnimation();
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (this.handlePreviousResponse) ipcRenderer.removeListener('navigate-previous-response', 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);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
if (this.handlePreviousResponse)
|
||||
ipcRenderer.removeListener(
|
||||
"navigate-previous-response",
|
||||
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() {
|
||||
const textInput = this.shadowRoot.querySelector('#textInput');
|
||||
const textInput = this.shadowRoot.querySelector("#textInput");
|
||||
if (textInput && textInput.value.trim()) {
|
||||
const message = textInput.value.trim();
|
||||
textInput.value = '';
|
||||
textInput.value = "";
|
||||
await this.onSendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
handleTextKeydown(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
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() {
|
||||
const canvas = this.shadowRoot.querySelector('.analyze-canvas');
|
||||
const canvas = this.shadowRoot.querySelector(".analyze-canvas");
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
@ -491,7 +592,8 @@ export class AssistantView extends LitElement {
|
||||
canvas.height = rect.height * 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 FADE_IN = 0.5; // seconds
|
||||
const PARTICLE_SPREAD = 4; // px inward from border
|
||||
@ -542,7 +644,11 @@ export class AssistantView extends LitElement {
|
||||
// Pre-seed random offsets for stable particles
|
||||
const seeds = [];
|
||||
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) => {
|
||||
@ -585,13 +691,17 @@ export class AssistantView extends LitElement {
|
||||
ctx.strokeStyle = dangerColor;
|
||||
ctx.globalAlpha = wave.opacity * fade;
|
||||
ctx.lineWidth = wave.width;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
for (let x = 0; x <= w; x++) {
|
||||
const norm = x / w;
|
||||
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);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
@ -610,16 +720,16 @@ export class AssistantView extends LitElement {
|
||||
cancelAnimationFrame(this._animFrame);
|
||||
this._animFrame = null;
|
||||
}
|
||||
const canvas = this.shadowRoot.querySelector('.analyze-canvas');
|
||||
const canvas = this.shadowRoot.querySelector(".analyze-canvas");
|
||||
if (canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
setTimeout(() => {
|
||||
const container = this.shadowRoot.querySelector('.response-container');
|
||||
const container = this.shadowRoot.querySelector(".response-container");
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
@ -633,11 +743,14 @@ export class AssistantView extends LitElement {
|
||||
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('responses') || changedProperties.has('currentResponseIndex')) {
|
||||
if (
|
||||
changedProperties.has("responses") ||
|
||||
changedProperties.has("currentResponseIndex")
|
||||
) {
|
||||
this.updateResponseContent();
|
||||
}
|
||||
|
||||
if (changedProperties.has('isAnalyzing')) {
|
||||
if (changedProperties.has("isAnalyzing")) {
|
||||
if (this.isAnalyzing) {
|
||||
this._startWaveformAnimation();
|
||||
} 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) {
|
||||
this.isAnalyzing = false;
|
||||
this.isExpanding = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateResponseContent() {
|
||||
const container = this.shadowRoot.querySelector('#responseContainer');
|
||||
const container = this.shadowRoot.querySelector("#responseContainer");
|
||||
if (container) {
|
||||
const currentResponse = this.getCurrentResponse();
|
||||
const renderedResponse = this.renderMarkdown(currentResponse);
|
||||
container.innerHTML = renderedResponse;
|
||||
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() {
|
||||
const hasMultipleResponses = this.responses.length > 1;
|
||||
const hasResponse =
|
||||
this.responses.length > 0 && this.currentResponseIndex >= 0;
|
||||
|
||||
return html`
|
||||
<div class="response-container" id="responseContainer"></div>
|
||||
|
||||
${hasMultipleResponses ? html`
|
||||
${hasMultipleResponses || hasResponse
|
||||
? html`
|
||||
<div class="response-nav">
|
||||
<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" />
|
||||
${hasMultipleResponses
|
||||
? html`
|
||||
<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>
|
||||
</button>
|
||||
<span class="response-counter">${this.currentResponseIndex + 1} of ${this.responses.length}</span>
|
||||
<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" />
|
||||
<span class="response-counter"
|
||||
>${this.currentResponseIndex + 1} of
|
||||
${this.responses.length}</span
|
||||
>
|
||||
<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>
|
||||
</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 class="input-bar">
|
||||
<div class="input-bar-inner">
|
||||
@ -695,11 +878,26 @@ export class AssistantView extends LitElement {
|
||||
@keydown=${this.handleTextKeydown}
|
||||
/>
|
||||
</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>
|
||||
<span class="analyze-btn-content">
|
||||
<svg 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
|
||||
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>
|
||||
Analyze Screen
|
||||
</span>
|
||||
@ -709,4 +907,4 @@ export class AssistantView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('assistant-view', AssistantView);
|
||||
customElements.define("assistant-view", AssistantView);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
|
||||
import { unifiedPageStyles } from './sharedPageStyles.js';
|
||||
import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js";
|
||||
import { unifiedPageStyles } from "./sharedPageStyles.js";
|
||||
|
||||
export class CustomizeView extends LitElement {
|
||||
static styles = [
|
||||
@ -22,7 +22,7 @@ export class CustomizeView extends LitElement {
|
||||
}
|
||||
|
||||
.warning-callout::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 16px;
|
||||
@ -198,26 +198,26 @@ export class CustomizeView extends LitElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedProfile = 'interview';
|
||||
this.selectedLanguage = 'en-US';
|
||||
this.selectedImageQuality = 'medium';
|
||||
this.layoutMode = 'normal';
|
||||
this.selectedProfile = "interview";
|
||||
this.selectedLanguage = "en-US";
|
||||
this.selectedImageQuality = "medium";
|
||||
this.layoutMode = "normal";
|
||||
this.keybinds = this.getDefaultKeybinds();
|
||||
this.onProfileChange = () => {};
|
||||
this.onLanguageChange = () => {};
|
||||
this.onImageQualityChange = () => {};
|
||||
this.onLayoutModeChange = () => {};
|
||||
this.googleSearchEnabled = true;
|
||||
this.providerMode = 'byok';
|
||||
this.providerMode = "byok";
|
||||
this.isClearing = false;
|
||||
this.isRestoring = false;
|
||||
this.clearStatusMessage = '';
|
||||
this.clearStatusType = '';
|
||||
this.clearStatusMessage = "";
|
||||
this.clearStatusType = "";
|
||||
this.backgroundTransparency = 0.8;
|
||||
this.fontSize = 20;
|
||||
this.audioMode = 'speaker_only';
|
||||
this.customPrompt = '';
|
||||
this.theme = 'dark';
|
||||
this.audioMode = "speaker_only";
|
||||
this.customPrompt = "";
|
||||
this.theme = "dark";
|
||||
this._loadFromStorage();
|
||||
}
|
||||
|
||||
@ -227,14 +227,17 @@ export class CustomizeView extends LitElement {
|
||||
|
||||
async _loadFromStorage() {
|
||||
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.providerMode = prefs.providerMode || 'byok';
|
||||
this.providerMode = prefs.providerMode || "byok";
|
||||
this.backgroundTransparency = prefs.backgroundTransparency ?? 0.8;
|
||||
this.fontSize = prefs.fontSize ?? 20;
|
||||
this.audioMode = prefs.audioMode ?? 'speaker_only';
|
||||
this.customPrompt = prefs.customPrompt ?? '';
|
||||
this.theme = prefs.theme ?? 'dark';
|
||||
this.audioMode = prefs.audioMode ?? "speaker_only";
|
||||
this.customPrompt = prefs.customPrompt ?? "";
|
||||
this.theme = prefs.theme ?? "dark";
|
||||
if (keybinds) {
|
||||
this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds };
|
||||
}
|
||||
@ -242,94 +245,144 @@ export class CustomizeView extends LitElement {
|
||||
this.updateFontSize();
|
||||
this.requestUpdate();
|
||||
} catch (error) {
|
||||
console.error('Error loading settings:', error);
|
||||
console.error("Error loading settings:", error);
|
||||
}
|
||||
}
|
||||
|
||||
getProfiles() {
|
||||
return [
|
||||
{ value: 'interview', name: 'Job Interview' },
|
||||
{ value: 'sales', name: 'Sales Call' },
|
||||
{ value: 'meeting', name: 'Business Meeting' },
|
||||
{ value: 'presentation', name: 'Presentation' },
|
||||
{ value: 'negotiation', name: 'Negotiation' },
|
||||
{ value: 'exam', name: 'Exam Assistant' },
|
||||
{ value: "interview", name: "Job Interview" },
|
||||
{ value: "sales", name: "Sales Call" },
|
||||
{ value: "meeting", name: "Business Meeting" },
|
||||
{ value: "presentation", name: "Presentation" },
|
||||
{ value: "negotiation", name: "Negotiation" },
|
||||
{ value: "exam", name: "Exam Assistant" },
|
||||
];
|
||||
}
|
||||
|
||||
getLanguages() {
|
||||
return [
|
||||
{ value: 'en-US', name: 'English (US)' },
|
||||
{ value: 'en-GB', name: 'English (UK)' },
|
||||
{ value: 'en-AU', name: 'English (Australia)' },
|
||||
{ value: 'en-IN', name: 'English (India)' },
|
||||
{ value: 'de-DE', name: 'German (Germany)' },
|
||||
{ value: 'es-US', name: 'Spanish (US)' },
|
||||
{ value: 'es-ES', name: 'Spanish (Spain)' },
|
||||
{ value: 'fr-FR', name: 'French (France)' },
|
||||
{ value: 'fr-CA', name: 'French (Canada)' },
|
||||
{ value: 'hi-IN', name: 'Hindi (India)' },
|
||||
{ value: 'pt-BR', name: 'Portuguese (Brazil)' },
|
||||
{ value: 'ar-XA', name: 'Arabic (Generic)' },
|
||||
{ value: 'id-ID', name: 'Indonesian (Indonesia)' },
|
||||
{ value: 'it-IT', name: 'Italian (Italy)' },
|
||||
{ value: 'ja-JP', name: 'Japanese (Japan)' },
|
||||
{ value: 'tr-TR', name: 'Turkish (Turkey)' },
|
||||
{ value: 'vi-VN', name: 'Vietnamese (Vietnam)' },
|
||||
{ value: 'bn-IN', name: 'Bengali (India)' },
|
||||
{ value: 'gu-IN', name: 'Gujarati (India)' },
|
||||
{ value: 'kn-IN', name: 'Kannada (India)' },
|
||||
{ value: 'ml-IN', name: 'Malayalam (India)' },
|
||||
{ value: 'mr-IN', name: 'Marathi (India)' },
|
||||
{ value: 'ta-IN', name: 'Tamil (India)' },
|
||||
{ value: 'te-IN', name: 'Telugu (India)' },
|
||||
{ value: 'nl-NL', name: 'Dutch (Netherlands)' },
|
||||
{ value: 'ko-KR', name: 'Korean (South Korea)' },
|
||||
{ value: 'cmn-CN', name: 'Mandarin Chinese (China)' },
|
||||
{ value: 'pl-PL', name: 'Polish (Poland)' },
|
||||
{ value: 'ru-RU', name: 'Russian (Russia)' },
|
||||
{ value: 'th-TH', name: 'Thai (Thailand)' },
|
||||
{ value: "en-US", name: "English (US)" },
|
||||
{ value: "en-GB", name: "English (UK)" },
|
||||
{ value: "en-AU", name: "English (Australia)" },
|
||||
{ value: "en-IN", name: "English (India)" },
|
||||
{ value: "de-DE", name: "German (Germany)" },
|
||||
{ value: "es-US", name: "Spanish (US)" },
|
||||
{ value: "es-ES", name: "Spanish (Spain)" },
|
||||
{ value: "fr-FR", name: "French (France)" },
|
||||
{ value: "fr-CA", name: "French (Canada)" },
|
||||
{ value: "hi-IN", name: "Hindi (India)" },
|
||||
{ value: "pt-BR", name: "Portuguese (Brazil)" },
|
||||
{ value: "ar-XA", name: "Arabic (Generic)" },
|
||||
{ value: "id-ID", name: "Indonesian (Indonesia)" },
|
||||
{ value: "it-IT", name: "Italian (Italy)" },
|
||||
{ value: "ja-JP", name: "Japanese (Japan)" },
|
||||
{ value: "tr-TR", name: "Turkish (Turkey)" },
|
||||
{ value: "vi-VN", name: "Vietnamese (Vietnam)" },
|
||||
{ value: "bn-IN", name: "Bengali (India)" },
|
||||
{ value: "gu-IN", name: "Gujarati (India)" },
|
||||
{ value: "kn-IN", name: "Kannada (India)" },
|
||||
{ value: "ml-IN", name: "Malayalam (India)" },
|
||||
{ value: "mr-IN", name: "Marathi (India)" },
|
||||
{ value: "ta-IN", name: "Tamil (India)" },
|
||||
{ value: "te-IN", name: "Telugu (India)" },
|
||||
{ value: "nl-NL", name: "Dutch (Netherlands)" },
|
||||
{ value: "ko-KR", name: "Korean (South Korea)" },
|
||||
{ value: "cmn-CN", name: "Mandarin Chinese (China)" },
|
||||
{ value: "pl-PL", name: "Polish (Poland)" },
|
||||
{ value: "ru-RU", name: "Russian (Russia)" },
|
||||
{ value: "th-TH", name: "Thai (Thailand)" },
|
||||
];
|
||||
}
|
||||
|
||||
getDefaultKeybinds() {
|
||||
const isMac = cheatingDaddy.isMacOS || navigator.platform.includes('Mac');
|
||||
const isMac = cheatingDaddy.isMacOS || navigator.platform.includes("Mac");
|
||||
return {
|
||||
moveUp: isMac ? 'Alt+Up' : 'Ctrl+Up',
|
||||
moveDown: isMac ? 'Alt+Down' : 'Ctrl+Down',
|
||||
moveLeft: isMac ? 'Alt+Left' : 'Ctrl+Left',
|
||||
moveRight: isMac ? 'Alt+Right' : 'Ctrl+Right',
|
||||
toggleVisibility: isMac ? 'Cmd+\\' : 'Ctrl+\\',
|
||||
toggleClickThrough: isMac ? 'Cmd+M' : 'Ctrl+M',
|
||||
nextStep: isMac ? 'Cmd+Enter' : 'Ctrl+Enter',
|
||||
previousResponse: isMac ? 'Cmd+[' : 'Ctrl+[',
|
||||
nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]',
|
||||
scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up',
|
||||
scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down',
|
||||
moveUp: isMac ? "Alt+Up" : "Ctrl+Up",
|
||||
moveDown: isMac ? "Alt+Down" : "Ctrl+Down",
|
||||
moveLeft: isMac ? "Alt+Left" : "Ctrl+Left",
|
||||
moveRight: isMac ? "Alt+Right" : "Ctrl+Right",
|
||||
toggleVisibility: isMac ? "Cmd+\\" : "Ctrl+\\",
|
||||
toggleClickThrough: isMac ? "Cmd+M" : "Ctrl+M",
|
||||
nextStep: isMac ? "Cmd+Enter" : "Ctrl+Enter",
|
||||
previousResponse: isMac ? "Cmd+[" : "Ctrl+[",
|
||||
nextResponse: isMac ? "Cmd+]" : "Ctrl+]",
|
||||
scrollUp: isMac ? "Cmd+Shift+Up" : "Ctrl+Shift+Up",
|
||||
scrollDown: isMac ? "Cmd+Shift+Down" : "Ctrl+Shift+Down",
|
||||
expandResponse: isMac ? "Cmd+E" : "Ctrl+E",
|
||||
};
|
||||
}
|
||||
|
||||
getKeybindActions() {
|
||||
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: '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: "moveUp",
|
||||
name: "Move Window Up",
|
||||
description: "Move the app window up",
|
||||
},
|
||||
{
|
||||
key: "moveDown",
|
||||
name: "Move Window Down",
|
||||
description: "Move the app window down",
|
||||
},
|
||||
{
|
||||
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() {
|
||||
await cheatingDaddy.storage.setKeybinds(this.keybinds);
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('update-keybinds', this.keybinds);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.send("update-keybinds", this.keybinds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,18 +408,24 @@ export class CustomizeView extends LitElement {
|
||||
|
||||
async handleCustomPromptInput(e) {
|
||||
this.customPrompt = e.target.value;
|
||||
await cheatingDaddy.storage.updatePreference('customPrompt', this.customPrompt);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"customPrompt",
|
||||
this.customPrompt,
|
||||
);
|
||||
}
|
||||
|
||||
async handleAudioModeSelect(e) {
|
||||
this.audioMode = e.target.value;
|
||||
await cheatingDaddy.storage.updatePreference('audioMode', this.audioMode);
|
||||
await cheatingDaddy.storage.updatePreference("audioMode", this.audioMode);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
async handleProviderModeChange(e) {
|
||||
this.providerMode = e.target.value;
|
||||
await cheatingDaddy.storage.updatePreference('providerMode', this.providerMode);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"providerMode",
|
||||
this.providerMode,
|
||||
);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
@ -379,13 +438,19 @@ export class CustomizeView extends LitElement {
|
||||
|
||||
async handleGoogleSearchChange(e) {
|
||||
this.googleSearchEnabled = e.target.checked;
|
||||
await cheatingDaddy.storage.updatePreference('googleSearchEnabled', this.googleSearchEnabled);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"googleSearchEnabled",
|
||||
this.googleSearchEnabled,
|
||||
);
|
||||
if (window.require) {
|
||||
try {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('update-google-search-setting', this.googleSearchEnabled);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke(
|
||||
"update-google-search-setting",
|
||||
this.googleSearchEnabled,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to notify main process:', error);
|
||||
console.error("Failed to notify main process:", error);
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
@ -393,25 +458,34 @@ export class CustomizeView extends LitElement {
|
||||
|
||||
async handleBackgroundTransparencyChange(e) {
|
||||
this.backgroundTransparency = parseFloat(e.target.value);
|
||||
await cheatingDaddy.storage.updatePreference('backgroundTransparency', this.backgroundTransparency);
|
||||
await cheatingDaddy.storage.updatePreference(
|
||||
"backgroundTransparency",
|
||||
this.backgroundTransparency,
|
||||
);
|
||||
this.updateBackgroundAppearance();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
updateBackgroundAppearance() {
|
||||
const colors = cheatingDaddy.theme.get(this.theme);
|
||||
cheatingDaddy.theme.applyBackgrounds(colors.background, this.backgroundTransparency);
|
||||
cheatingDaddy.theme.applyBackgrounds(
|
||||
colors.background,
|
||||
this.backgroundTransparency,
|
||||
);
|
||||
}
|
||||
|
||||
async handleFontSizeChange(e) {
|
||||
this.fontSize = parseInt(e.target.value, 10);
|
||||
await cheatingDaddy.storage.updatePreference('fontSize', this.fontSize);
|
||||
await cheatingDaddy.storage.updatePreference("fontSize", this.fontSize);
|
||||
this.updateFontSize();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
updateFontSize() {
|
||||
document.documentElement.style.setProperty('--response-font-size', `${this.fontSize}px`);
|
||||
document.documentElement.style.setProperty(
|
||||
"--response-font-size",
|
||||
`${this.fontSize}px`,
|
||||
);
|
||||
}
|
||||
|
||||
handleKeybindChange(action, value) {
|
||||
@ -421,50 +495,50 @@ export class CustomizeView extends LitElement {
|
||||
}
|
||||
|
||||
handleKeybindFocus(e) {
|
||||
e.target.placeholder = 'Press key combination...';
|
||||
e.target.placeholder = "Press key combination...";
|
||||
e.target.select();
|
||||
}
|
||||
|
||||
handleKeybindInput(e) {
|
||||
e.preventDefault();
|
||||
const modifiers = [];
|
||||
if (e.ctrlKey) modifiers.push('Ctrl');
|
||||
if (e.metaKey) modifiers.push('Cmd');
|
||||
if (e.altKey) modifiers.push('Alt');
|
||||
if (e.shiftKey) modifiers.push('Shift');
|
||||
if (e.ctrlKey) modifiers.push("Ctrl");
|
||||
if (e.metaKey) modifiers.push("Cmd");
|
||||
if (e.altKey) modifiers.push("Alt");
|
||||
if (e.shiftKey) modifiers.push("Shift");
|
||||
let mainKey = e.key;
|
||||
|
||||
switch (e.code) {
|
||||
case 'ArrowUp':
|
||||
mainKey = 'Up';
|
||||
case "ArrowUp":
|
||||
mainKey = "Up";
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
mainKey = 'Down';
|
||||
case "ArrowDown":
|
||||
mainKey = "Down";
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
mainKey = 'Left';
|
||||
case "ArrowLeft":
|
||||
mainKey = "Left";
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
mainKey = 'Right';
|
||||
case "ArrowRight":
|
||||
mainKey = "Right";
|
||||
break;
|
||||
case 'Enter':
|
||||
mainKey = 'Enter';
|
||||
case "Enter":
|
||||
mainKey = "Enter";
|
||||
break;
|
||||
case 'Space':
|
||||
mainKey = 'Space';
|
||||
case "Space":
|
||||
mainKey = "Space";
|
||||
break;
|
||||
case 'Backslash':
|
||||
mainKey = '\\';
|
||||
case "Backslash":
|
||||
mainKey = "\\";
|
||||
break;
|
||||
default:
|
||||
if (e.key.length === 1) mainKey = e.key.toUpperCase();
|
||||
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 keybind = [...modifiers, mainKey].join('+');
|
||||
const keybind = [...modifiers, mainKey].join("+");
|
||||
this.handleKeybindChange(action, keybind);
|
||||
e.target.value = keybind;
|
||||
e.target.blur();
|
||||
@ -474,8 +548,8 @@ export class CustomizeView extends LitElement {
|
||||
this.keybinds = this.getDefaultKeybinds();
|
||||
await cheatingDaddy.storage.setKeybinds(null);
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('update-keybinds', this.keybinds);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.send("update-keybinds", this.keybinds);
|
||||
}
|
||||
this.requestUpdate();
|
||||
}
|
||||
@ -483,22 +557,22 @@ export class CustomizeView extends LitElement {
|
||||
async restoreAllSettings() {
|
||||
if (this.isRestoring) return;
|
||||
this.isRestoring = true;
|
||||
this.clearStatusMessage = '';
|
||||
this.clearStatusType = '';
|
||||
this.clearStatusMessage = "";
|
||||
this.clearStatusType = "";
|
||||
this.requestUpdate();
|
||||
try {
|
||||
// Restore all preferences to defaults
|
||||
const defaults = {
|
||||
customPrompt: '',
|
||||
selectedProfile: 'interview',
|
||||
selectedLanguage: 'en-US',
|
||||
selectedScreenshotInterval: '5',
|
||||
selectedImageQuality: 'medium',
|
||||
audioMode: 'speaker_only',
|
||||
customPrompt: "",
|
||||
selectedProfile: "interview",
|
||||
selectedLanguage: "en-US",
|
||||
selectedScreenshotInterval: "5",
|
||||
selectedImageQuality: "medium",
|
||||
audioMode: "speaker_only",
|
||||
fontSize: 20,
|
||||
backgroundTransparency: 0.8,
|
||||
googleSearchEnabled: false,
|
||||
theme: 'dark',
|
||||
theme: "dark",
|
||||
};
|
||||
for (const [key, value] of Object.entries(defaults)) {
|
||||
await cheatingDaddy.storage.updatePreference(key, value);
|
||||
@ -508,8 +582,8 @@ export class CustomizeView extends LitElement {
|
||||
this.keybinds = this.getDefaultKeybinds();
|
||||
await cheatingDaddy.storage.setKeybinds(null);
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('update-keybinds', this.keybinds);
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
ipcRenderer.send("update-keybinds", this.keybinds);
|
||||
}
|
||||
|
||||
// Apply to local state
|
||||
@ -533,12 +607,12 @@ export class CustomizeView extends LitElement {
|
||||
this.updateFontSize();
|
||||
await cheatingDaddy.theme.save(defaults.theme);
|
||||
|
||||
this.clearStatusMessage = 'All settings restored to defaults';
|
||||
this.clearStatusType = 'success';
|
||||
this.clearStatusMessage = "All settings restored to defaults";
|
||||
this.clearStatusType = "success";
|
||||
} catch (error) {
|
||||
console.error('Error restoring settings:', error);
|
||||
console.error("Error restoring settings:", error);
|
||||
this.clearStatusMessage = `Error restoring settings: ${error.message}`;
|
||||
this.clearStatusType = 'error';
|
||||
this.clearStatusType = "error";
|
||||
} finally {
|
||||
this.isRestoring = false;
|
||||
this.requestUpdate();
|
||||
@ -548,28 +622,28 @@ export class CustomizeView extends LitElement {
|
||||
async clearLocalData() {
|
||||
if (this.isClearing) return;
|
||||
this.isClearing = true;
|
||||
this.clearStatusMessage = '';
|
||||
this.clearStatusType = '';
|
||||
this.clearStatusMessage = "";
|
||||
this.clearStatusType = "";
|
||||
this.requestUpdate();
|
||||
try {
|
||||
await cheatingDaddy.storage.clearAll();
|
||||
this.clearStatusMessage = 'Successfully cleared all local data';
|
||||
this.clearStatusType = 'success';
|
||||
this.clearStatusMessage = "Successfully cleared all local data";
|
||||
this.clearStatusType = "success";
|
||||
this.requestUpdate();
|
||||
setTimeout(() => {
|
||||
this.clearStatusMessage = 'Closing application...';
|
||||
this.clearStatusMessage = "Closing application...";
|
||||
this.requestUpdate();
|
||||
setTimeout(async () => {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
await ipcRenderer.invoke('quit-application');
|
||||
const { ipcRenderer } = window.require("electron");
|
||||
await ipcRenderer.invoke("quit-application");
|
||||
}
|
||||
}, 1000);
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('Error clearing data:', error);
|
||||
console.error("Error clearing data:", error);
|
||||
this.clearStatusMessage = `Error clearing data: ${error.message}`;
|
||||
this.clearStatusType = 'error';
|
||||
this.clearStatusType = "error";
|
||||
} finally {
|
||||
this.isClearing = false;
|
||||
this.requestUpdate();
|
||||
@ -583,7 +657,11 @@ export class CustomizeView extends LitElement {
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<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="local">Local AI (Ollama)</option>
|
||||
</select>
|
||||
@ -600,18 +678,31 @@ export class CustomizeView extends LitElement {
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<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="mic_only">Microphone Only (Me)</option>
|
||||
<option value="both">Both Speaker and Microphone</option>
|
||||
</select>
|
||||
</div>
|
||||
${this.audioMode !== 'speaker_only' ? html`
|
||||
<div class="warning-callout">May cause unexpected behavior. Only change this if you know what you're doing.</div>
|
||||
` : ''}
|
||||
${this.audioMode !== "speaker_only"
|
||||
? html`
|
||||
<div class="warning-callout">
|
||||
May cause unexpected behavior. Only change this if you know
|
||||
what you're doing.
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="form-group">
|
||||
<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="medium">Medium Quality</option>
|
||||
<option value="low">Low Quality</option>
|
||||
@ -629,8 +720,17 @@ export class CustomizeView extends LitElement {
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Speech Language</label>
|
||||
<select class="control" .value=${this.selectedLanguage} @change=${this.handleLanguageSelect}>
|
||||
${this.getLanguages().map(language => html`<option value=${language.value}>${language.name}</option>`)}
|
||||
<select
|
||||
class="control"
|
||||
.value=${this.selectedLanguage}
|
||||
@change=${this.handleLanguageSelect}
|
||||
>
|
||||
${this.getLanguages().map(
|
||||
(language) =>
|
||||
html`<option value=${language.value}>
|
||||
${language.name}
|
||||
</option>`,
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -645,14 +745,23 @@ export class CustomizeView extends LitElement {
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Theme</label>
|
||||
<select class="control" .value=${this.theme} @change=${this.handleThemeChange}>
|
||||
${this.getThemes().map(theme => html`<option value=${theme.value}>${theme.name}</option>`)}
|
||||
<select
|
||||
class="control"
|
||||
.value=${this.theme}
|
||||
@change=${this.handleThemeChange}
|
||||
>
|
||||
${this.getThemes().map(
|
||||
(theme) =>
|
||||
html`<option value=${theme.value}>${theme.name}</option>`,
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group slider-wrap">
|
||||
<div class="slider-header">
|
||||
<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>
|
||||
<input
|
||||
class="slider-input"
|
||||
@ -688,7 +797,8 @@ export class CustomizeView extends LitElement {
|
||||
return html`
|
||||
<section class="surface">
|
||||
<div class="surface-title">Keyboard Shortcuts</div>
|
||||
${this.getKeybindActions().map(action => html`
|
||||
${this.getKeybindActions().map(
|
||||
(action) => html`
|
||||
<div class="keybind-row">
|
||||
<span class="keybind-name">${action.name}</span>
|
||||
<input
|
||||
@ -701,9 +811,16 @@ export class CustomizeView extends LitElement {
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
`)}
|
||||
`,
|
||||
)}
|
||||
<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>
|
||||
</section>
|
||||
`;
|
||||
@ -714,16 +831,32 @@ export class CustomizeView extends LitElement {
|
||||
<section class="surface danger-surface">
|
||||
<div class="surface-title danger">Privacy and Data</div>
|
||||
<div style="display:flex;gap:var(--space-sm);flex-wrap:wrap;">
|
||||
<button class="danger-button" @click=${this.restoreAllSettings} ?disabled=${this.isRestoring}>
|
||||
${this.isRestoring ? 'Restoring...' : 'Restore all settings'}
|
||||
<button
|
||||
class="danger-button"
|
||||
@click=${this.restoreAllSettings}
|
||||
?disabled=${this.isRestoring}
|
||||
>
|
||||
${this.isRestoring ? "Restoring..." : "Restore all settings"}
|
||||
</button>
|
||||
<button class="danger-button" @click=${this.clearLocalData} ?disabled=${this.isClearing}>
|
||||
${this.isClearing ? 'Clearing...' : 'Delete all data'}
|
||||
<button
|
||||
class="danger-button"
|
||||
@click=${this.clearLocalData}
|
||||
?disabled=${this.isClearing}
|
||||
>
|
||||
${this.isClearing ? "Clearing..." : "Delete all data"}
|
||||
</button>
|
||||
</div>
|
||||
${this.clearStatusMessage ? html`
|
||||
<div class="status ${this.clearStatusType === 'success' ? 'success' : 'error'}">${this.clearStatusMessage}</div>
|
||||
` : ''}
|
||||
${this.clearStatusMessage
|
||||
? html`
|
||||
<div
|
||||
class="status ${this.clearStatusType === "success"
|
||||
? "success"
|
||||
: "error"}"
|
||||
>
|
||||
${this.clearStatusMessage}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@ -733,16 +866,13 @@ export class CustomizeView extends LitElement {
|
||||
<div class="unified-page">
|
||||
<div class="unified-wrap">
|
||||
<div class="page-title">Settings</div>
|
||||
${this.renderAISection()}
|
||||
${this.renderAudioSection()}
|
||||
${this.renderLanguageSection()}
|
||||
${this.renderAppearanceSection()}
|
||||
${this.renderKeyboardSection()}
|
||||
${this.renderPrivacySection()}
|
||||
${this.renderAISection()} ${this.renderAudioSection()}
|
||||
${this.renderLanguageSection()} ${this.renderAppearanceSection()}
|
||||
${this.renderKeyboardSection()} ${this.renderPrivacySection()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('customize-view', CustomizeView);
|
||||
customElements.define("customize-view", CustomizeView);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,13 +1,37 @@
|
||||
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.`,
|
||||
|
||||
formatRequirements: `**RESPONSE FORMAT REQUIREMENTS:**
|
||||
const responseModeFormats = {
|
||||
brief: `**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`,
|
||||
- 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:**
|
||||
- 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: {
|
||||
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:**
|
||||
- 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
|
||||
@ -70,13 +87,6 @@ Provide only the exact words to say in **markdown format**. Be persuasive but no
|
||||
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.`,
|
||||
|
||||
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:**
|
||||
- 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
|
||||
@ -101,13 +111,6 @@ Provide only the exact words to say in **markdown format**. Be clear, concise, a
|
||||
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.`,
|
||||
|
||||
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:**
|
||||
- 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
|
||||
@ -132,13 +135,6 @@ Provide only the exact words to say in **markdown format**. Be confident, engagi
|
||||
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.`,
|
||||
|
||||
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:**
|
||||
- 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
|
||||
@ -163,13 +159,6 @@ Provide only the exact words to say in **markdown format**. Focus on finding win
|
||||
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.`,
|
||||
|
||||
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:**
|
||||
- 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
|
||||
@ -201,25 +190,57 @@ Provide direct exam answers in **markdown format**. Include the question text, t
|
||||
},
|
||||
};
|
||||
|
||||
function buildSystemPrompt(promptParts, customPrompt = '', googleSearchEnabled = true) {
|
||||
const sections = [promptParts.intro, '\n\n', promptParts.formatRequirements];
|
||||
function buildSystemPrompt(
|
||||
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
|
||||
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;
|
||||
return buildSystemPrompt(promptParts, customPrompt, googleSearchEnabled);
|
||||
return buildSystemPrompt(
|
||||
promptParts,
|
||||
customPrompt,
|
||||
googleSearchEnabled,
|
||||
responseMode,
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
profilePrompts,
|
||||
responseModeFormats,
|
||||
codingAwareness,
|
||||
getSystemPrompt,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
const { BrowserWindow, globalShortcut, ipcMain, screen } = require('electron');
|
||||
const path = require('node:path');
|
||||
const storage = require('../storage');
|
||||
const { BrowserWindow, globalShortcut, ipcMain, screen } = require("electron");
|
||||
const path = require("node:path");
|
||||
const storage = require("../storage");
|
||||
|
||||
let mouseEventsIgnored = false;
|
||||
|
||||
@ -20,21 +20,21 @@ function createWindow(sendToRenderer, geminiSessionRef) {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false, // TODO: change to true
|
||||
backgroundThrottling: false,
|
||||
enableBlinkFeatures: 'GetDisplayMedia',
|
||||
enableBlinkFeatures: "GetDisplayMedia",
|
||||
webSecurity: true,
|
||||
allowRunningInsecureContent: false,
|
||||
},
|
||||
backgroundColor: '#00000000',
|
||||
backgroundColor: "#00000000",
|
||||
});
|
||||
|
||||
const { session, desktopCapturer } = require('electron');
|
||||
const { session, desktopCapturer } = require("electron");
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then(sources => {
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
desktopCapturer.getSources({ types: ["screen"] }).then((sources) => {
|
||||
callback({ video: sources[0], audio: "loopback" });
|
||||
});
|
||||
},
|
||||
{ useSystemPicker: true }
|
||||
{ useSystemPicker: true },
|
||||
);
|
||||
|
||||
mainWindow.setResizable(false);
|
||||
@ -42,20 +42,20 @@ function createWindow(sendToRenderer, geminiSessionRef) {
|
||||
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||
|
||||
// Hide from Windows taskbar
|
||||
if (process.platform === 'win32') {
|
||||
if (process.platform === "win32") {
|
||||
try {
|
||||
mainWindow.setSkipTaskbar(true);
|
||||
} 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
|
||||
if (process.platform === 'darwin') {
|
||||
if (process.platform === "darwin") {
|
||||
try {
|
||||
mainWindow.setHiddenInMissionControl(true);
|
||||
} 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;
|
||||
mainWindow.setPosition(x, y);
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
mainWindow.setAlwaysOnTop(true, 'screen-saver', 1);
|
||||
if (process.platform === "win32") {
|
||||
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
|
||||
mainWindow.webContents.once('dom-ready', () => {
|
||||
mainWindow.webContents.once("dom-ready", () => {
|
||||
setTimeout(() => {
|
||||
const defaultKeybinds = getDefaultKeybinds();
|
||||
let keybinds = defaultKeybinds;
|
||||
@ -84,7 +84,12 @@ function createWindow(sendToRenderer, geminiSessionRef) {
|
||||
keybinds = { ...defaultKeybinds, ...savedKeybinds };
|
||||
}
|
||||
|
||||
updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef);
|
||||
updateGlobalShortcuts(
|
||||
keybinds,
|
||||
mainWindow,
|
||||
sendToRenderer,
|
||||
geminiSessionRef,
|
||||
);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
@ -94,25 +99,31 @@ function createWindow(sendToRenderer, geminiSessionRef) {
|
||||
}
|
||||
|
||||
function getDefaultKeybinds() {
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isMac = process.platform === "darwin";
|
||||
return {
|
||||
moveUp: isMac ? 'Alt+Up' : 'Ctrl+Up',
|
||||
moveDown: isMac ? 'Alt+Down' : 'Ctrl+Down',
|
||||
moveLeft: isMac ? 'Alt+Left' : 'Ctrl+Left',
|
||||
moveRight: isMac ? 'Alt+Right' : 'Ctrl+Right',
|
||||
toggleVisibility: isMac ? 'Cmd+\\' : 'Ctrl+\\',
|
||||
toggleClickThrough: isMac ? 'Cmd+M' : 'Ctrl+M',
|
||||
nextStep: isMac ? 'Cmd+Enter' : 'Ctrl+Enter',
|
||||
previousResponse: isMac ? 'Cmd+[' : 'Ctrl+[',
|
||||
nextResponse: isMac ? 'Cmd+]' : 'Ctrl+]',
|
||||
scrollUp: isMac ? 'Cmd+Shift+Up' : 'Ctrl+Shift+Up',
|
||||
scrollDown: isMac ? 'Cmd+Shift+Down' : 'Ctrl+Shift+Down',
|
||||
emergencyErase: isMac ? 'Cmd+Shift+E' : 'Ctrl+Shift+E',
|
||||
moveUp: isMac ? "Alt+Up" : "Ctrl+Up",
|
||||
moveDown: isMac ? "Alt+Down" : "Ctrl+Down",
|
||||
moveLeft: isMac ? "Alt+Left" : "Ctrl+Left",
|
||||
moveRight: isMac ? "Alt+Right" : "Ctrl+Right",
|
||||
toggleVisibility: isMac ? "Cmd+\\" : "Ctrl+\\",
|
||||
toggleClickThrough: isMac ? "Cmd+M" : "Ctrl+M",
|
||||
nextStep: isMac ? "Cmd+Enter" : "Ctrl+Enter",
|
||||
previousResponse: isMac ? "Cmd+[" : "Ctrl+[",
|
||||
nextResponse: isMac ? "Cmd+]" : "Ctrl+]",
|
||||
scrollUp: isMac ? "Cmd+Shift+Up" : "Ctrl+Shift+Up",
|
||||
scrollDown: isMac ? "Cmd+Shift+Down" : "Ctrl+Shift+Down",
|
||||
expandResponse: isMac ? "Cmd+E" : "Ctrl+E",
|
||||
emergencyErase: isMac ? "Cmd+Shift+E" : "Ctrl+Shift+E",
|
||||
};
|
||||
}
|
||||
|
||||
function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessionRef) {
|
||||
console.log('Updating global shortcuts with:', keybinds);
|
||||
function updateGlobalShortcuts(
|
||||
keybinds,
|
||||
mainWindow,
|
||||
sendToRenderer,
|
||||
geminiSessionRef,
|
||||
) {
|
||||
console.log("Updating global shortcuts with:", keybinds);
|
||||
|
||||
// Unregister all existing shortcuts
|
||||
globalShortcut.unregisterAll();
|
||||
@ -146,7 +157,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
|
||||
};
|
||||
|
||||
// Register each movement shortcut
|
||||
Object.keys(movementActions).forEach(action => {
|
||||
Object.keys(movementActions).forEach((action) => {
|
||||
const keybind = keybinds[action];
|
||||
if (keybind) {
|
||||
try {
|
||||
@ -170,7 +181,10 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
|
||||
});
|
||||
console.log(`Registered toggleVisibility: ${keybinds.toggleVisibility}`);
|
||||
} 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;
|
||||
if (mouseEventsIgnored) {
|
||||
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
||||
console.log('Mouse events ignored');
|
||||
console.log("Mouse events ignored");
|
||||
} else {
|
||||
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) {
|
||||
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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.nextStep, async () => {
|
||||
console.log('Next step shortcut triggered');
|
||||
console.log("Next step shortcut triggered");
|
||||
try {
|
||||
// Determine the shortcut key format
|
||||
const isMac = process.platform === 'darwin';
|
||||
const shortcutKey = isMac ? 'cmd+enter' : 'ctrl+enter';
|
||||
const isMac = process.platform === "darwin";
|
||||
const shortcutKey = isMac ? "cmd+enter" : "ctrl+enter";
|
||||
|
||||
// Use the new handleShortcut function
|
||||
mainWindow.webContents.executeJavaScript(`
|
||||
cheatingDaddy.handleShortcut('${shortcutKey}');
|
||||
`);
|
||||
} catch (error) {
|
||||
console.error('Error handling next step shortcut:', error);
|
||||
console.error("Error handling next step shortcut:", error);
|
||||
}
|
||||
});
|
||||
console.log(`Registered nextStep: ${keybinds.nextStep}`);
|
||||
} 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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.previousResponse, () => {
|
||||
console.log('Previous response shortcut triggered');
|
||||
sendToRenderer('navigate-previous-response');
|
||||
console.log("Previous response shortcut triggered");
|
||||
sendToRenderer("navigate-previous-response");
|
||||
});
|
||||
console.log(`Registered previousResponse: ${keybinds.previousResponse}`);
|
||||
} 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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.nextResponse, () => {
|
||||
console.log('Next response shortcut triggered');
|
||||
sendToRenderer('navigate-next-response');
|
||||
console.log("Next response shortcut triggered");
|
||||
sendToRenderer("navigate-next-response");
|
||||
});
|
||||
console.log(`Registered nextResponse: ${keybinds.nextResponse}`);
|
||||
} 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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.scrollUp, () => {
|
||||
console.log('Scroll up shortcut triggered');
|
||||
sendToRenderer('scroll-response-up');
|
||||
console.log("Scroll up shortcut triggered");
|
||||
sendToRenderer("scroll-response-up");
|
||||
});
|
||||
console.log(`Registered scrollUp: ${keybinds.scrollUp}`);
|
||||
} 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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.scrollDown, () => {
|
||||
console.log('Scroll down shortcut triggered');
|
||||
sendToRenderer('scroll-response-down');
|
||||
console.log("Scroll down shortcut triggered");
|
||||
sendToRenderer("scroll-response-down");
|
||||
});
|
||||
console.log(`Registered scrollDown: ${keybinds.scrollDown}`);
|
||||
} 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) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.emergencyErase, () => {
|
||||
console.log('Emergency Erase triggered!');
|
||||
console.log("Emergency Erase triggered!");
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.hide();
|
||||
|
||||
@ -283,28 +336,31 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, geminiSessi
|
||||
geminiSessionRef.current = null;
|
||||
}
|
||||
|
||||
sendToRenderer('clear-sensitive-data');
|
||||
sendToRenderer("clear-sensitive-data");
|
||||
|
||||
setTimeout(() => {
|
||||
const { app } = require('electron');
|
||||
const { app } = require("electron");
|
||||
app.quit();
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
console.log(`Registered emergencyErase: ${keybinds.emergencyErase}`);
|
||||
} 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) {
|
||||
ipcMain.on('view-changed', (event, view) => {
|
||||
ipcMain.on("view-changed", (event, view) => {
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const { width: screenWidth } = primaryDisplay.workAreaSize;
|
||||
|
||||
if (view === 'assistant') {
|
||||
if (view === "assistant") {
|
||||
// Shrink window for live view
|
||||
const liveWidth = 850;
|
||||
const liveHeight = 400;
|
||||
@ -323,22 +379,27 @@ function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('window-minimize', () => {
|
||||
ipcMain.handle("window-minimize", () => {
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
mainWindow.minimize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('update-keybinds', (event, newKeybinds) => {
|
||||
ipcMain.on("update-keybinds", (event, newKeybinds) => {
|
||||
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 {
|
||||
if (mainWindow.isDestroyed()) {
|
||||
return { success: false, error: 'Window has been destroyed' };
|
||||
return { success: false, error: "Window has been destroyed" };
|
||||
}
|
||||
|
||||
if (mainWindow.isVisible()) {
|
||||
@ -348,12 +409,12 @@ function setupWindowIpcHandlers(mainWindow, sendToRenderer, geminiSessionRef) {
|
||||
}
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error toggling window visibility:', error);
|
||||
console.error("Error toggling window visibility:", error);
|
||||
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.
|
||||
// This handler is kept for compatibility but is a no-op now.
|
||||
return { success: true };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user