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/sohzm/cheating-daddy/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; } } 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)} .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);