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 = [
unifiedPageStyles,
css`
.danger-surface {
border-color: var(--danger);
}
.warning-callout {
position: relative;
margin-top: 4px;
padding: 8px 12px;
border: 1px solid var(--danger);
border-radius: var(--radius-sm);
color: var(--danger);
font-size: var(--font-size-xs);
line-height: 1.4;
background: rgba(239, 68, 68, 0.06);
}
.warning-callout::before {
content: "";
position: absolute;
top: -6px;
left: 16px;
width: 10px;
height: 10px;
background: var(--bg-surface);
border-top: 1px solid var(--danger);
border-left: 1px solid var(--danger);
transform: rotate(45deg);
}
.toggle-row {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--bg-elevated);
}
.toggle-input {
width: 14px;
height: 14px;
accent-color: var(--text-primary);
cursor: pointer;
}
.toggle-label {
color: var(--text-primary);
font-size: var(--font-size-sm);
cursor: pointer;
user-select: none;
}
.slider-wrap {
display: flex;
flex-direction: column;
align-items: stretch;
gap: var(--space-xs);
}
.slider-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
}
.slider-value {
font-family: var(--font-mono);
font-size: var(--font-size-xs);
color: var(--text-secondary);
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 2px 8px;
}
.slider-input {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 4px;
border-radius: 2px;
background: var(--border);
outline: none;
cursor: pointer;
}
.slider-input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--text-primary);
border: none;
}
.slider-input::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--text-primary);
border: none;
}
.keybind-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-sm) 0;
border-bottom: 1px solid var(--border);
}
.keybind-row:last-of-type {
border-bottom: none;
}
.keybind-name {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.keybind-input {
width: 140px;
text-align: center;
font-family: var(--font-mono);
font-size: var(--font-size-xs);
}
.danger-button {
border: 1px solid var(--danger);
color: var(--danger);
background: transparent;
border-radius: var(--radius-sm);
padding: 9px 12px;
font-size: var(--font-size-sm);
cursor: pointer;
transition: background var(--transition);
}
.danger-button:hover {
background: rgba(241, 76, 76, 0.11);
}
.danger-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status {
margin-top: var(--space-sm);
padding: var(--space-sm);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
font-size: var(--font-size-xs);
}
.status.success {
border-color: var(--success);
color: var(--success);
}
.status.error {
border-color: var(--danger);
color: var(--danger);
}
`,
];
static properties = {
selectedProfile: { type: String },
selectedLanguage: { type: String },
providerMode: { type: String },
selectedImageQuality: { type: String },
layoutMode: { type: String },
keybinds: { type: Object },
googleSearchEnabled: { type: Boolean },
backgroundTransparency: { type: Number },
fontSize: { type: Number },
theme: { type: String },
onProfileChange: { type: Function },
onLanguageChange: { type: Function },
onImageQualityChange: { type: Function },
onLayoutModeChange: { type: Function },
isClearing: { type: Boolean },
isRestoring: { type: Boolean },
clearStatusMessage: { type: String },
clearStatusType: { type: String },
};
constructor() {
super();
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.isClearing = false;
this.isRestoring = false;
this.clearStatusMessage = "";
this.clearStatusType = "";
this.backgroundTransparency = 0.8;
this.fontSize = 20;
this.audioMode = "speaker_only";
this.customPrompt = "";
this.theme = "dark";
this._loadFromStorage();
}
getThemes() {
return cheatingDaddy.theme.getAll();
}
async _loadFromStorage() {
try {
const [prefs, keybinds] = await Promise.all([
cheatingDaddy.storage.getPreferences(),
cheatingDaddy.storage.getKeybinds(),
]);
this.googleSearchEnabled = prefs.googleSearchEnabled ?? true;
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";
if (keybinds) {
this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds };
}
this.updateBackgroundAppearance();
this.updateFontSize();
this.requestUpdate();
} catch (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" },
];
}
getLanguages() {
return [
{ value: "auto", name: "Auto (Multilingual)" },
{ 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");
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",
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: "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);
}
}
handleProfileSelect(e) {
this.selectedProfile = e.target.value;
this.onProfileChange(this.selectedProfile);
}
handleLanguageSelect(e) {
this.selectedLanguage = e.target.value;
this.onLanguageChange(this.selectedLanguage);
}
handleImageQualitySelect(e) {
this.selectedImageQuality = e.target.value;
this.onImageQualityChange(this.selectedImageQuality);
}
handleLayoutModeSelect(e) {
this.layoutMode = e.target.value;
this.onLayoutModeChange(this.layoutMode);
}
async handleCustomPromptInput(e) {
this.customPrompt = e.target.value;
await cheatingDaddy.storage.updatePreference(
"customPrompt",
this.customPrompt,
);
}
async handleAudioModeSelect(e) {
this.audioMode = e.target.value;
await cheatingDaddy.storage.updatePreference("audioMode", this.audioMode);
this.requestUpdate();
}
async handleProviderModeChange(e) {
this.providerMode = e.target.value;
await cheatingDaddy.storage.updatePreference(
"providerMode",
this.providerMode,
);
this.requestUpdate();
}
async handleThemeChange(e) {
this.theme = e.target.value;
await cheatingDaddy.theme.save(this.theme);
this.updateBackgroundAppearance();
this.requestUpdate();
}
async handleGoogleSearchChange(e) {
this.googleSearchEnabled = e.target.checked;
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,
);
} catch (error) {
console.error("Failed to notify main process:", error);
}
}
this.requestUpdate();
}
async handleBackgroundTransparencyChange(e) {
this.backgroundTransparency = parseFloat(e.target.value);
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,
);
}
async handleFontSizeChange(e) {
this.fontSize = parseInt(e.target.value, 10);
await cheatingDaddy.storage.updatePreference("fontSize", this.fontSize);
this.updateFontSize();
this.requestUpdate();
}
updateFontSize() {
document.documentElement.style.setProperty(
"--response-font-size",
`${this.fontSize}px`,
);
}
handleKeybindChange(action, value) {
this.keybinds = { ...this.keybinds, [action]: value };
this.saveKeybinds();
this.requestUpdate();
}
handleKeybindFocus(e) {
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");
let mainKey = e.key;
switch (e.code) {
case "ArrowUp":
mainKey = "Up";
break;
case "ArrowDown":
mainKey = "Down";
break;
case "ArrowLeft":
mainKey = "Left";
break;
case "ArrowRight":
mainKey = "Right";
break;
case "Enter":
mainKey = "Enter";
break;
case "Space":
mainKey = "Space";
break;
case "Backslash":
mainKey = "\\";
break;
default:
if (e.key.length === 1) mainKey = e.key.toUpperCase();
break;
}
if (["Control", "Meta", "Alt", "Shift"].includes(e.key)) return;
const action = e.target.dataset.action;
const keybind = [...modifiers, mainKey].join("+");
this.handleKeybindChange(action, keybind);
e.target.value = keybind;
e.target.blur();
}
async resetKeybinds() {
this.keybinds = this.getDefaultKeybinds();
await cheatingDaddy.storage.setKeybinds(null);
if (window.require) {
const { ipcRenderer } = window.require("electron");
ipcRenderer.send("update-keybinds", this.keybinds);
}
this.requestUpdate();
}
async restoreAllSettings() {
if (this.isRestoring) return;
this.isRestoring = true;
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",
fontSize: 20,
backgroundTransparency: 0.8,
googleSearchEnabled: false,
theme: "dark",
};
for (const [key, value] of Object.entries(defaults)) {
await cheatingDaddy.storage.updatePreference(key, value);
}
// Restore keybinds
this.keybinds = this.getDefaultKeybinds();
await cheatingDaddy.storage.setKeybinds(null);
if (window.require) {
const { ipcRenderer } = window.require("electron");
ipcRenderer.send("update-keybinds", this.keybinds);
}
// Apply to local state
this.selectedProfile = defaults.selectedProfile;
this.selectedLanguage = defaults.selectedLanguage;
this.selectedImageQuality = defaults.selectedImageQuality;
this.audioMode = defaults.audioMode;
this.fontSize = defaults.fontSize;
this.backgroundTransparency = defaults.backgroundTransparency;
this.googleSearchEnabled = defaults.googleSearchEnabled;
this.customPrompt = defaults.customPrompt;
this.theme = defaults.theme;
// Notify parent callbacks
this.onProfileChange(defaults.selectedProfile);
this.onLanguageChange(defaults.selectedLanguage);
this.onImageQualityChange(defaults.selectedImageQuality);
// Apply visual changes
this.updateBackgroundAppearance();
this.updateFontSize();
await cheatingDaddy.theme.save(defaults.theme);
this.clearStatusMessage = "All settings restored to defaults";
this.clearStatusType = "success";
} catch (error) {
console.error("Error restoring settings:", error);
this.clearStatusMessage = `Error restoring settings: ${error.message}`;
this.clearStatusType = "error";
} finally {
this.isRestoring = false;
this.requestUpdate();
}
}
async clearLocalData() {
if (this.isClearing) return;
this.isClearing = true;
this.clearStatusMessage = "";
this.clearStatusType = "";
this.requestUpdate();
try {
await cheatingDaddy.storage.clearAll();
this.clearStatusMessage = "Successfully cleared all local data";
this.clearStatusType = "success";
this.requestUpdate();
setTimeout(() => {
this.clearStatusMessage = "Closing application...";
this.requestUpdate();
setTimeout(async () => {
if (window.require) {
const { ipcRenderer } = window.require("electron");
await ipcRenderer.invoke("quit-application");
}
}, 1000);
}, 2000);
} catch (error) {
console.error("Error clearing data:", error);
this.clearStatusMessage = `Error clearing data: ${error.message}`;
this.clearStatusType = "error";
} finally {
this.isClearing = false;
this.requestUpdate();
}
}
renderAISection() {
return html`