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 },
_whisperProgress: { 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._whisperProgress = null;
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;
if (!downloading) this._whisperProgress = null;
});
ipcRenderer.on("whisper-progress", (_, progress) => {
this._whisperProgress = progress;
});
}
}
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");
ipcRenderer.removeAllListeners("whisper-progress");
}
}
// ── 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`