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` * { box-sizing: border-box; font-family: var(--font); margin: 0; padding: 0; cursor: default; user-select: none; } :host { display: block; width: 100%; height: 100vh; background: var(--bg-app); color: var(--text-primary); } /* ── Full app shell: top bar + sidebar/content ── */ .app-shell { display: flex; height: 100vh; overflow: hidden; } .top-drag-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 9999; display: flex; align-items: center; height: 38px; background: transparent; } .drag-region { flex: 1; height: 100%; -webkit-app-region: drag; } .top-drag-bar.hidden { display: none; } .traffic-lights { display: flex; align-items: center; gap: 8px; padding: 0 var(--space-md); height: 100%; -webkit-app-region: no-drag; } .traffic-light { width: 12px; height: 12px; border-radius: 50%; border: none; cursor: pointer; padding: 0; transition: opacity 0.15s ease; } .traffic-light:hover { opacity: 0.8; } .traffic-light.close { background: #ff5f57; } .traffic-light.minimize { background: #febc2e; } .traffic-light.maximize { background: #28c840; } .sidebar { width: var(--sidebar-width); min-width: var(--sidebar-width); background: var(--bg-surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 42px 0 var(--space-md) 0; transition: width var(--transition), min-width var(--transition), opacity var(--transition); } .sidebar.hidden { width: 0; min-width: 0; padding: 0; overflow: hidden; border-right: none; opacity: 0; } .sidebar-brand { padding: var(--space-sm) var(--space-lg); padding-top: var(--space-md); margin-bottom: var(--space-lg); } .sidebar-brand h1 { font-size: var(--font-size-sm); font-weight: var(--font-weight-semibold); color: var(--text-primary); letter-spacing: -0.01em; } .sidebar-nav { flex: 1; display: flex; flex-direction: column; gap: var(--space-xs); padding: 0 var(--space-sm); -webkit-app-region: no-drag; } .nav-item { display: flex; align-items: center; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md); color: var(--text-secondary); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); cursor: pointer; transition: color var(--transition), background var(--transition); border: none; background: none; width: 100%; text-align: left; } .nav-item:hover { color: var(--text-primary); background: var(--bg-hover); } .nav-item.active { color: var(--text-primary); background: var(--bg-elevated); } .nav-item svg { width: 20px; height: 20px; flex-shrink: 0; } .sidebar-footer { padding: var(--space-sm); margin-top: var(--space-sm); -webkit-app-region: no-drag; } .update-btn { display: flex; align-items: center; gap: var(--space-sm); width: 100%; padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md); border: 1px solid rgba(239, 68, 68, 0.2); background: rgba(239, 68, 68, 0.08); color: var(--danger); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); cursor: pointer; text-align: left; transition: background var(--transition), border-color var(--transition); animation: update-wobble 5s ease-in-out infinite; } .update-btn:hover { background: rgba(239, 68, 68, 0.14); border-color: rgba(239, 68, 68, 0.35); } @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); } } .update-btn svg { width: 20px; height: 20px; flex-shrink: 0; } .version-text { font-size: var(--font-size-xs); color: var(--text-muted); padding: var(--space-xs) var(--space-md); } /* ── Main content area ── */ .content { flex: 1; overflow: hidden; display: flex; flex-direction: column; background: var(--bg-app); } /* Live mode top bar */ .live-bar { position: relative; display: flex; align-items: center; justify-content: space-between; padding: 0 var(--space-md); background: var(--bg-surface); border-bottom: 1px solid var(--border); height: 36px; -webkit-app-region: drag; } .live-bar-left { display: flex; align-items: center; -webkit-app-region: no-drag; z-index: 1; } .live-bar-back { display: flex; align-items: center; justify-content: center; color: var(--text-muted); cursor: pointer; background: none; border: none; padding: var(--space-xs); border-radius: var(--radius-sm); transition: color var(--transition); } .live-bar-back:hover { color: var(--text-primary); } .live-bar-back svg { width: 14px; height: 14px; } .live-bar-center { position: absolute; left: 50%; transform: translateX(-50%); font-size: var(--font-size-xs); color: var(--text-muted); font-weight: var(--font-weight-medium); white-space: nowrap; pointer-events: none; } .live-bar-right { display: flex; align-items: center; gap: var(--space-md); -webkit-app-region: no-drag; z-index: 1; } .live-bar-text { font-size: var(--font-size-xs); color: var(--text-muted); font-family: var(--font-mono); white-space: nowrap; } .live-bar-text.clickable { cursor: pointer; transition: color var(--transition); } .live-bar-text.clickable:hover { color: var(--text-primary); } /* Content inner */ .content-inner { flex: 1; overflow-y: auto; overflow-x: hidden; } .content-inner.live { overflow: hidden; display: flex; flex-direction: column; } /* Onboarding fills everything */ .fullscreen { position: fixed; inset: 0; z-index: 100; background: var(--bg-app); } ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #444444; } `; static properties = { currentView: { type: String }, statusText: { type: String }, startTime: { type: Number }, isRecording: { type: Boolean }, sessionActive: { type: Boolean }, selectedProfile: { type: String }, selectedLanguage: { type: String }, responses: { type: Array }, currentResponseIndex: { type: Number }, selectedScreenshotInterval: { type: String }, selectedImageQuality: { type: String }, layoutMode: { type: String }, _viewInstances: { type: Object, state: true }, _isClickThrough: { state: true }, _awaitingNewResponse: { state: true }, shouldAnimateResponse: { type: Boolean }, _storageLoaded: { state: true }, _updateAvailable: { state: true }, _whisperDownloading: { state: true }, }; constructor() { super(); 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.responses = []; this.currentResponseIndex = -1; this._viewInstances = new Map(); this._isClickThrough = false; this._awaitingNewResponse = false; this._currentResponseIsComplete = true; this.shouldAnimateResponse = false; this._storageLoaded = false; this._timerInterval = null; this._updateAvailable = false; this._whisperDownloading = false; this._localVersion = ""; this._loadFromStorage(); this._checkForUpdates(); } async _checkForUpdates() { try { this._localVersion = await cheatingDaddy.getVersion(); this.requestUpdate(); 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 [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) ) { this._updateAvailable = true; this.requestUpdate(); } } catch (e) { // silently ignore } } async _loadFromStorage() { try { const [config, prefs] = await Promise.all([ cheatingDaddy.storage.getConfig(), 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._storageLoaded = true; this.requestUpdate(); } catch (error) { console.error("Error loading from storage:", error); this._storageLoaded = true; this.requestUpdate(); } } connectedCallback() { 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; }); } } disconnectedCallback() { 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"); } } // ── Timer ── _startTimer() { this._stopTimer(); if (this.startTime) { this._timerInterval = setInterval(() => this.requestUpdate(), 1000); } } _stopTimer() { if (this._timerInterval) { clearInterval(this._timerInterval); this._timerInterval = null; } } getElapsedTime() { 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"); if (h > 0) return `${h}:${pad(m)}:${pad(s)}`; return `${m}:${pad(s)}`; } // ── Status & Responses ── setStatus(text) { this.statusText = text; if ( text.includes("Ready") || text.includes("Listening") || text.includes("Error") ) { this._currentResponseIsComplete = true; } } addNewResponse(response) { const wasOnLatest = this.currentResponseIndex === this.responses.length - 1; this.responses = [...this.responses, response]; if (wasOnLatest || this.currentResponseIndex === -1) { this.currentResponseIndex = this.responses.length - 1; } this._awaitingNewResponse = false; this.requestUpdate(); } updateCurrentResponse(response) { if (this.responses.length > 0) { this.responses = [...this.responses.slice(0, -1), response]; } else { this.addNewResponse(response); } this.requestUpdate(); } // ── Navigation ── navigate(view) { this.currentView = view; this.requestUpdate(); } async handleClose() { if (this.currentView === "assistant") { cheatingDaddy.stopCapture(); if (window.require) { const { ipcRenderer } = window.require("electron"); await ipcRenderer.invoke("close-session"); } this.sessionActive = false; this._stopTimer(); this.currentView = "main"; } else { if (window.require) { 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"); } } async handleHideToggle() { if (window.require) { const { ipcRenderer } = window.require("electron"); await ipcRenderer.invoke("toggle-window-visibility"); } } // ── Session start ── async handleStart() { const prefs = await cheatingDaddy.storage.getPreferences(); const providerMode = prefs.providerMode || "byok"; if (providerMode === "local") { const success = await cheatingDaddy.initializeLocal(this.selectedProfile); if (!success) { const mainView = this.shadowRoot.querySelector("main-view"); if (mainView && mainView.triggerApiKeyError) { mainView.triggerApiKeyError(); } return; } } else { const apiKey = await cheatingDaddy.storage.getApiKey(); if (!apiKey || apiKey === "") { const mainView = this.shadowRoot.querySelector("main-view"); if (mainView && mainView.triggerApiKeyError) { mainView.triggerApiKeyError(); } return; } await cheatingDaddy.initializeGemini( this.selectedProfile, this.selectedLanguage, ); } cheatingDaddy.startCapture( this.selectedScreenshotInterval, this.selectedImageQuality, ); this.responses = []; this.currentResponseIndex = -1; this.startTime = Date.now(); this.sessionActive = true; 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", ); } } async handleGroqAPIKeyHelp() { if (window.require) { const { ipcRenderer } = window.require("electron"); await ipcRenderer.invoke( "open-external", "https://console.groq.com/keys", ); } } // ── Settings handlers ── async handleProfileChange(profile) { this.selectedProfile = profile; await cheatingDaddy.storage.updatePreference("selectedProfile", profile); } async handleLanguageChange(language) { this.selectedLanguage = language; await cheatingDaddy.storage.updatePreference("selectedLanguage", language); } async handleScreenshotIntervalChange(interval) { this.selectedScreenshotInterval = interval; await cheatingDaddy.storage.updatePreference( "selectedScreenshotInterval", interval, ); } async handleImageQualityChange(quality) { this.selectedImageQuality = quality; await cheatingDaddy.storage.updatePreference( "selectedImageQuality", quality, ); } async handleLayoutModeChange(layoutMode) { this.layoutMode = layoutMode; await cheatingDaddy.storage.updateConfig("layout", layoutMode); if (window.require) { try { const { ipcRenderer } = window.require("electron"); await ipcRenderer.invoke("update-sizes"); } catch (error) { console.error("Failed to update sizes:", error); } } this.requestUpdate(); } async handleExternalLinkClick(url) { if (window.require) { 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); } else { 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; } } handleResponseIndexChanged(e) { this.currentResponseIndex = e.detail.index; this.shouldAnimateResponse = false; this.requestUpdate(); } handleOnboardingComplete() { 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); } } // ── Helpers ── _isLiveMode() { return this.currentView === "assistant"; } // ── Render ── renderCurrentView() { switch (this.currentView) { case "onboarding": return html` this.handleOnboardingComplete()} .onClose=${() => this.handleClose()} > `; case "main": return html` this.handleProfileChange(p)} .onStart=${() => this.handleStart()} .onExternalLink=${(url) => this.handleExternalLinkClick(url)} .whisperDownloading=${this._whisperDownloading} > `; case "ai-customize": return html` this.handleProfileChange(p)} > `; case "customize": return html` this.handleProfileChange(p)} .onLanguageChange=${(l) => this.handleLanguageChange(l)} .onScreenshotIntervalChange=${(i) => this.handleScreenshotIntervalChange(i)} .onImageQualityChange=${(q) => this.handleImageQualityChange(q)} .onLayoutModeChange=${(lm) => this.handleLayoutModeChange(lm)} > `; case "feedback": return html``; case "help": return html` this.handleExternalLinkClick(url)} >`; case "history": return html``; case "assistant": return html` this.handleSendText(msg)} .onExpandResponse=${() => this.handleExpandResponse()} .shouldAnimateResponse=${this.shouldAnimateResponse} @response-index-changed=${this.handleResponseIndexChanged} @response-animation-complete=${() => { this.shouldAnimateResponse = false; this._currentResponseIsComplete = true; this.requestUpdate(); }} > `; default: return html`
Unknown view: ${this.currentView}
`; } } renderSidebar() { const items = [ { id: "main", label: "Home", icon: html` `, }, { id: "ai-customize", label: "AI Customization", icon: html` `, }, { id: "history", label: "History", icon: html` `, }, { id: "customize", label: "Settings", icon: html` `, }, { id: "feedback", label: "Feedback", icon: html` `, }, { id: "help", label: "Help", icon: html` `, }, ]; return html` `; } renderLiveBar() { if (!this._isLiveMode()) return ""; const profileLabels = { interview: "Interview", sales: "Sales Call", meeting: "Meeting", presentation: "Presentation", negotiation: "Negotiation", exam: "Exam", }; return html`
${profileLabels[this.selectedProfile] || "Session"}
${this.statusText ? html`${this.statusText}` : ""} ${this.getElapsedTime()} ${this._isClickThrough ? html`[click through]` : ""} this.handleHideToggle()} >[hide]
`; } render() { // Onboarding is fullscreen, no sidebar if (this.currentView === "onboarding") { return html`
${this.renderCurrentView()}
`; } const isLive = this._isLiveMode(); return html`
${this.renderSidebar()}
${isLive ? this.renderLiveBar() : ""}
${this.renderCurrentView()}
`; } } customElements.define("cheating-daddy-app", CheatingDaddyApp);