From 310b6b3fbdc2b5b2b0c1b151e1da61485aa34201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sat, 14 Feb 2026 04:17:46 +0300 Subject: [PATCH 01/12] huge refactor --- .npmrc | 1 - .prettierignore | 2 - .prettierrc | 10 - AGENTS.md | 8 +- entitlements.plist | 2 - forge.config.js | 63 +- package.json | 58 +- pnpm-lock.yaml | 856 +++++++-- pnpm-workspace.yaml | 8 + src/audioUtils.js | 2 +- src/components/app/AppHeader.js | 288 +-- src/components/app/CheatingDaddyApp.js | 881 +++++++++ src/components/app/MastermindApp.js | 640 ------- src/components/index.js | 2 +- src/components/views/AICustomizeView.js | 143 ++ src/components/views/AssistantView.js | 806 ++++----- src/components/views/CustomizeView.js | 1897 +++++--------------- src/components/views/FeedbackView.js | 237 +++ src/components/views/HelpView.js | 569 ++---- src/components/views/HistoryView.js | 902 ++++------ src/components/views/MainView.js | 989 ++++++++-- src/components/views/OnboardingView.js | 787 +++----- src/components/views/ScreenPickerDialog.js | 175 -- src/components/views/sharedPageStyles.js | 172 ++ src/index.html | 235 ++- src/index.js | 99 +- src/storage.js | 226 +-- src/utils/ai-provider-manager.js | 464 ----- src/utils/gemini.js | 590 +++++- src/utils/localai.js | 437 +++++ src/utils/logger.js | 99 - src/utils/openai-realtime.js | 404 ----- src/utils/openai-sdk.js | 820 --------- src/utils/prompts.js | 53 - src/utils/renderer.js | 685 +++---- src/utils/window.js | 505 +----- src/utils/windowResize.js | 2 +- 37 files changed, 5899 insertions(+), 8218 deletions(-) delete mode 100644 .npmrc delete mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 pnpm-workspace.yaml create mode 100644 src/components/app/CheatingDaddyApp.js delete mode 100644 src/components/app/MastermindApp.js create mode 100644 src/components/views/AICustomizeView.js create mode 100644 src/components/views/FeedbackView.js delete mode 100644 src/components/views/ScreenPickerDialog.js create mode 100644 src/components/views/sharedPageStyles.js delete mode 100644 src/utils/ai-provider-manager.js create mode 100644 src/utils/localai.js delete mode 100644 src/utils/logger.js delete mode 100644 src/utils/openai-realtime.js delete mode 100644 src/utils/openai-sdk.js diff --git a/.npmrc b/.npmrc deleted file mode 100644 index d67f374..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -node-linker=hoisted diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 1734101..0000000 --- a/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -src/assets -node_modules diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 2ef7ebb..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "semi": true, - "tabWidth": 4, - "printWidth": 150, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "arrowParens": "avoid", - "endOfLine": "lf" -} diff --git a/AGENTS.md b/AGENTS.md index de63472..e7853fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,16 +10,16 @@ packaging. Install dependencies and run the development app: ``` -1. pnpm install -2. pnpm start +1. npm install +2. npm start ``` ## Style -Run `pnpm prettier --write .` before committing. Prettier uses the settings in +Run `npx prettier --write .` before committing. Prettier uses the settings in `.prettierrc` (four-space indentation, print width 150, semicolons and single quotes). `src/assets` and `node_modules` are ignored via `.prettierignore`. -The project does not provide linting; `pnpm run lint` simply prints +The project does not provide linting; `npm run lint` simply prints "No linting configured". ## Code standards diff --git a/entitlements.plist b/entitlements.plist index 8aad2a6..61b197a 100644 --- a/entitlements.plist +++ b/entitlements.plist @@ -10,8 +10,6 @@ com.apple.security.cs.disable-library-validation - com.apple.security.cs.allow-dyld-environment-variables - com.apple.security.device.audio-input com.apple.security.device.microphone diff --git a/forge.config.js b/forge.config.js index 8b7281b..576643f 100644 --- a/forge.config.js +++ b/forge.config.js @@ -1,45 +1,26 @@ const { FusesPlugin } = require('@electron-forge/plugin-fuses'); const { FuseV1Options, FuseVersion } = require('@electron/fuses'); -const path = require('path'); -const fs = require('fs'); module.exports = { packagerConfig: { - asar: true, + asar: { + unpack: '**/{onnxruntime-node,onnxruntime-common,@huggingface/transformers,sharp,@img}/**', + }, extraResource: ['./src/assets/SystemAudioDump'], - name: 'Mastermind', + name: 'Cheating Daddy', icon: 'src/assets/logo', - // Fix executable permissions after packaging - afterCopy: [ - (buildPath, electronVersion, platform, arch, callback) => { - if (platform === 'darwin') { - const systemAudioDump = path.join(buildPath, '..', 'Resources', 'SystemAudioDump'); - if (fs.existsSync(systemAudioDump)) { - try { - fs.chmodSync(systemAudioDump, 0o755); - console.log('✓ Set executable permissions for SystemAudioDump'); - } catch (err) { - console.error('✗ Failed to set permissions:', err.message); - } - } else { - console.warn('SystemAudioDump not found at:', systemAudioDump); - } - } - callback(); - }, - ], // use `security find-identity -v -p codesigning` to find your identity // for macos signing - // Disabled for local builds - ad-hoc signing causes issues + // also fuck apple // osxSign: { - // identity: '-', // ad-hoc signing (no Apple Developer account needed) - // optionsForFile: (filePath) => { - // return { - // entitlements: 'entitlements.plist', - // }; - // }, + // identity: '', + // optionsForFile: (filePath) => { + // return { + // entitlements: 'entitlements.plist', + // }; + // }, // }, - // notarize is off - requires Apple Developer account + // notarize if off cuz i ran this for 6 hours and it still didnt finish // osxNotarize: { // appleId: 'your apple id', // appleIdPassword: 'app specific password', @@ -51,9 +32,9 @@ module.exports = { { name: '@electron-forge/maker-squirrel', config: { - name: 'mastermind', - productName: 'Mastermind', - shortcutName: 'Mastermind', + name: 'cheating-daddy', + productName: 'Cheating Daddy', + shortcutName: 'Cheating Daddy', createDesktopShortcut: true, createStartMenuShortcut: true, }, @@ -61,23 +42,19 @@ module.exports = { { name: '@electron-forge/maker-dmg', platforms: ['darwin'], - config: { - name: 'Mastermind', - format: 'ULFO', - }, }, { name: '@reforged/maker-appimage', platforms: ['linux'], config: { options: { - name: 'Mastermind', - productName: 'Mastermind', + name: 'Cheating Daddy', + productName: 'Cheating Daddy', genericName: 'AI Assistant', - description: 'AI assistant for video calls, interviews, presentations, and meetings', + description: 'AI assistant for interviews and learning', categories: ['Development', 'Education'], - icon: 'src/assets/logo.png', - }, + icon: 'src/assets/logo.png' + } }, }, ], diff --git a/package.json b/package.json index a1738d9..b8314af 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "mastermind", - "productName": "mastermind", - "version": "0.6.0", - "description": "Mastermind", + "name": "cheating-daddy", + "productName": "cheating-daddy", + "version": "0.7.0", + "description": "cheating daddy", "main": "src/index.js", "scripts": { "start": "electron-forge start", @@ -12,35 +12,41 @@ "lint": "echo \"No linting configured\"" }, "keywords": [ - "mastermind", - "mastermind ai", - "mastermind ai assistant", - "mastermind ai assistant for interviews", - "mastermind ai assistant for interviews" + "cheating daddy", + "cheating daddy ai", + "cheating daddy ai assistant", + "cheating daddy ai assistant for interviews", + "cheating daddy ai assistant for interviews" ], "author": { - "name": "ShiftyX1", - "email": "lead@pyserve.org" + "name": "sohzm", + "email": "sohambharambe9@gmail.com" }, "license": "GPL-3.0", "dependencies": { - "@google/genai": "^1.35.0", + "@google/genai": "^1.41.0", + "@huggingface/transformers": "^3.8.1", "electron-squirrel-startup": "^1.0.1", - "openai": "^6.16.0", - "ws": "^8.18.0" + "ollama": "^0.6.3", + "p-retry": "^4.6.2", + "ws": "^8.19.0" }, "devDependencies": { - "@electron-forge/cli": "^7.11.1", - "@electron-forge/maker-deb": "^7.11.1", - "@electron-forge/maker-dmg": "^7.11.1", - "@electron-forge/maker-rpm": "^7.11.1", - "@electron-forge/maker-squirrel": "^7.11.1", - "@electron-forge/maker-zip": "^7.11.1", - "@electron-forge/plugin-auto-unpack-natives": "^7.11.1", - "@electron-forge/plugin-fuses": "^7.11.1", - "@electron/fuses": "^2.0.0", - "@electron/osx-sign": "^2.3.0", - "@reforged/maker-appimage": "^5.1.1", - "electron": "^39.2.7" + "@electron-forge/cli": "^7.8.1", + "@electron-forge/maker-deb": "^7.8.1", + "@electron-forge/maker-dmg": "^7.8.1", + "@electron-forge/maker-rpm": "^7.8.1", + "@electron-forge/maker-squirrel": "^7.8.1", + "@electron-forge/maker-zip": "^7.8.1", + "@electron-forge/plugin-auto-unpack-natives": "^7.8.1", + "@electron-forge/plugin-fuses": "^7.8.1", + "@electron/fuses": "^1.8.0", + "@reforged/maker-appimage": "^5.0.0", + "electron": "^30.0.5" + }, + "pnpm": { + "overrides": { + "p-retry": "4.6.2" + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba55f6a..64d14f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,59 +4,65 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + p-retry: 4.6.2 + importers: .: dependencies: '@google/genai': - specifier: ^1.35.0 - version: 1.35.0 + specifier: ^1.41.0 + version: 1.41.0 + '@huggingface/transformers': + specifier: ^3.8.1 + version: 3.8.1 electron-squirrel-startup: specifier: ^1.0.1 version: 1.0.1 - openai: - specifier: ^6.16.0 - version: 6.16.0(ws@8.19.0) + ollama: + specifier: ^0.6.3 + version: 0.6.3 + p-retry: + specifier: 4.6.2 + version: 4.6.2 ws: - specifier: ^8.18.0 + specifier: ^8.19.0 version: 8.19.0 devDependencies: '@electron-forge/cli': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1(encoding@0.1.13) '@electron-forge/maker-deb': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/maker-dmg': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/maker-rpm': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/maker-squirrel': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/maker-zip': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/plugin-auto-unpack-natives': - specifier: ^7.11.1 + specifier: ^7.8.1 version: 7.11.1 '@electron-forge/plugin-fuses': - specifier: ^7.11.1 - version: 7.11.1(@electron/fuses@2.0.0) + specifier: ^7.8.1 + version: 7.11.1(@electron/fuses@1.8.0) '@electron/fuses': - specifier: ^2.0.0 - version: 2.0.0 - '@electron/osx-sign': - specifier: ^2.3.0 - version: 2.3.0 + specifier: ^1.8.0 + version: 1.8.0 '@reforged/maker-appimage': - specifier: ^5.1.1 - version: 5.1.1 + specifier: ^5.0.0 + version: 5.2.0 electron: - specifier: ^39.2.7 - version: 39.2.7 + specifier: ^30.0.5 + version: 30.5.1 packages: @@ -148,9 +154,8 @@ packages: engines: {node: '>=10.12.0'} hasBin: true - '@electron/fuses@2.0.0': - resolution: {integrity: sha512-lyb1zK3YHeWUjaz7yiK0GnxSPduwASKMyiDbCtbn3spP6EEt+UWtktggWehG0icFrXAk3GwvcJ4nCrJO0N9IhQ==} - engines: {node: '>=22.12.0'} + '@electron/fuses@1.8.0': + resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==} hasBin: true '@electron/get@2.0.3': @@ -176,11 +181,6 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - '@electron/osx-sign@2.3.0': - resolution: {integrity: sha512-qh/BkWUHITP2W1grtCWn8Pm4EML3q1Rfo2tnd/KHo+f1le5gAYotBc6yEfK6X2VHyN21mPZ3CjvOiYOwjimBlA==} - engines: {node: '>=22.12.0'} - hasBin: true - '@electron/packager@18.4.4': resolution: {integrity: sha512-fTUCmgL25WXTcFpM1M72VmFP8w3E4d+KNzWxmTDRpvwkfn/S206MAtM2cy0GF78KS9AwASMOUmlOIzCHeNxcGQ==} engines: {node: '>= 16.13.0'} @@ -200,18 +200,165 @@ packages: engines: {node: '>=14.14'} hasBin: true + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@google/genai@1.35.0': - resolution: {integrity: sha512-ZC1d0PSM5eS73BpbVIgL3ZsmXeMKLVJurxzww1Z9axy3B2eUB3ioEytbQt4Qu0Od6qPluKrTDew9pSi9kEuPaw==} + '@google/genai@1.41.0': + resolution: {integrity: sha512-S4WGil+PG0NBQRAx+0yrQuM/TWOLn2gGEy5wn4IsoOI6ouHad0P61p3OWdhJ3aqr9kfj8o904i/jevfaGoGuIQ==} engines: {node: '>=20.0.0'} peerDependencies: - '@modelcontextprotocol/sdk': ^1.24.0 + '@modelcontextprotocol/sdk': ^1.25.2 peerDependenciesMeta: '@modelcontextprotocol/sdk': optional: true + '@huggingface/jinja@0.5.5': + resolution: {integrity: sha512-xRlzazC+QZwr6z4ixEqYHo9fgwhTZ3xNSdljlKfUFGZSdlvt166DljRELFUfFytlYOYvo3vTisA/AFOuOAzFQQ==} + engines: {node: '>=18'} + + '@huggingface/transformers@3.8.1': + resolution: {integrity: sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/checkbox@3.0.1': resolution: {integrity: sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==} engines: {node: '>=18'} @@ -276,6 +423,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -331,12 +482,42 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@reforged/maker-appimage@5.1.1': - resolution: {integrity: sha512-KjuMp2UXY2Tca/82J+ocWfNFCULBgBPJugDFn/qOgMfT9rPwmvTMjjJpc4AgtTWIpYJ2eytYbGdi1HLx/pvGQg==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@reforged/maker-appimage@5.2.0': + resolution: {integrity: sha512-5u7spsDMyMfwqAnTRsSipVgTIy+DW+wlfhceaRghCuTvyY8Sti8/tFhVKj4vb+dYTqPvS7m30Gl0InGv22J8RQ==} engines: {node: '>=19.0.0 || ^18.11.0'} - '@reforged/maker-types@2.0.0': - resolution: {integrity: sha512-Vc8xblKLfo+CP7CE/5Yshtyo6NwBkE4ZW00boCI50yePHG2wN04w1qrFlSxAmuau70J3alMhUrByeMrddlxAyw==} + '@reforged/maker-types@2.1.0': + resolution: {integrity: sha512-gNMAFO6mxqGwuUov0CzXGTHUMfAawlM6v/uYrqVnKeMwmceaLBt3HtfPcuNapDSH4br6D4EZ14WuWbgefmDfOQ==} '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} @@ -371,8 +552,8 @@ packages: '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -383,15 +564,21 @@ packages: '@types/mute-stream@0.0.4': resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} - '@types/node@22.19.6': - resolution: {integrity: sha512-qm+G8HuG6hOHQigsi7VGuLjUVu6TtBo/F05zvX04Mw2uCg9Dv0Qxy3Qw7j41SidlTcl5D/5yg0SEZqOB+EqZnQ==} + '@types/node@20.19.33': + resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==} - '@types/node@25.0.8': - resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==} + '@types/node@22.19.11': + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} + + '@types/node@25.2.3': + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/wrap-ansi@3.0.0': resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} @@ -552,8 +739,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.14: - resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true bignumber.js@9.3.1: @@ -611,8 +798,8 @@ packages: resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} engines: {node: '>=8'} - caniuse-lite@1.0.30001764: - resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} + caniuse-lite@1.0.30001769: + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -625,6 +812,10 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -800,15 +991,15 @@ packages: electron-squirrel-startup@1.0.1: resolution: {integrity: sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA==} - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} electron-winstaller@5.4.0: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@39.2.7: - resolution: {integrity: sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==} + electron@30.5.1: + resolution: {integrity: sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==} engines: {node: '>= 12.20.55'} hasBin: true @@ -827,8 +1018,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} env-paths@2.2.1: @@ -883,8 +1074,8 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} @@ -949,6 +1140,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + flora-colossus@2.0.0: resolution: {integrity: sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==} engines: {node: '>= 12'} @@ -1053,16 +1247,17 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -1099,6 +1294,9 @@ packages: resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} engines: {node: '>=18'} + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1316,8 +1514,8 @@ packages: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -1327,6 +1525,9 @@ packages: resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -1439,6 +1640,10 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -1461,8 +1666,8 @@ packages: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - nan@2.24.0: - resolution: {integrity: sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==} + nan@2.25.0: + resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} negotiator@0.6.4: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} @@ -1474,8 +1679,8 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - node-abi@3.85.0: - resolution: {integrity: sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==} + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} engines: {node: '>=10'} node-api-version@0.2.1: @@ -1522,6 +1727,9 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + ollama@0.6.3: + resolution: {integrity: sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1529,17 +1737,18 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - openai@6.16.0: - resolution: {integrity: sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true + onnxruntime-common@1.21.0: + resolution: {integrity: sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==} + + onnxruntime-common@1.22.0-dev.20250409-89f8206ba4: + resolution: {integrity: sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==} + + onnxruntime-node@1.21.0: + resolution: {integrity: sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==} + os: [win32, darwin, linux] + + onnxruntime-web@1.22.0-dev.20250409-89f8206ba4: + resolution: {integrity: sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==} ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} @@ -1585,6 +1794,10 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} @@ -1652,6 +1865,9 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + plist@3.1.0: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} @@ -1661,8 +1877,8 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - prettier@3.7.4: - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true @@ -1686,6 +1902,10 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -1761,6 +1981,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1810,8 +2034,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -1822,6 +2046,10 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -1945,6 +2173,11 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + tar@7.5.7: + resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} + engines: {node: '>=18'} temp@0.9.4: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} @@ -1966,8 +2199,8 @@ packages: uglify-js: optional: true - terser@5.44.1: - resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} engines: {node: '>=10'} hasBin: true @@ -2003,6 +2236,9 @@ packages: resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} engines: {node: '>=0.10.0'} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -2062,8 +2298,8 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - watchpack@2.5.0: - resolution: {integrity: sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==} + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} wcwidth@1.0.1: @@ -2080,8 +2316,8 @@ packages: resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} engines: {node: '>=10.13.0'} - webpack@5.104.1: - resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} + webpack@5.105.2: + resolution: {integrity: sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -2090,6 +2326,9 @@ packages: webpack-cli: optional: true + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -2148,6 +2387,10 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -2191,7 +2434,7 @@ snapshots: fs-extra: 10.1.0 listr2: 7.0.2 log-symbols: 4.1.0 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - '@swc/core' - bluebird @@ -2212,7 +2455,7 @@ snapshots: fs-extra: 10.1.0 log-symbols: 4.1.0 parse-author: 2.0.0 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - bluebird - supports-color @@ -2246,11 +2489,11 @@ snapshots: interpret: 3.1.1 jiti: 2.6.1 listr2: 7.0.2 - lodash: 4.17.21 + lodash: 4.17.23 log-symbols: 4.1.0 node-fetch: 2.7.0(encoding@0.1.13) rechoir: 0.8.0 - semver: 7.7.3 + semver: 7.7.4 source-map-support: 0.5.21 username: 5.1.0 transitivePeerDependencies: @@ -2339,11 +2582,11 @@ snapshots: - bluebird - supports-color - '@electron-forge/plugin-fuses@7.11.1(@electron/fuses@2.0.0)': + '@electron-forge/plugin-fuses@7.11.1(@electron/fuses@1.8.0)': dependencies: '@electron-forge/plugin-base': 7.11.1 '@electron-forge/shared-types': 7.11.1 - '@electron/fuses': 2.0.0 + '@electron/fuses': 1.8.0 transitivePeerDependencies: - bluebird - supports-color @@ -2372,7 +2615,7 @@ snapshots: '@malept/cross-spawn-promise': 2.0.0 debug: 4.4.3 fs-extra: 10.1.0 - semver: 7.7.3 + semver: 7.7.4 username: 5.1.0 transitivePeerDependencies: - bluebird @@ -2402,7 +2645,7 @@ snapshots: '@electron-forge/template-base': 7.11.1 fs-extra: 10.1.0 typescript: 5.4.5 - webpack: 5.104.1 + webpack: 5.105.2 transitivePeerDependencies: - '@swc/core' - bluebird @@ -2430,7 +2673,11 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - '@electron/fuses@2.0.0': {} + '@electron/fuses@1.8.0': + dependencies: + chalk: 4.1.2 + fs-extra: 9.1.0 + minimist: 1.2.8 '@electron/get@2.0.3': dependencies: @@ -2469,7 +2716,7 @@ snapshots: make-fetch-happen: 10.2.1 nopt: 6.0.0 proc-log: 2.0.1 - semver: 7.7.3 + semver: 7.7.4 tar: 6.2.1 which: 2.0.2 transitivePeerDependencies: @@ -2495,15 +2742,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/osx-sign@2.3.0': - dependencies: - debug: 4.4.3 - isbinaryfile: 4.0.10 - plist: 3.1.0 - semver: 7.7.3 - transitivePeerDependencies: - - supports-color - '@electron/packager@18.4.4': dependencies: '@electron/asar': 3.4.1 @@ -2522,10 +2760,10 @@ snapshots: junk: 3.1.0 parse-author: 2.0.0 plist: 3.1.0 - prettier: 3.7.4 + prettier: 3.8.1 resedit: 2.0.3 resolve: 1.22.11 - semver: 7.7.3 + semver: 7.7.4 yargs-parser: 21.1.1 transitivePeerDependencies: - supports-color @@ -2539,11 +2777,11 @@ snapshots: detect-libc: 2.1.2 fs-extra: 10.1.0 got: 11.8.6 - node-abi: 3.85.0 + node-abi: 3.87.0 node-api-version: 0.2.1 ora: 5.4.1 read-binary-file-arch: 1.0.6 - semver: 7.7.3 + semver: 7.7.4 tar: 6.2.1 yargs: 17.7.2 transitivePeerDependencies: @@ -2572,17 +2810,129 @@ snapshots: transitivePeerDependencies: - supports-color + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + '@gar/promisify@1.1.3': {} - '@google/genai@1.35.0': + '@google/genai@1.41.0': dependencies: google-auth-library: 10.5.0 + p-retry: 4.6.2 + protobufjs: 7.5.4 ws: 8.19.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + '@huggingface/jinja@0.5.5': {} + + '@huggingface/transformers@3.8.1': + dependencies: + '@huggingface/jinja': 0.5.5 + onnxruntime-node: 1.21.0 + onnxruntime-web: 1.22.0-dev.20250409-89f8206ba4 + sharp: 0.34.5 + + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@inquirer/checkbox@3.0.1': dependencies: '@inquirer/core': 9.2.1 @@ -2601,7 +2951,7 @@ snapshots: '@inquirer/figures': 1.0.15 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.19.6 + '@types/node': 22.19.11 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -2692,6 +3042,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2740,7 +3094,7 @@ snapshots: '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.7.3 + semver: 7.7.4 '@npmcli/move-file@2.0.1': dependencies: @@ -2750,17 +3104,40 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@reforged/maker-appimage@5.1.1': + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@reforged/maker-appimage@5.2.0': dependencies: '@electron-forge/maker-base': 7.11.1 - '@reforged/maker-types': 2.0.0 + '@reforged/maker-types': 2.1.0 '@spacingbat3/lss': 1.2.0 - semver: 7.7.3 + semver: 7.7.4 transitivePeerDependencies: - bluebird - supports-color - '@reforged/maker-types@2.0.0': {} + '@reforged/maker-types@2.1.0': {} '@sindresorhus/is@4.6.0': {} @@ -2774,14 +3151,14 @@ snapshots: '@types/appdmg@0.5.5': dependencies: - '@types/node': 25.0.8 + '@types/node': 25.2.3 optional: true '@types/cacheable-request@6.0.3': dependencies: - '@types/http-cache-semantics': 4.0.4 + '@types/http-cache-semantics': 4.2.0 '@types/keyv': 3.1.4 - '@types/node': 25.0.8 + '@types/node': 25.2.3 '@types/responselike': 1.0.3 '@types/eslint-scope@3.7.7': @@ -2798,38 +3175,44 @@ snapshots: '@types/fs-extra@9.0.13': dependencies: - '@types/node': 25.0.8 + '@types/node': 25.2.3 optional: true - '@types/http-cache-semantics@4.0.4': {} + '@types/http-cache-semantics@4.2.0': {} '@types/json-schema@7.0.15': {} '@types/keyv@3.1.4': dependencies: - '@types/node': 25.0.8 + '@types/node': 25.2.3 '@types/mute-stream@0.0.4': dependencies: - '@types/node': 22.19.6 + '@types/node': 22.19.11 - '@types/node@22.19.6': + '@types/node@20.19.33': dependencies: undici-types: 6.21.0 - '@types/node@25.0.8': + '@types/node@22.19.11': + dependencies: + undici-types: 6.21.0 + + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 '@types/responselike@1.0.3': dependencies: - '@types/node': 25.0.8 + '@types/node': 25.2.3 + + '@types/retry@0.12.0': {} '@types/wrap-ansi@3.0.0': {} '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.19.6 + '@types/node': 20.19.33 optional: true '@vscode/sudo-prompt@9.3.2': {} @@ -3006,7 +3389,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.14: {} + baseline-browser-mapping@2.9.19: {} bignumber.js@9.3.1: {} @@ -3018,8 +3401,7 @@ snapshots: bluebird@3.7.2: {} - boolean@3.2.0: - optional: true + boolean@3.2.0: {} bplist-creator@0.0.8: dependencies: @@ -3041,9 +3423,9 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.14 - caniuse-lite: 1.0.30001764 - electron-to-chromium: 1.5.267 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001769 + electron-to-chromium: 1.5.286 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -3093,7 +3475,7 @@ snapshots: normalize-url: 6.1.0 responselike: 2.0.1 - caniuse-lite@1.0.30001764: {} + caniuse-lite@1.0.30001769: {} chalk@4.1.2: dependencies: @@ -3104,6 +3486,8 @@ snapshots: chownr@2.0.0: {} + chownr@3.0.0: {} + chrome-trace-event@1.0.4: {} clean-stack@2.2.0: {} @@ -3210,19 +3594,16 @@ snapshots: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - optional: true define-properties@1.2.1: dependencies: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - optional: true detect-libc@2.1.2: {} - detect-node@2.1.0: - optional: true + detect-node@2.1.0: {} dir-compare@4.2.0: dependencies: @@ -3249,9 +3630,9 @@ snapshots: debug: 4.4.3 fs-extra: 9.1.0 glob: 7.2.3 - lodash: 4.17.21 + lodash: 4.17.23 parse-author: 2.0.0 - semver: 7.7.3 + semver: 7.7.4 tmp-promise: 3.0.3 optionalDependencies: '@types/fs-extra': 9.0.13 @@ -3266,7 +3647,7 @@ snapshots: electron-installer-common: 0.10.4 fs-extra: 9.1.0 get-folder-size: 2.0.1 - lodash: 4.17.21 + lodash: 4.17.23 word-wrap: 1.2.5 yargs: 16.2.0 transitivePeerDependencies: @@ -3290,7 +3671,7 @@ snapshots: debug: 4.4.3 electron-installer-common: 0.10.4 fs-extra: 9.1.0 - lodash: 4.17.21 + lodash: 4.17.23 word-wrap: 1.2.5 yargs: 16.2.0 transitivePeerDependencies: @@ -3303,14 +3684,14 @@ snapshots: transitivePeerDependencies: - supports-color - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.286: {} electron-winstaller@5.4.0: dependencies: '@electron/asar': 3.4.1 debug: 4.4.3 fs-extra: 7.0.1 - lodash: 4.17.21 + lodash: 4.17.23 temp: 0.9.4 optionalDependencies: '@electron/windows-sign': 1.2.2 @@ -3318,10 +3699,10 @@ snapshots: - supports-color optional: true - electron@39.2.7: + electron@30.5.1: dependencies: '@electron/get': 2.0.3 - '@types/node': 22.19.6 + '@types/node': 20.19.33 extract-zip: 2.0.1 transitivePeerDependencies: - supports-color @@ -3342,7 +3723,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.18.4: + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.0 @@ -3355,23 +3736,19 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-define-property@1.0.1: - optional: true + es-define-property@1.0.1: {} - es-errors@1.3.0: - optional: true + es-errors@1.3.0: {} es-module-lexer@2.0.0: {} - es6-error@4.1.1: - optional: true + es6-error@4.1.1: {} escalade@3.2.0: {} escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: - optional: true + escape-string-regexp@4.0.0: {} eslint-scope@5.1.1: dependencies: @@ -3386,7 +3763,7 @@ snapshots: estraverse@5.3.0: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} events@3.3.0: {} @@ -3466,6 +3843,8 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + flatbuffers@25.9.23: {} + flora-colossus@2.0.0: dependencies: debug: 4.4.3 @@ -3636,9 +4015,8 @@ snapshots: es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.7.3 + semver: 7.7.4 serialize-error: 7.0.1 - optional: true global-dirs@3.0.1: dependencies: @@ -3648,7 +4026,6 @@ snapshots: dependencies: define-properties: 1.2.1 gopd: 1.2.0 - optional: true google-auth-library@10.5.0: dependencies: @@ -3664,8 +4041,7 @@ snapshots: google-logging-utils@1.1.3: {} - gopd@1.2.0: - optional: true + gopd@1.2.0: {} got@11.8.6: dependencies: @@ -3690,12 +4066,13 @@ snapshots: transitivePeerDependencies: - supports-color + guid-typescript@1.0.9: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 - optional: true hasown@2.0.2: dependencies: @@ -3825,7 +4202,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.0.8 + '@types/node': 25.2.3 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -3841,8 +4218,7 @@ snapshots: json-schema-traverse@1.0.0: {} - json-stringify-safe@5.0.1: - optional: true + json-stringify-safe@5.0.1: {} jsonfile@4.0.0: optionalDependencies: @@ -3878,7 +4254,7 @@ snapshots: dependencies: cli-truncate: 3.1.0 colorette: 2.0.20 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 log-update: 5.0.1 rfdc: 1.4.1 wrap-ansi: 8.1.0 @@ -3903,7 +4279,7 @@ snapshots: lodash.get@4.4.2: {} - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -3918,6 +4294,8 @@ snapshots: strip-ansi: 7.1.2 wrap-ansi: 8.1.0 + long@5.3.2: {} + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -3926,7 +4304,7 @@ snapshots: macos-alias@0.2.12: dependencies: - nan: 2.24.0 + nan: 2.25.0 optional: true make-fetch-happen@10.2.1: @@ -3958,7 +4336,6 @@ snapshots: matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 - optional: true mem@4.3.0: dependencies: @@ -4038,6 +4415,10 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -4058,7 +4439,7 @@ snapshots: mute-stream@1.0.0: {} - nan@2.24.0: + nan@2.25.0: optional: true negotiator@0.6.4: {} @@ -4067,13 +4448,13 @@ snapshots: nice-try@1.0.5: {} - node-abi@3.85.0: + node-abi@3.87.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 node-api-version@0.2.1: dependencies: - semver: 7.7.3 + semver: 7.7.4 node-domexception@1.0.0: {} @@ -4108,8 +4489,11 @@ snapshots: dependencies: path-key: 2.0.1 - object-keys@1.1.1: - optional: true + object-keys@1.1.1: {} + + ollama@0.6.3: + dependencies: + whatwg-fetch: 3.6.20 once@1.4.0: dependencies: @@ -4119,9 +4503,24 @@ snapshots: dependencies: mimic-fn: 2.1.0 - openai@6.16.0(ws@8.19.0): - optionalDependencies: - ws: 8.19.0 + onnxruntime-common@1.21.0: {} + + onnxruntime-common@1.22.0-dev.20250409-89f8206ba4: {} + + onnxruntime-node@1.21.0: + dependencies: + global-agent: 3.0.0 + onnxruntime-common: 1.21.0 + tar: 7.5.7 + + onnxruntime-web@1.22.0-dev.20250409-89f8206ba4: + dependencies: + flatbuffers: 25.9.23 + guid-typescript: 1.0.9 + long: 5.3.2 + onnxruntime-common: 1.22.0-dev.20250409-89f8206ba4 + platform: 1.3.6 + protobufjs: 7.5.4 ora@5.4.1: dependencies: @@ -4165,6 +4564,11 @@ snapshots: dependencies: aggregate-error: 3.1.0 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + p-try@1.0.0: {} package-json-from-dist@1.0.1: {} @@ -4213,6 +4617,8 @@ snapshots: pify@2.3.0: {} + platform@1.3.6: {} + plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.11 @@ -4223,7 +4629,7 @@ snapshots: dependencies: commander: 9.5.0 - prettier@3.7.4: {} + prettier@3.8.1: {} proc-log@2.0.1: {} @@ -4236,6 +4642,21 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 25.2.3 + long: 5.3.2 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -4317,6 +4738,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -4342,7 +4765,6 @@ snapshots: json-stringify-safe: 5.0.1 semver-compare: 1.0.0 sprintf-js: 1.1.3 - optional: true run-parallel@1.2.0: dependencies: @@ -4359,24 +4781,53 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) - semver-compare@1.0.0: - optional: true + semver-compare@1.0.0: {} semver@5.7.2: {} semver@6.3.1: {} - semver@7.7.3: {} + semver@7.7.4: {} serialize-error@7.0.1: dependencies: type-fest: 0.13.1 - optional: true serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@1.2.0: dependencies: shebang-regex: 1.0.0 @@ -4434,8 +4885,7 @@ snapshots: spdx-license-ids@3.0.22: {} - sprintf-js@1.1.3: - optional: true + sprintf-js@1.1.3: {} ssri@9.0.1: dependencies: @@ -4503,22 +4953,30 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + tar@7.5.7: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + temp@0.9.4: dependencies: mkdirp: 0.5.6 rimraf: 2.6.3 optional: true - terser-webpack-plugin@5.3.16(webpack@5.104.1): + terser-webpack-plugin@5.3.16(webpack@5.105.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.1 - webpack: 5.104.1 + terser: 5.46.0 + webpack: 5.105.2 - terser@5.44.1: + terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 @@ -4558,9 +5016,11 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - type-fest@0.13.1: + tslib@2.8.1: optional: true + type-fest@0.13.1: {} + type-fest@0.21.3: {} type-fest@1.4.0: {} @@ -4604,7 +5064,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - watchpack@2.5.0: + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 @@ -4619,7 +5079,7 @@ snapshots: webpack-sources@3.3.3: {} - webpack@5.104.1: + webpack@5.105.2: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -4631,7 +5091,7 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.15.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.19.0 es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -4643,14 +5103,16 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.104.1) - watchpack: 2.5.0 + terser-webpack-plugin: 5.3.16(webpack@5.105.2) + watchpack: 2.5.1 webpack-sources: 3.3.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js + whatwg-fetch@3.6.20: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -4698,6 +5160,8 @@ snapshots: yallist@4.0.0: {} + yallist@5.0.0: {} + yargs-parser@20.2.9: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..53b9e54 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +onlyBuiltDependencies: + - electron + - electron-winstaller + - fs-xattr + - macos-alias + - onnxruntime-node + - protobufjs + - sharp diff --git a/src/audioUtils.js b/src/audioUtils.js index a9535e8..40602c6 100644 --- a/src/audioUtils.js +++ b/src/audioUtils.js @@ -86,7 +86,7 @@ function analyzeAudioBuffer(buffer, label = 'Audio') { // Save audio buffer with metadata for debugging function saveDebugAudio(buffer, type, timestamp = Date.now()) { const homeDir = require('os').homedir(); - const debugDir = path.join(homeDir, 'mastermind-debug'); + const debugDir = path.join(homeDir, 'cheating-daddy-debug'); if (!fs.existsSync(debugDir)) { fs.mkdirSync(debugDir, { recursive: true }); diff --git a/src/components/app/AppHeader.js b/src/components/app/AppHeader.js index 2b3f329..c6bc2ff 100644 --- a/src/components/app/AppHeader.js +++ b/src/components/app/AppHeader.js @@ -3,11 +3,7 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; export class AppHeader extends LitElement { static styles = css` * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; + font-family: var(--font); cursor: default; user-select: none; } @@ -18,14 +14,14 @@ export class AppHeader extends LitElement { align-items: center; padding: var(--header-padding); background: var(--header-background); - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid var(--border); } .header-title { flex: 1; font-size: var(--header-font-size); font-weight: 500; - color: var(--text-color); + color: var(--text-primary); -webkit-app-region: drag; } @@ -43,8 +39,8 @@ export class AppHeader extends LitElement { .button { background: transparent; - color: var(--text-color); - border: 1px solid var(--border-color); + color: var(--text-primary); + border: 1px solid var(--border); padding: var(--header-button-padding); border-radius: 3px; font-size: var(--header-font-size-small); @@ -77,7 +73,7 @@ export class AppHeader extends LitElement { .icon-button:hover { background: var(--hover-background); - color: var(--text-color); + color: var(--text-primary); } :host([isclickthrough]) .button:hover, @@ -90,7 +86,7 @@ export class AppHeader extends LitElement { padding: 2px 6px; border-radius: 3px; font-size: 11px; - font-family: 'SF Mono', Monaco, monospace; + font-family: var(--font-mono); } .click-through-indicator { @@ -99,7 +95,7 @@ export class AppHeader extends LitElement { background: var(--key-background); padding: 2px 6px; border-radius: 3px; - font-family: 'SF Mono', Monaco, monospace; + font-family: var(--font-mono); } .update-button { @@ -124,152 +120,6 @@ export class AppHeader extends LitElement { .update-button:hover { background: rgba(241, 76, 76, 0.1); } - - .status-wrapper { - position: relative; - display: inline-flex; - align-items: center; - } - - .status-text { - font-size: var(--header-font-size-small); - color: var(--text-secondary); - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .status-text.error { - color: #f14c4c; - } - - .status-tooltip { - position: absolute; - top: 100%; - right: 0; - margin-top: 8px; - background: var(--tooltip-bg, #1a1a1a); - color: var(--tooltip-text, #ffffff); - padding: 10px 14px; - border-radius: 6px; - font-size: 12px; - max-width: 300px; - word-wrap: break-word; - white-space: normal; - opacity: 0; - visibility: hidden; - transition: - opacity 0.15s ease, - visibility 0.15s ease; - pointer-events: none; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - z-index: 1000; - line-height: 1.4; - } - - .status-tooltip::before { - content: ''; - position: absolute; - bottom: 100%; - right: 16px; - border: 6px solid transparent; - border-bottom-color: var(--tooltip-bg, #1a1a1a); - } - - .status-wrapper:hover .status-tooltip { - opacity: 1; - visibility: visible; - } - - .status-tooltip .tooltip-label { - font-size: 10px; - text-transform: uppercase; - opacity: 0.6; - margin-bottom: 4px; - } - - .status-tooltip .tooltip-content { - color: #f14c4c; - } - - .model-info { - display: flex; - gap: 6px; - align-items: center; - } - - .model-badge { - font-size: 10px; - color: var(--text-muted); - background: var(--key-background); - padding: 2px 6px; - border-radius: 3px; - font-family: 'SF Mono', Monaco, monospace; - max-width: 100px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .model-badge-wrapper { - position: relative; - display: inline-flex; - } - - .model-badge-wrapper .model-tooltip { - position: absolute; - top: 100%; - right: 0; - margin-top: 8px; - background: var(--tooltip-bg, #1a1a1a); - color: var(--tooltip-text, #ffffff); - padding: 10px 14px; - border-radius: 6px; - font-size: 12px; - white-space: nowrap; - opacity: 0; - visibility: hidden; - transition: - opacity 0.15s ease, - visibility 0.15s ease; - pointer-events: none; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - z-index: 1000; - } - - .model-badge-wrapper .model-tooltip::before { - content: ''; - position: absolute; - bottom: 100%; - right: 16px; - border: 6px solid transparent; - border-bottom-color: var(--tooltip-bg, #1a1a1a); - } - - .model-badge-wrapper:hover .model-tooltip { - opacity: 1; - visibility: visible; - } - - .model-tooltip-row { - display: flex; - justify-content: space-between; - gap: 16px; - margin-bottom: 4px; - } - - .model-tooltip-row:last-child { - margin-bottom: 0; - } - - .model-tooltip-label { - opacity: 0.7; - } - - .model-tooltip-value { - font-family: 'SF Mono', Monaco, monospace; - } `; static properties = { @@ -284,8 +134,6 @@ export class AppHeader extends LitElement { onHideToggleClick: { type: Function }, isClickThrough: { type: Boolean, reflect: true }, updateAvailable: { type: Boolean }, - aiProvider: { type: String }, - modelInfo: { type: Object }, }; constructor() { @@ -302,8 +150,6 @@ export class AppHeader extends LitElement { this.isClickThrough = false; this.updateAvailable = false; this._timerInterval = null; - this.aiProvider = 'gemini'; - this.modelInfo = { model: '', visionModel: '', whisperModel: '' }; } connectedCallback() { @@ -314,8 +160,8 @@ export class AppHeader extends LitElement { async _checkForUpdates() { try { - const currentVersion = await mastermind.getVersion(); - const response = await fetch('https://raw.githubusercontent.com/ShiftyX1/Mastermind/refs/heads/master/package.json'); + const currentVersion = await cheatingDaddy.getVersion(); + const response = await fetch('https://raw.githubusercontent.com/sohzm/cheating-daddy/refs/heads/master/package.json'); if (!response.ok) return; const remotePackage = await response.json(); @@ -344,7 +190,7 @@ export class AppHeader extends LitElement { async _openUpdatePage() { const { ipcRenderer } = require('electron'); - await ipcRenderer.invoke('open-external', 'https://github.com/ShiftyX1/Mastermind'); + await ipcRenderer.invoke('open-external', 'https://cheatingdaddy.com'); } disconnectedCallback() { @@ -396,15 +242,15 @@ export class AppHeader extends LitElement { getViewTitle() { const titles = { - onboarding: 'Welcome to Mastermind', - main: 'Mastermind', + onboarding: 'Welcome to Cheating Daddy', + main: 'Cheating Daddy', customize: 'Customize', help: 'Help & Shortcuts', history: 'Conversation History', advanced: 'Advanced Tools', - assistant: 'Mastermind', + assistant: 'Cheating Daddy', }; - return titles[this.currentView] || 'Mastermind'; + return titles[this.currentView] || 'Cheating Daddy'; } getElapsedTime() { @@ -425,49 +271,8 @@ export class AppHeader extends LitElement { return navigationViews.includes(this.currentView); } - getProviderDisplayName() { - const names = { - 'gemini': 'Gemini', - 'openai-realtime': 'OpenAI Realtime', - 'openai-sdk': 'OpenAI SDK', - }; - return names[this.aiProvider] || this.aiProvider; - } - - renderModelInfo() { - // Only show model info for OpenAI SDK provider - if (this.aiProvider !== 'openai-sdk' || !this.modelInfo) { - return ''; - } - - const { model, visionModel, whisperModel } = this.modelInfo; - - // Show a compact badge with tooltip for model details - return html` -
- ${model || 'gpt-4o'} -
-
- Text - ${model || 'gpt-4o'} -
-
- Vision - ${visionModel || 'gpt-4o'} -
-
- Speech - ${whisperModel || 'whisper-1'} -
-
-
- `; - } - render() { const elapsedTime = this.getElapsedTime(); - const isError = this.statusText && (this.statusText.toLowerCase().includes('error') || this.statusText.toLowerCase().includes('failed')); - const shortStatus = isError ? 'Error' : this.statusText; return html`
@@ -475,63 +280,34 @@ export class AppHeader extends LitElement {
${this.currentView === 'assistant' ? html` - ${this.renderModelInfo()} ${elapsedTime} -
- ${shortStatus} - ${isError - ? html` -
-
Error Details
-
${this.statusText}
-
- ` - : ''} -
+ ${this.statusText} ${this.isClickThrough ? html`click-through` : ''} ` : ''} ${this.currentView === 'main' ? html` - ${this.updateAvailable - ? html` - - ` - : ''} + ${this.updateAvailable ? html` + + ` : ''} ` @@ -539,23 +315,19 @@ export class AppHeader extends LitElement { ${this.currentView === 'assistant' ? html` ` : html` `} diff --git a/src/components/app/CheatingDaddyApp.js b/src/components/app/CheatingDaddyApp.js new file mode 100644 index 0000000..8f29f40 --- /dev/null +++ b/src/components/app/CheatingDaddyApp.js @@ -0,0 +1,881 @@ +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); diff --git a/src/components/app/MastermindApp.js b/src/components/app/MastermindApp.js deleted file mode 100644 index 2087bdd..0000000 --- a/src/components/app/MastermindApp.js +++ /dev/null @@ -1,640 +0,0 @@ -import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { AppHeader } from './AppHeader.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 { ScreenPickerDialog } from '../views/ScreenPickerDialog.js'; - -export class MastermindApp extends LitElement { - static styles = css` - * { - box-sizing: border-box; - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - margin: 0px; - padding: 0px; - cursor: default; - user-select: none; - } - - :host { - display: block; - width: 100%; - height: 100vh; - background-color: var(--background-transparent); - color: var(--text-color); - } - - .window-container { - height: 100vh; - overflow: hidden; - background: var(--bg-primary); - } - - .container { - display: flex; - flex-direction: column; - height: 100%; - } - - .main-content { - flex: 1; - padding: var(--main-content-padding); - overflow-y: auto; - background: var(--main-content-background); - } - - .main-content.with-border { - border-top: none; - } - - .main-content.assistant-view { - padding: 12px; - } - - .main-content.onboarding-view { - padding: 0; - background: transparent; - } - - .main-content.settings-view, - .main-content.help-view, - .main-content.history-view { - padding: 0; - } - - .view-container { - opacity: 1; - height: 100%; - } - - .view-container.entering { - opacity: 0; - } - - ::-webkit-scrollbar { - width: 8px; - height: 8px; - } - - ::-webkit-scrollbar-track { - background: transparent; - } - - ::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 4px; - } - - ::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); - } - `; - - 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 }, - aiProvider: { type: String }, - modelInfo: { type: Object }, - showScreenPicker: { type: Boolean }, - screenSources: { type: Array }, - }; - - constructor() { - super(); - // Set defaults - will be overwritten by storage - this.currentView = 'main'; // Will check onboarding after storage loads - 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.aiProvider = 'gemini'; - this.modelInfo = { model: '', visionModel: '', whisperModel: '' }; - this.showScreenPicker = false; - this.screenSources = []; - - // Load from storage - this._loadFromStorage(); - } - - async _loadFromStorage() { - try { - const [config, prefs, openaiSdkCreds] = await Promise.all([ - mastermind.storage.getConfig(), - mastermind.storage.getPreferences(), - mastermind.storage.getOpenAISDKCredentials(), - ]); - - // Check onboarding status - this.currentView = config.onboarded ? 'main' : 'onboarding'; - - // Apply background appearance (color + transparency) - this.applyBackgroundAppearance(prefs.backgroundColor ?? '#1e1e1e', prefs.backgroundTransparency ?? 0.8); - - // Load preferences - 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'; - - // Load AI provider and model info - this.aiProvider = prefs.aiProvider || 'gemini'; - this.modelInfo = { - model: openaiSdkCreds.model || 'gpt-4o', - visionModel: openaiSdkCreds.visionModel || 'gpt-4o', - whisperModel: openaiSdkCreds.whisperModel || 'whisper-1', - }; - - this._storageLoaded = true; - this.updateLayoutMode(); - this.requestUpdate(); - } catch (error) { - console.error('Error loading from storage:', error); - this._storageLoaded = true; - this.requestUpdate(); - } - } - - hexToRgb(hex) { - const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result - ? { - r: parseInt(result[1], 16), - g: parseInt(result[2], 16), - b: parseInt(result[3], 16), - } - : { r: 30, g: 30, b: 30 }; - } - - lightenColor(rgb, amount) { - return { - r: Math.min(255, rgb.r + amount), - g: Math.min(255, rgb.g + amount), - b: Math.min(255, rgb.b + amount), - }; - } - - applyBackgroundAppearance(backgroundColor, alpha) { - const root = document.documentElement; - const baseRgb = this.hexToRgb(backgroundColor); - - // Generate color variants based on the base color - const secondary = this.lightenColor(baseRgb, 7); - const tertiary = this.lightenColor(baseRgb, 15); - const hover = this.lightenColor(baseRgb, 20); - - root.style.setProperty('--header-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`); - root.style.setProperty('--main-content-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`); - root.style.setProperty('--bg-primary', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`); - root.style.setProperty('--bg-secondary', `rgba(${secondary.r}, ${secondary.g}, ${secondary.b}, ${alpha})`); - root.style.setProperty('--bg-tertiary', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`); - root.style.setProperty('--bg-hover', `rgba(${hover.r}, ${hover.g}, ${hover.b}, ${alpha})`); - root.style.setProperty('--input-background', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`); - root.style.setProperty('--input-focus-background', `rgba(${tertiary.r}, ${tertiary.g}, ${tertiary.b}, ${alpha})`); - root.style.setProperty('--hover-background', `rgba(${hover.r}, ${hover.g}, ${hover.b}, ${alpha})`); - root.style.setProperty('--scrollbar-background', `rgba(${baseRgb.r}, ${baseRgb.g}, ${baseRgb.b}, ${alpha})`); - } - - // Keep old function name for backwards compatibility - applyBackgroundTransparency(alpha) { - this.applyBackgroundAppearance('#1e1e1e', alpha); - } - - connectedCallback() { - super.connectedCallback(); - - // Apply layout mode to document root - this.updateLayoutMode(); - - // Set up IPC listeners if needed - 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); - }); - } - } - - disconnectedCallback() { - super.disconnectedCallback(); - 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'); - } - } - - setStatus(text) { - this.statusText = text; - - // Mark response as complete when we get certain status messages - if (text.includes('Ready') || text.includes('Listening') || text.includes('Error')) { - this._currentResponseIsComplete = true; - console.log('[setStatus] Marked current response as complete'); - } - } - - addNewResponse(response) { - // Add a new response entry (first word of a new AI response) - this.responses = [...this.responses, response]; - this.currentResponseIndex = this.responses.length - 1; - this._awaitingNewResponse = false; - console.log('[addNewResponse] Added:', response); - this.requestUpdate(); - } - - updateCurrentResponse(response) { - // Update the current response in place (streaming subsequent words) - if (this.responses.length > 0) { - this.responses = [...this.responses.slice(0, -1), response]; - console.log('[updateCurrentResponse] Updated to:', response); - } else { - // Fallback: if no responses exist, add as new - this.addNewResponse(response); - } - this.requestUpdate(); - } - - // Header event handlers - handleCustomizeClick() { - this.currentView = 'customize'; - this.requestUpdate(); - } - - handleHelpClick() { - this.currentView = 'help'; - this.requestUpdate(); - } - - handleHistoryClick() { - this.currentView = 'history'; - this.requestUpdate(); - } - - async handleClose() { - if (this.currentView === 'customize' || this.currentView === 'help' || this.currentView === 'history') { - this.currentView = 'main'; - } else if (this.currentView === 'assistant') { - mastermind.stopCapture(); - - // Close the session - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('close-session'); - } - this.sessionActive = false; - this.currentView = 'main'; - console.log('Session closed'); - } else { - // Quit the entire application - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('quit-application'); - } - } - } - - async handleHideToggle() { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('toggle-window-visibility'); - } - } - - // Main view event handlers - async handleStart() { - // check if api key is empty do nothing - const apiKey = await mastermind.storage.getApiKey(); - if (!apiKey || apiKey === '') { - // Trigger the red blink animation on the API key input - const mainView = this.shadowRoot.querySelector('main-view'); - if (mainView && mainView.triggerApiKeyError) { - mainView.triggerApiKeyError(); - } - return; - } - - await mastermind.initializeGemini(this.selectedProfile, this.selectedLanguage); - // Pass the screenshot interval as string (including 'manual' option) - mastermind.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality); - this.responses = []; - this.currentResponseIndex = -1; - this.startTime = Date.now(); - this.currentView = 'assistant'; - } - - async handleAPIKeyHelp() { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('open-external', 'https://cheatingdaddy.com/help/api-key'); - } - } - - // Customize view event handlers - async handleProfileChange(profile) { - this.selectedProfile = profile; - await mastermind.storage.updatePreference('selectedProfile', profile); - } - - async handleLanguageChange(language) { - this.selectedLanguage = language; - await mastermind.storage.updatePreference('selectedLanguage', language); - } - - async handleScreenshotIntervalChange(interval) { - this.selectedScreenshotInterval = interval; - await mastermind.storage.updatePreference('selectedScreenshotInterval', interval); - } - - async handleImageQualityChange(quality) { - this.selectedImageQuality = quality; - await mastermind.storage.updatePreference('selectedImageQuality', quality); - } - - handleBackClick() { - this.currentView = 'main'; - this.requestUpdate(); - } - - // Help view event handlers - async handleExternalLinkClick(url) { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('open-external', url); - } - } - - // Assistant view event handlers - async handleSendText(message) { - const result = await window.mastermind.sendTextMessage(message); - - if (!result.success) { - console.error('Failed to send message:', result.error); - 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(); - } - - // Onboarding event handlers - handleOnboardingComplete() { - this.currentView = 'main'; - } - - updated(changedProperties) { - super.updated(changedProperties); - - // Only notify main process of view change if the view actually changed - if (changedProperties.has('currentView') && window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('view-changed', this.currentView); - - // Add a small delay to smooth out the transition - const viewContainer = this.shadowRoot?.querySelector('.view-container'); - if (viewContainer) { - viewContainer.classList.add('entering'); - requestAnimationFrame(() => { - viewContainer.classList.remove('entering'); - }); - } - } - - if (changedProperties.has('layoutMode')) { - this.updateLayoutMode(); - } - } - - renderCurrentView() { - // Only re-render the view if it hasn't been cached or if critical properties changed - const viewKey = `${this.currentView}-${this.selectedProfile}-${this.selectedLanguage}`; - - switch (this.currentView) { - case 'onboarding': - return html` - this.handleOnboardingComplete()} .onClose=${() => this.handleClose()}> - `; - - case 'main': - return html` - this.handleStart()} - .onAPIKeyHelp=${() => this.handleAPIKeyHelp()} - .onLayoutModeChange=${layoutMode => this.handleLayoutModeChange(layoutMode)} - > - `; - - case 'customize': - return html` - this.handleProfileChange(profile)} - .onLanguageChange=${language => this.handleLanguageChange(language)} - .onScreenshotIntervalChange=${interval => this.handleScreenshotIntervalChange(interval)} - .onImageQualityChange=${quality => this.handleImageQualityChange(quality)} - .onLayoutModeChange=${layoutMode => this.handleLayoutModeChange(layoutMode)} - > - `; - - case 'help': - return html` this.handleExternalLinkClick(url)}> `; - - case 'history': - return html` `; - - case 'assistant': - return html` - this.handleSendText(message)} - .shouldAnimateResponse=${this.shouldAnimateResponse} - @response-index-changed=${this.handleResponseIndexChanged} - @response-animation-complete=${() => { - this.shouldAnimateResponse = false; - this._currentResponseIsComplete = true; - console.log('[response-animation-complete] Marked current response as complete'); - this.requestUpdate(); - }} - > - `; - - default: - return html`
Unknown view: ${this.currentView}
`; - } - } - - render() { - const viewClassMap = { - assistant: 'assistant-view', - onboarding: 'onboarding-view', - customize: 'settings-view', - help: 'help-view', - history: 'history-view', - }; - const mainContentClass = `main-content ${viewClassMap[this.currentView] || 'with-border'}`; - - return html` -
-
- this.handleCustomizeClick()} - .onHelpClick=${() => this.handleHelpClick()} - .onHistoryClick=${() => this.handleHistoryClick()} - .onCloseClick=${() => this.handleClose()} - .onBackClick=${() => this.handleBackClick()} - .onHideToggleClick=${() => this.handleHideToggle()} - ?isClickThrough=${this._isClickThrough} - > -
-
${this.renderCurrentView()}
-
-
- ${this.showScreenPicker - ? html` - - ` - : ''} -
- `; - } - - updateLayoutMode() { - // Apply or remove compact layout class to document root - if (this.layoutMode === 'compact') { - document.documentElement.classList.add('compact-layout'); - } else { - document.documentElement.classList.remove('compact-layout'); - } - } - - async handleLayoutModeChange(layoutMode) { - this.layoutMode = layoutMode; - await mastermind.storage.updateConfig('layout', layoutMode); - this.updateLayoutMode(); - - // Notify main process about layout change for window resizing - if (window.require) { - try { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('update-sizes'); - } catch (error) { - console.error('Failed to update sizes in main process:', error); - } - } - - this.requestUpdate(); - } - - async showScreenPickerDialog() { - const { ipcRenderer } = window.require('electron'); - const result = await ipcRenderer.invoke('get-screen-sources'); - - if (result.success) { - this.screenSources = result.sources; - this.showScreenPicker = true; - return new Promise(resolve => { - this._screenPickerResolve = resolve; - }); - } else { - console.error('Failed to get screen sources:', result.error); - return { cancelled: true }; - } - } - - async handleSourceSelected(event) { - const { source } = event.detail; - const { ipcRenderer } = window.require('electron'); - - // Tell main process which source was selected - await ipcRenderer.invoke('set-selected-source', source.id); - - this.showScreenPicker = false; - if (this._screenPickerResolve) { - this._screenPickerResolve({ source }); - this._screenPickerResolve = null; - } - } - - handlePickerCancelled() { - this.showScreenPicker = false; - if (this._screenPickerResolve) { - this._screenPickerResolve({ cancelled: true }); - this._screenPickerResolve = null; - } - } -} - -customElements.define('mastermind-app', MastermindApp); diff --git a/src/components/index.js b/src/components/index.js index b927755..33c93fc 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,5 +1,5 @@ // Main app components -export { MastermindApp } from './app/MastermindApp.js'; +export { CheatingDaddyApp } from './app/CheatingDaddyApp.js'; export { AppHeader } from './app/AppHeader.js'; // View components diff --git a/src/components/views/AICustomizeView.js b/src/components/views/AICustomizeView.js new file mode 100644 index 0000000..095c31c --- /dev/null +++ b/src/components/views/AICustomizeView.js @@ -0,0 +1,143 @@ +import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; +import { unifiedPageStyles } from './sharedPageStyles.js'; + +export class AICustomizeView extends LitElement { + static styles = [ + unifiedPageStyles, + css` + .unified-page { + height: 100%; + } + .unified-wrap { + height: 100%; + } + section.surface { + flex: 1; + display: flex; + flex-direction: column; + } + .form-grid { + flex: 1; + display: flex; + flex-direction: column; + } + .form-group.vertical { + flex: 1; + display: flex; + flex-direction: column; + } + textarea.control { + flex: 1; + resize: none; + overflow-y: auto; + min-height: 0; + } + `, + ]; + + static properties = { + selectedProfile: { type: String }, + onProfileChange: { type: Function }, + _context: { state: true }, + _providerMode: { state: true }, + }; + + constructor() { + super(); + this.selectedProfile = 'interview'; + this.onProfileChange = () => {}; + this._context = ''; + this._providerMode = 'byok'; + this._loadFromStorage(); + } + + async _loadFromStorage() { + try { + const prefs = await cheatingDaddy.storage.getPreferences(); + this._context = prefs.customPrompt || ''; + this._providerMode = prefs.providerMode || 'byok'; + this.requestUpdate(); + } catch (error) { + console.error('Error loading AI customize storage:', error); + } + } + + _handleProfileChange(e) { + this.onProfileChange(e.target.value); + } + + async _handleProviderModeChange(e) { + this._providerMode = e.target.value; + await cheatingDaddy.storage.updatePreference('providerMode', this._providerMode); + this.requestUpdate(); + } + + async _saveContext(val) { + this._context = val; + await cheatingDaddy.storage.updatePreference('customPrompt', val); + } + + _getProfileName(profile) { + const names = { + interview: 'Job Interview', + sales: 'Sales Call', + meeting: 'Business Meeting', + presentation: 'Presentation', + negotiation: 'Negotiation', + exam: 'Exam Assistant', + }; + return names[profile] || profile; + } + + render() { + const profiles = [ + { value: 'interview', label: 'Job Interview' }, + { value: 'sales', label: 'Sales Call' }, + { value: 'meeting', label: 'Business Meeting' }, + { value: 'presentation', label: 'Presentation' }, + { value: 'negotiation', label: 'Negotiation' }, + { value: 'exam', label: 'Exam Assistant' }, + ]; + + return html` +
+
+
+
AI Context
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
Sent as context at session start. Keep it short.
+
+
+
+ +
+
+ `; + } +} + +customElements.define('ai-customize-view', AICustomizeView); diff --git a/src/components/views/AssistantView.js b/src/components/views/AssistantView.js index ad1a0af..22cf506 100644 --- a/src/components/views/AssistantView.js +++ b/src/components/views/AssistantView.js @@ -9,24 +9,23 @@ export class AssistantView extends LitElement { } * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; + font-family: var(--font); cursor: default; } + /* ── Response area ── */ + .response-container { - height: calc(100% - 50px); + flex: 1; overflow-y: auto; - font-size: var(--response-font-size, 16px); - line-height: 1.6; - background: var(--bg-primary); - padding: 12px; + font-size: var(--response-font-size, 15px); + line-height: var(--line-height); + background: var(--bg-app); + padding: var(--space-sm) var(--space-md); scroll-behavior: smooth; user-select: text; cursor: text; + color: var(--text-primary); } .response-container * { @@ -38,12 +37,12 @@ export class AssistantView extends LitElement { cursor: pointer; } - /* Word display (no animation) */ .response-container [data-word] { display: inline-block; } - /* Markdown styling */ + /* ── Markdown ── */ + .response-container h1, .response-container h2, .response-container h3, @@ -51,39 +50,27 @@ export class AssistantView extends LitElement { .response-container h5, .response-container h6 { margin: 1em 0 0.5em 0; - color: var(--text-color); - font-weight: 600; + color: var(--text-primary); + font-weight: var(--font-weight-semibold); } - .response-container h1 { - font-size: 1.6em; - } - .response-container h2 { - font-size: 1.4em; - } - .response-container h3 { - font-size: 1.2em; - } - .response-container h4 { - font-size: 1.1em; - } - .response-container h5 { - font-size: 1em; - } - .response-container h6 { - font-size: 0.9em; - } + .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 p { margin: 0.6em 0; - color: var(--text-color); + color: var(--text-primary); } .response-container ul, .response-container ol { margin: 0.6em 0; padding-left: 1.5em; - color: var(--text-color); + color: var(--text-primary); } .response-container li { @@ -93,23 +80,24 @@ export class AssistantView extends LitElement { .response-container blockquote { margin: 0.8em 0; padding: 0.5em 1em; - border-left: 2px solid var(--border-default); - background: var(--bg-secondary); + border-left: 2px solid var(--border-strong); + background: var(--bg-surface); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; } .response-container code { - background: var(--bg-tertiary); + background: var(--bg-elevated); padding: 0.15em 0.4em; - border-radius: 3px; - font-family: 'SF Mono', Monaco, monospace; + border-radius: var(--radius-sm); + font-family: var(--font-mono); font-size: 0.85em; } .response-container pre { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 3px; - padding: 12px; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: var(--space-md); overflow-x: auto; margin: 0.8em 0; } @@ -120,19 +108,19 @@ export class AssistantView extends LitElement { } .response-container a { - color: var(--text-color); + color: var(--accent); text-decoration: underline; text-underline-offset: 2px; } .response-container strong, .response-container b { - font-weight: 600; + font-weight: var(--font-weight-semibold); } .response-container hr { border: none; - border-top: 1px solid var(--border-color); + border-top: 1px solid var(--border); margin: 1.5em 0; } @@ -144,18 +132,18 @@ export class AssistantView extends LitElement { .response-container th, .response-container td { - border: 1px solid var(--border-color); - padding: 8px; + border: 1px solid var(--border); + padding: var(--space-sm); text-align: left; } .response-container th { - background: var(--bg-secondary); - font-weight: 600; + background: var(--bg-surface); + font-weight: var(--font-weight-semibold); } .response-container::-webkit-scrollbar { - width: 8px; + width: 6px; } .response-container::-webkit-scrollbar-track { @@ -163,259 +151,152 @@ export class AssistantView extends LitElement { } .response-container::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 4px; + background: var(--border-strong); + border-radius: 3px; } .response-container::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); + background: #444444; } - .text-input-container { - display: flex; - gap: 8px; - margin-top: 8px; - align-items: center; - } + /* ── Response navigation strip ── */ - .text-input-container input { - flex: 1; - background: transparent; - color: var(--text-color); - border: none; - border-bottom: 1px solid var(--border-color); - padding: 8px 4px; - border-radius: 0; - font-size: 13px; - } - - .text-input-container input:focus { - outline: none; - border-bottom-color: var(--text-color); - } - - .text-input-container input::placeholder { - color: var(--placeholder-color); - } - - .nav-button { - background: transparent; - color: var(--text-secondary); - border: none; - padding: 6px; - border-radius: 3px; - font-size: 12px; + .response-nav { display: flex; align-items: center; justify-content: center; - transition: all 0.1s ease; + gap: var(--space-sm); + padding: var(--space-xs) var(--space-md); + border-top: 1px solid var(--border); + background: var(--bg-app); } - .nav-button:hover { - background: var(--hover-background); - color: var(--text-color); + .nav-btn { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: var(--space-xs); + border-radius: var(--radius-sm); + display: flex; + align-items: center; + justify-content: center; + transition: color var(--transition); } - .nav-button:disabled { - opacity: 0.3; + .nav-btn:hover:not(:disabled) { + color: var(--text-primary); } - .nav-button svg { - width: 18px; - height: 18px; - stroke: currentColor; + .nav-btn:disabled { + opacity: 0.25; + cursor: default; + } + + .nav-btn svg { + width: 14px; + height: 14px; } .response-counter { - font-size: 11px; + font-size: var(--font-size-xs); color: var(--text-muted); - white-space: nowrap; - min-width: 50px; + font-family: var(--font-mono); + min-width: 40px; text-align: center; - font-family: 'SF Mono', Monaco, monospace; } - .screen-answer-btn { + /* ── Bottom input bar ── */ + + .input-bar { display: flex; align-items: center; - gap: 6px; - background: var(--btn-primary-bg, #ffffff); - color: var(--btn-primary-text, #000000); + gap: var(--space-sm); + padding: var(--space-md); + background: var(--bg-app); + } + + .input-bar-inner { + display: flex; + align-items: center; + flex: 1; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 100px; + padding: 0 var(--space-md); + height: 32px; + transition: border-color var(--transition); + } + + .input-bar-inner:focus-within { + border-color: var(--accent); + } + + .input-bar-inner input { + flex: 1; + background: none; + color: var(--text-primary); border: none; - padding: 6px 12px; - border-radius: 20px; - font-size: 12px; - font-weight: 500; + padding: 0; + font-size: var(--font-size-sm); + font-family: var(--font); + height: 100%; + outline: none; + } + + .input-bar-inner input::placeholder { + color: var(--text-muted); + } + + .analyze-btn { + position: relative; + background: var(--bg-elevated); + border: 1px solid var(--border); + color: var(--text-primary); cursor: pointer; - transition: all 0.15s ease; + font-size: var(--font-size-xs); + font-family: var(--font-mono); white-space: nowrap; - } - - .screen-answer-btn:hover { - background: var(--btn-primary-hover, #f0f0f0); - } - - .screen-answer-btn svg { - width: 16px; - height: 16px; + padding: var(--space-xs) var(--space-md); + border-radius: 100px; + height: 32px; + display: flex; + align-items: center; + gap: 4px; + transition: border-color 0.4s ease, background var(--transition); flex-shrink: 0; + overflow: hidden; } - .screen-answer-btn .usage-count { - font-size: 11px; - opacity: 0.7; - font-family: 'SF Mono', Monaco, monospace; + .analyze-btn:hover:not(.analyzing) { + border-color: var(--accent); + background: var(--bg-surface); } - .screen-answer-btn-wrapper { + .analyze-btn.analyzing { + cursor: default; + border-color: transparent; + } + + .analyze-btn-content { + display: flex; + align-items: center; + gap: 4px; + transition: opacity 0.4s ease; + z-index: 1; position: relative; } - .screen-answer-btn-wrapper .tooltip { - position: absolute; - bottom: 100%; - right: 0; - margin-bottom: 8px; - background: var(--tooltip-bg, #1a1a1a); - color: var(--tooltip-text, #ffffff); - padding: 8px 12px; - border-radius: 6px; - font-size: 11px; - white-space: nowrap; + .analyze-btn.analyzing .analyze-btn-content { opacity: 0; - visibility: hidden; - transition: - opacity 0.15s ease, - visibility 0.15s ease; - pointer-events: none; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - z-index: 100; } - .screen-answer-btn-wrapper .tooltip::after { - content: ''; + .analyze-canvas { position: absolute; - top: 100%; - right: 16px; - border: 6px solid transparent; - border-top-color: var(--tooltip-bg, #1a1a1a); - } - - .screen-answer-btn-wrapper:hover .tooltip { - opacity: 1; - visibility: visible; - } - - .tooltip-row { - display: flex; - justify-content: space-between; - gap: 16px; - margin-bottom: 4px; - } - - .tooltip-row:last-child { - margin-bottom: 0; - } - - .tooltip-label { - opacity: 0.7; - } - - .tooltip-value { - font-family: 'SF Mono', Monaco, monospace; - } - - .tooltip-note { - margin-top: 6px; - padding-top: 6px; - border-top: 1px solid rgba(255, 255, 255, 0.1); - opacity: 0.5; - font-size: 10px; - } - - .capture-buttons { - display: flex; - gap: 6px; - } - - .region-select-btn { - display: flex; - align-items: center; - justify-content: center; - background: transparent; - color: var(--text-secondary); - border: 1px solid var(--border-color); - padding: 6px 10px; - border-radius: 20px; - font-size: 12px; - cursor: pointer; - transition: all 0.15s ease; - } - - .region-select-btn:hover { - background: var(--hover-background); - color: var(--text-color); - border-color: var(--text-color); - } - - .region-select-btn svg { - width: 16px; - height: 16px; - } - - .region-select-btn span { - margin-left: 4px; - } - - .ptt-toggle-btn { - display: flex; - align-items: center; - justify-content: center; - background: transparent; - color: var(--text-secondary); - border: 1px solid var(--border-color); - padding: 6px 12px; - border-radius: 20px; - font-size: 12px; - cursor: pointer; - transition: all 0.15s ease; - } - - .ptt-toggle-btn:hover { - background: var(--hover-background); - color: var(--text-color); - border-color: var(--text-color); - } - - .ptt-toggle-btn.active { - color: var(--error-color); - border-color: var(--error-color); - } - - .ptt-indicator { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - color: var(--text-secondary); - margin-bottom: 6px; - } - - .ptt-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--border-color); - box-shadow: 0 0 0 1px var(--border-color); - } - - .ptt-dot.active { - background: var(--error-color); - box-shadow: 0 0 0 1px var(--error-color); - } - - .ptt-label { - font-family: 'SF Mono', Monaco, monospace; + inset: -1px; + width: calc(100% + 2px); + height: calc(100% + 2px); + pointer-events: none; } `; @@ -425,12 +306,7 @@ export class AssistantView extends LitElement { selectedProfile: { type: String }, onSendText: { type: Function }, shouldAnimateResponse: { type: Boolean }, - flashCount: { type: Number }, - flashLiteCount: { type: Number }, - aiProvider: { type: String }, - pushToTalkActive: { type: Boolean }, - audioInputMode: { type: String }, - pushToTalkKeybind: { type: String }, + isAnalyzing: { type: Boolean, state: true }, }; constructor() { @@ -439,12 +315,8 @@ export class AssistantView extends LitElement { this.currentResponseIndex = -1; this.selectedProfile = 'interview'; this.onSendText = () => {}; - this.flashCount = 0; - this.flashLiteCount = 0; - this.aiProvider = 'gemini'; - this.pushToTalkActive = false; - this.audioInputMode = 'auto'; - this.pushToTalkKeybind = ''; + this.isAnalyzing = false; + this._animFrame = null; } getProfileNames() { @@ -462,29 +334,26 @@ export class AssistantView extends LitElement { const profileNames = this.getProfileNames(); return this.responses.length > 0 && this.currentResponseIndex >= 0 ? this.responses[this.currentResponseIndex] - : `Hey, Im listening to your ${profileNames[this.selectedProfile] || 'session'}?`; + : `Listening to your ${profileNames[this.selectedProfile] || 'session'}...`; } renderMarkdown(content) { - // Check if marked is available if (typeof window !== 'undefined' && window.marked) { try { - // Configure marked for better security and formatting window.marked.setOptions({ breaks: true, gfm: true, - sanitize: false, // We trust the AI responses + sanitize: false, }); let rendered = window.marked.parse(content); rendered = this.wrapWordsInSpans(rendered); return rendered; } catch (error) { console.warn('Error parsing markdown:', error); - return content; // Fallback to plain text + return content; } } - console.log('Marked not available, using plain text'); - return content; // Fallback if marked is not available + return content; } wrapWordsInSpans(html) { @@ -515,10 +384,6 @@ export class AssistantView extends LitElement { return doc.body.innerHTML; } - getResponseCounter() { - return this.responses.length > 0 ? `${this.currentResponseIndex + 1}/${this.responses.length}` : ''; - } - navigateToPreviousResponse() { if (this.currentResponseIndex > 0) { this.currentResponseIndex--; @@ -546,7 +411,7 @@ export class AssistantView extends LitElement { scrollResponseUp() { const container = this.shadowRoot.querySelector('.response-container'); if (container) { - const scrollAmount = container.clientHeight * 0.3; // Scroll 30% of container height + const scrollAmount = container.clientHeight * 0.3; container.scrollTop = Math.max(0, container.scrollTop - scrollAmount); } } @@ -554,7 +419,7 @@ export class AssistantView extends LitElement { scrollResponseDown() { const container = this.shadowRoot.querySelector('.response-container'); if (container) { - const scrollAmount = container.clientHeight * 0.3; // Scroll 30% of container height + const scrollAmount = container.clientHeight * 0.3; container.scrollTop = Math.min(container.scrollHeight - container.clientHeight, container.scrollTop + scrollAmount); } } @@ -562,69 +427,31 @@ export class AssistantView extends LitElement { connectedCallback() { super.connectedCallback(); - // Load limits on mount - this.loadLimits(); - this.loadPushToTalkKeybind(); - - // Set up IPC listeners for keyboard shortcuts if (window.require) { const { ipcRenderer } = window.require('electron'); - this.handlePreviousResponse = () => { - console.log('Received navigate-previous-response message'); - this.navigateToPreviousResponse(); - }; - - this.handleNextResponse = () => { - console.log('Received navigate-next-response message'); - this.navigateToNextResponse(); - }; - - this.handleScrollUp = () => { - console.log('Received scroll-response-up message'); - this.scrollResponseUp(); - }; - - this.handleScrollDown = () => { - console.log('Received scroll-response-down message'); - this.scrollResponseDown(); - }; - - this.handlePushToTalkState = (event, state) => { - this.pushToTalkActive = state?.active ?? false; - this.audioInputMode = state?.inputMode ?? 'auto'; - this.requestUpdate(); - }; + this.handlePreviousResponse = () => this.navigateToPreviousResponse(); + this.handleNextResponse = () => this.navigateToNextResponse(); + this.handleScrollUp = () => this.scrollResponseUp(); + this.handleScrollDown = () => this.scrollResponseDown(); 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('push-to-talk-state', this.handlePushToTalkState); } } disconnectedCallback() { super.disconnectedCallback(); + this._stopWaveformAnimation(); - // Clean up IPC listeners 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); - } - if (this.handlePushToTalkState) { - ipcRenderer.removeListener('push-to-talk-state', this.handlePushToTalkState); - } + 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); } } @@ -632,7 +459,7 @@ export class AssistantView extends LitElement { const textInput = this.shadowRoot.querySelector('#textInput'); if (textInput && textInput.value.trim()) { const message = textInput.value.trim(); - textInput.value = ''; // Clear input + textInput.value = ''; await this.onSendText(message); } } @@ -644,53 +471,150 @@ export class AssistantView extends LitElement { } } - async loadLimits() { - if (window.mastermind?.storage?.getTodayLimits) { - const limits = await window.mastermind.storage.getTodayLimits(); - this.flashCount = limits.flash?.count || 0; - this.flashLiteCount = limits.flashLite?.count || 0; - } - } - - async loadPushToTalkKeybind() { - if (window.mastermind?.storage?.getKeybinds) { - const isMac = window.mastermind?.isMacOS || navigator.platform.includes('Mac'); - const defaultKeybind = isMac ? 'Ctrl+Space' : 'Ctrl+Space'; - const keybinds = await window.mastermind.storage.getKeybinds(); - this.pushToTalkKeybind = keybinds?.pushToTalk || defaultKeybind; - } - } - - getTotalUsed() { - return this.flashCount + this.flashLiteCount; - } - - getTotalAvailable() { - return 40; // 20 flash + 20 flash-lite - } - async handleScreenAnswer() { + if (this.isAnalyzing) return; if (window.captureManualScreenshot) { + this.isAnalyzing = true; + this._responseCountWhenStarted = this.responses.length; window.captureManualScreenshot(); - // Reload limits after a short delay to catch the update - setTimeout(() => this.loadLimits(), 1000); } } - handleRegionSelect() { - if (window.startRegionSelection) { - window.startRegionSelection(); - // Reload limits after a short delay to catch the update - setTimeout(() => this.loadLimits(), 1000); + _startWaveformAnimation() { + const canvas = this.shadowRoot.querySelector('.analyze-canvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + const dpr = window.devicePixelRatio || 1; + + const rect = canvas.getBoundingClientRect(); + canvas.width = rect.width * dpr; + canvas.height = rect.height * dpr; + ctx.scale(dpr, dpr); + + 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 + const PARTICLE_COUNT = 250; + + // Pill perimeter helpers + const w = rect.width; + const h = rect.height; + const r = h / 2; // pill radius = half height + const straightLen = w - 2 * r; + const arcLen = Math.PI * r; + const perimeter = 2 * straightLen + 2 * arcLen; + + // Given a distance along the perimeter, return {x, y, nx, ny} (position + inward normal) + const pointOnPerimeter = (d) => { + d = ((d % perimeter) + perimeter) % perimeter; + // Top straight: left to right + if (d < straightLen) { + return { x: r + d, y: 0, nx: 0, ny: 1 }; + } + d -= straightLen; + // Right arc + if (d < arcLen) { + const angle = -Math.PI / 2 + (d / arcLen) * Math.PI; + return { + x: w - r + Math.cos(angle) * r, + y: r + Math.sin(angle) * r, + nx: -Math.cos(angle), + ny: -Math.sin(angle), + }; + } + d -= arcLen; + // Bottom straight: right to left + if (d < straightLen) { + return { x: w - r - d, y: h, nx: 0, ny: -1 }; + } + d -= straightLen; + // Left arc + const angle = Math.PI / 2 + (d / arcLen) * Math.PI; + return { + x: r + Math.cos(angle) * r, + y: r + Math.sin(angle) * r, + nx: -Math.cos(angle), + ny: -Math.sin(angle), + }; + }; + + // 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() }); } + + const draw = (now) => { + const elapsed = (now - startTime) / 1000; + const fade = Math.min(1, elapsed / FADE_IN); + + ctx.clearRect(0, 0, w, h); + + // ── Particle border ── + ctx.fillStyle = dangerColor; + for (let i = 0; i < PARTICLE_COUNT; i++) { + const s = seeds[i]; + const along = (s.pos + s.drift * elapsed * 0.03) * perimeter; + const depth = s.depthSeed * PARTICLE_SPREAD; + const density = 1 - depth / PARTICLE_SPREAD; + + if (Math.random() > density) continue; + + const p = pointOnPerimeter(along); + const px = p.x + p.nx * depth; + const py = p.y + p.ny * depth; + const size = 0.8 + density * 0.6; + + ctx.globalAlpha = fade * density * 0.85; + ctx.beginPath(); + ctx.arc(px, py, size, 0, Math.PI * 2); + ctx.fill(); + } + + // ── Waveform ── + const midY = h / 2; + const waves = [ + { freq: 3, amp: 0.35, speed: 2.5, opacity: 0.9, width: 1.8 }, + { freq: 5, amp: 0.2, speed: 3.5, opacity: 0.5, width: 1.2 }, + { freq: 7, amp: 0.12, speed: 5, opacity: 0.3, width: 0.8 }, + ]; + + for (const wave of waves) { + ctx.beginPath(); + ctx.strokeStyle = dangerColor; + ctx.globalAlpha = wave.opacity * fade; + ctx.lineWidth = wave.width; + 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; + if (x === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + } + + ctx.globalAlpha = 1; + this._animFrame = requestAnimationFrame(draw); + }; + + this._animFrame = requestAnimationFrame(draw); } - handlePushToTalkToggle() { - if (!window.require) { - return; + _stopWaveformAnimation() { + if (this._animFrame) { + cancelAnimationFrame(this._animFrame); + this._animFrame = null; + } + const canvas = this.shadowRoot.querySelector('.analyze-canvas'); + if (canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); } - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('push-to-talk-toggle'); } scrollToBottom() { @@ -712,116 +636,74 @@ export class AssistantView extends LitElement { if (changedProperties.has('responses') || changedProperties.has('currentResponseIndex')) { this.updateResponseContent(); } + + if (changedProperties.has('isAnalyzing')) { + if (this.isAnalyzing) { + this._startWaveformAnimation(); + } else { + this._stopWaveformAnimation(); + } + } + + if (changedProperties.has('responses') && this.isAnalyzing) { + if (this.responses.length > this._responseCountWhenStarted) { + this.isAnalyzing = false; + } + } } updateResponseContent() { - console.log('updateResponseContent called'); const container = this.shadowRoot.querySelector('#responseContainer'); if (container) { const currentResponse = this.getCurrentResponse(); - console.log('Current response:', currentResponse); const renderedResponse = this.renderMarkdown(currentResponse); - console.log('Rendered response:', renderedResponse); container.innerHTML = renderedResponse; - // Show all words immediately (no animation) if (this.shouldAnimateResponse) { this.dispatchEvent(new CustomEvent('response-animation-complete', { bubbles: true, composed: true })); } - } else { - console.log('Response container not found'); } } render() { - const responseCounter = this.getResponseCounter(); - const showPushToTalk = this.aiProvider === 'openai-sdk' && this.audioInputMode === 'push-to-talk'; - const keybindLabel = this.pushToTalkKeybind || 'Hotkey'; - const pushToTalkLabel = this.pushToTalkActive - ? 'Recording...' - : `Press ${keybindLabel} to start/stop`; - const pushToTalkButtonLabel = this.pushToTalkActive ? 'Stop' : 'Record'; + const hasMultipleResponses = this.responses.length > 1; return html`
- ${showPushToTalk - ? html` -
- - Push-to-Talk: - ${pushToTalkLabel} -
- ` - : ''} - -
- - - ${this.responses.length > 0 ? html`${responseCounter}` : ''} - - - - - -
- ${showPushToTalk - ? html` - - ` - : ''} - + ${this.currentResponseIndex + 1} of ${this.responses.length} + -
- ${this.aiProvider === 'gemini' - ? html` -
-
- Flash - ${this.flashCount}/20 -
-
- Flash Lite - ${this.flashLiteCount}/20 -
-
Resets every 24 hours
-
- ` - : ''} - -
+ ` : ''} + +
+
+ +
+
`; } diff --git a/src/components/views/CustomizeView.js b/src/components/views/CustomizeView.js index ee698ae..435b7f5 100644 --- a/src/components/views/CustomizeView.js +++ b/src/components/views/CustomizeView.js @@ -1,548 +1,184 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; +import { unifiedPageStyles } from './sharedPageStyles.js'; export class CustomizeView extends LitElement { - static styles = css` - * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - cursor: default; - user-select: none; - } - - :host { - display: block; - height: 100%; - } - - .settings-layout { - display: flex; - height: 100%; - } - - /* Sidebar */ - .settings-sidebar { - width: 160px; - min-width: 160px; - border-right: 1px solid var(--border-color); - padding: 8px 0; - display: flex; - flex-direction: column; - gap: 2px; - } - - .sidebar-item { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 12px; - margin: 0 8px; - border-radius: 3px; - font-size: 12px; - color: var(--text-secondary); - cursor: pointer; - transition: all 0.1s ease; - border: none; - background: transparent; - text-align: left; - width: calc(100% - 16px); - } - - .sidebar-item:hover { - background: var(--hover-background); - color: var(--text-color); - } - - .sidebar-item.active { - background: var(--bg-tertiary); - color: var(--text-color); - } - - .sidebar-item svg { - width: 16px; - height: 16px; - flex-shrink: 0; - } - - .sidebar-item.danger { - color: var(--error-color); - } - - .sidebar-item.danger:hover, - .sidebar-item.danger.active { - color: var(--error-color); - } - - /* Main content */ - .settings-content { - flex: 1; - padding: 16px 0; - overflow-y: auto; - display: flex; - flex-direction: column; - } - - .settings-content > * { - flex-shrink: 0; - } - - .settings-content > .profile-section { - flex: 1; - min-height: 0; - } - - .settings-content::-webkit-scrollbar { - width: 8px; - } - - .settings-content::-webkit-scrollbar-track { - background: transparent; - } - - .settings-content::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 4px; - } - - .settings-content::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); - } - - .content-header { - font-size: 16px; - font-weight: 600; - color: var(--text-color); - margin-bottom: 16px; - padding: 0 16px 12px 16px; - border-bottom: 1px solid var(--border-color); - } - - .settings-section { - padding: 12px 16px; - } - - .section-title { - font-size: 11px; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 12px; - } - - .form-grid { - display: grid; - gap: 12px; - padding: 0 16px; - } - - .form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - align-items: start; - } - - @media (max-width: 600px) { - .form-row { - grid-template-columns: 1fr; + static styles = [ + unifiedPageStyles, + css` + .danger-surface { + border-color: var(--danger); } - } - .form-group { - display: flex; - flex-direction: column; - gap: 6px; - } + .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); + } - .form-group.full-width { - grid-column: 1 / -1; - } + .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); + } - .form-label { - font-weight: 500; - font-size: 12px; - color: var(--text-color); - display: flex; - align-items: center; - gap: 6px; - } + .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); + } - .form-description { - font-size: 11px; - color: var(--text-muted); - line-height: 1.4; - margin-top: 2px; - } + .toggle-input { + width: 14px; + height: 14px; + accent-color: var(--text-primary); + cursor: pointer; + } - .form-control { - background: var(--input-background); - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 8px 10px; - border-radius: 3px; - font-size: 12px; - transition: border-color 0.1s ease; - } + .toggle-label { + color: var(--text-primary); + font-size: var(--font-size-sm); + cursor: pointer; + user-select: none; + } - .form-control:focus { - outline: none; - border-color: var(--border-default); - } + .slider-wrap { + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--space-xs); + } - .form-control:hover:not(:focus) { - border-color: var(--border-default); - } + .slider-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-sm); + } - select.form-control { - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b6b6b' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); - background-position: right 8px center; - background-repeat: no-repeat; - background-size: 12px; - padding-right: 28px; - } + .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; + } - textarea.form-control { - resize: vertical; - min-height: 60px; - line-height: 1.4; - font-family: inherit; - } + .slider-input { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 4px; + border-radius: 2px; + background: var(--border); + outline: none; + cursor: pointer; + } - /* Profile section with expanding textarea */ - .profile-section { - display: flex; - flex-direction: column; - height: 100%; - } + .slider-input::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--text-primary); + border: none; + } - .profile-section .form-grid { - flex: 1; - display: flex; - flex-direction: column; - } + .slider-input::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--text-primary); + border: none; + } - .profile-section .form-group.expand { - flex: 1; - display: flex; - flex-direction: column; - } + .keybind-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-sm) 0; + border-bottom: 1px solid var(--border); + } - .profile-section .form-group.expand textarea { - flex: 1; - resize: none; - } + .keybind-row:last-of-type { + border-bottom: none; + } - textarea.form-control::placeholder { - color: var(--placeholder-color); - } + .keybind-name { + color: var(--text-secondary); + font-size: var(--font-size-sm); + } - .current-selection { - display: inline-flex; - align-items: center; - font-size: 10px; - color: var(--text-secondary); - background: var(--bg-tertiary); - padding: 2px 6px; - border-radius: 3px; - font-weight: 500; - } + .keybind-input { + width: 140px; + text-align: center; + font-family: var(--font-mono); + font-size: var(--font-size-xs); + } - .keybind-input { - cursor: pointer; - font-family: 'SF Mono', Monaco, monospace; - text-align: center; - letter-spacing: 0.5px; - font-weight: 500; - } + .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); + } - .keybind-input:focus { - cursor: text; - } + .danger-button:hover { + background: rgba(241, 76, 76, 0.11); + } - .keybind-input::placeholder { - color: var(--placeholder-color); - font-style: italic; - } + .danger-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } - .reset-keybinds-button { - background: transparent; - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 6px 10px; - border-radius: 3px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - transition: background 0.1s ease; - } + .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); + } - .reset-keybinds-button:hover { - background: var(--hover-background); - } + .status.success { + border-color: var(--success); + color: var(--success); + } - .keybinds-table { - width: 100%; - border-collapse: collapse; - margin-top: 8px; - } - - .keybinds-table th, - .keybinds-table td { - padding: 8px 0; - text-align: left; - border-bottom: 1px solid var(--border-color); - } - - .keybinds-table th { - font-weight: 600; - font-size: 11px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - } - - .keybinds-table td { - vertical-align: middle; - } - - .keybinds-table .action-name { - font-weight: 500; - color: var(--text-color); - font-size: 12px; - } - - .keybinds-table .action-description { - font-size: 10px; - color: var(--text-muted); - margin-top: 1px; - } - - .keybinds-table .keybind-input { - min-width: 100px; - padding: 4px 8px; - margin: 0; - font-size: 11px; - } - - .keybinds-table tr:hover { - background: var(--hover-background); - } - - .keybinds-table tr:last-child td { - border-bottom: none; - } - - .table-reset-row { - border-top: 1px solid var(--border-color); - } - - .table-reset-row td { - padding-top: 10px; - padding-bottom: 8px; - border-bottom: none; - } - - .table-reset-row:hover { - background: transparent; - } - - .settings-note { - font-size: 11px; - color: var(--text-muted); - text-align: center; - margin-top: 16px; - padding: 12px; - border-top: 1px solid var(--border-color); - } - - .checkbox-group { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 0; - } - - .checkbox-input { - width: 14px; - height: 14px; - accent-color: var(--text-color); - cursor: pointer; - } - - .checkbox-label { - font-weight: 500; - font-size: 12px; - color: var(--text-color); - cursor: pointer; - user-select: none; - } - - /* Slider styles */ - .slider-container { - display: flex; - flex-direction: column; - gap: 8px; - } - - .slider-header { - display: flex; - justify-content: space-between; - align-items: center; - } - - .slider-value { - font-size: 11px; - color: var(--text-secondary); - background: var(--bg-tertiary); - padding: 2px 6px; - border-radius: 3px; - font-weight: 500; - font-family: 'SF Mono', Monaco, monospace; - } - - .slider-input { - -webkit-appearance: none; - appearance: none; - width: 100%; - height: 4px; - border-radius: 2px; - background: var(--border-color); - outline: none; - cursor: pointer; - } - - .slider-input::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 14px; - height: 14px; - border-radius: 50%; - background: var(--text-color); - cursor: pointer; - border: none; - } - - .slider-input::-moz-range-thumb { - width: 14px; - height: 14px; - border-radius: 50%; - background: var(--text-color); - cursor: pointer; - border: none; - } - - .slider-labels { - display: flex; - justify-content: space-between; - margin-top: 4px; - font-size: 10px; - color: var(--text-muted); - } - - /* Color picker styles */ - .color-picker-container { - display: flex; - align-items: center; - gap: 10px; - } - - .color-picker-input { - -webkit-appearance: none; - appearance: none; - width: 40px; - height: 32px; - border: 1px solid var(--border-color); - border-radius: 3px; - cursor: pointer; - padding: 2px; - background: var(--input-background); - } - - .color-picker-input::-webkit-color-swatch-wrapper { - padding: 0; - } - - .color-picker-input::-webkit-color-swatch { - border: none; - border-radius: 2px; - } - - .color-hex-input { - width: 80px; - font-family: 'SF Mono', Monaco, monospace; - text-transform: uppercase; - } - - .reset-color-button { - background: transparent; - color: var(--text-secondary); - border: 1px solid var(--border-color); - padding: 6px 10px; - border-radius: 3px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - transition: all 0.1s ease; - } - - .reset-color-button:hover { - background: var(--hover-background); - color: var(--text-color); - } - - /* Danger button and status */ - .danger-button { - background: transparent; - color: var(--error-color); - border: 1px solid var(--error-color); - padding: 8px 14px; - border-radius: 3px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - transition: background 0.1s ease; - } - - .danger-button:hover { - background: rgba(241, 76, 76, 0.1); - } - - .danger-button:disabled { - opacity: 0.5; - cursor: not-allowed; - } - - .status-message { - margin-top: 12px; - padding: 8px 12px; - border-radius: 3px; - font-size: 11px; - font-weight: 500; - } - - .status-success { - background: var(--bg-secondary); - color: var(--success-color); - border-left: 2px solid var(--success-color); - } - - .status-error { - background: var(--bg-secondary); - color: var(--error-color); - border-left: 2px solid var(--error-color); - } - - `; + .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 }, @@ -550,13 +186,12 @@ export class CustomizeView extends LitElement { backgroundTransparency: { type: Number }, fontSize: { type: Number }, theme: { type: String }, - audioInputMode: { type: String }, onProfileChange: { type: Function }, onLanguageChange: { type: Function }, onImageQualityChange: { type: Function }, onLayoutModeChange: { type: Function }, - activeSection: { type: String }, isClearing: { type: Boolean }, + isRestoring: { type: Boolean }, clearStatusMessage: { type: String }, clearStatusType: { type: String }, }; @@ -572,307 +207,53 @@ export class CustomizeView extends LitElement { this.onLanguageChange = () => {}; this.onImageQualityChange = () => {}; this.onLayoutModeChange = () => {}; - - // Google Search default this.googleSearchEnabled = true; - - // Clear data state + this.providerMode = 'byok'; this.isClearing = false; + this.isRestoring = false; this.clearStatusMessage = ''; this.clearStatusType = ''; - - // Background transparency default this.backgroundTransparency = 0.8; - - // Font size default (in pixels) this.fontSize = 20; - - // Audio mode default this.audioMode = 'speaker_only'; - this.audioInputMode = 'auto'; - - // Custom prompt this.customPrompt = ''; - - // Active section for sidebar navigation - this.activeSection = 'profile'; - - // Theme default this.theme = 'dark'; - - // AI Provider settings - this.aiProvider = 'gemini'; - this.geminiApiKey = ''; - this.openaiApiKey = ''; - this.openaiBaseUrl = ''; - this.openaiModel = 'gpt-4o-realtime-preview-2024-12-17'; - // OpenAI SDK settings - this.openaiSdkApiKey = ''; - this.openaiSdkBaseUrl = ''; - this.openaiSdkModel = 'gpt-4o'; - this.openaiSdkVisionModel = 'gpt-4o'; - this.openaiSdkWhisperModel = 'whisper-1'; - this._loadFromStorage(); } getThemes() { - return mastermind.theme.getAll(); - } - - setActiveSection(section) { - this.activeSection = section; - this.requestUpdate(); - } - - getSidebarSections() { - return [ - { id: 'profile', name: 'Profile', icon: 'user' }, - { id: 'ai-provider', name: 'AI Provider', icon: 'cpu' }, - { id: 'appearance', name: 'Appearance', icon: 'display' }, - { id: 'audio', name: 'Audio', icon: 'mic' }, - { id: 'language', name: 'Language', icon: 'globe' }, - { id: 'capture', name: 'Capture', icon: 'camera' }, - { id: 'keyboard', name: 'Keyboard', icon: 'keyboard' }, - { id: 'search', name: 'Search', icon: 'search' }, - { id: 'advanced', name: 'Advanced', icon: 'warning', danger: true }, - ]; - } - - renderSidebarIcon(icon) { - const icons = { - user: html` - - - `, - mic: html` - - - - - `, - globe: html` - - - - `, - display: html` - - - - `, - camera: html` - - - `, - keyboard: html` - - - - - - - - - - `, - search: html` - - - `, - cpu: html` - - - - - - - - - - - `, - warning: html` - - - - `, - }; - return icons[icon] || ''; + return cheatingDaddy.theme.getAll(); } async _loadFromStorage() { try { - const [prefs, keybinds, credentials, openaiCreds, openaiSdkCreds] = await Promise.all([ - mastermind.storage.getPreferences(), - mastermind.storage.getKeybinds(), - mastermind.storage.getCredentials(), - mastermind.storage.getOpenAICredentials(), - mastermind.storage.getOpenAISDKCredentials(), - ]); - + 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.audioInputMode = prefs.audioInputMode ?? 'auto'; this.customPrompt = prefs.customPrompt ?? ''; this.theme = prefs.theme ?? 'dark'; - this.aiProvider = prefs.aiProvider ?? 'gemini'; - - // Load Gemini API key - this.geminiApiKey = credentials.apiKey ?? ''; - - // Load OpenAI Realtime credentials - this.openaiApiKey = openaiCreds.apiKey ?? ''; - this.openaiBaseUrl = openaiCreds.baseUrl ?? ''; - this.openaiModel = openaiCreds.model ?? 'gpt-4o-realtime-preview-2024-12-17'; - - // Load OpenAI SDK credentials - this.openaiSdkApiKey = openaiSdkCreds.apiKey ?? ''; - this.openaiSdkBaseUrl = openaiSdkCreds.baseUrl ?? ''; - this.openaiSdkModel = openaiSdkCreds.model ?? 'gpt-4o'; - this.openaiSdkVisionModel = openaiSdkCreds.visionModel ?? 'gpt-4o'; - this.openaiSdkWhisperModel = openaiSdkCreds.whisperModel ?? 'whisper-1'; - if (keybinds) { this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds }; } - - this.updateBackgroundTransparency(); + this.updateBackgroundAppearance(); this.updateFontSize(); - this.notifyPushToTalkSettings(); this.requestUpdate(); } catch (error) { console.error('Error loading settings:', error); } } - connectedCallback() { - super.connectedCallback(); - // Resize window for this view - resizeLayout(); - } - - disconnectedCallback() { - super.disconnectedCallback(); - } - getProfiles() { return [ - { - value: 'interview', - name: 'Job Interview', - description: 'Get help with answering interview questions', - }, - { - value: 'sales', - name: 'Sales Call', - description: 'Assist with sales conversations and objection handling', - }, - { - value: 'meeting', - name: 'Business Meeting', - description: 'Support for professional meetings and discussions', - }, - { - value: 'presentation', - name: 'Presentation', - description: 'Help with presentations and public speaking', - }, - { - value: 'negotiation', - name: 'Negotiation', - description: 'Guidance for business negotiations and deals', - }, - { - value: 'exam', - name: 'Exam Assistant', - description: 'Academic assistance for test-taking and exam questions', - }, + { 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' }, ]; } @@ -883,7 +264,7 @@ export class CustomizeView extends LitElement { { value: 'en-AU', name: 'English (Australia)' }, { value: 'en-IN', name: 'English (India)' }, { value: 'de-DE', name: 'German (Germany)' }, - { value: 'es-US', name: 'Spanish (United States)' }, + { value: 'es-US', name: 'Spanish (US)' }, { value: 'es-ES', name: 'Spanish (Spain)' }, { value: 'fr-FR', name: 'French (France)' }, { value: 'fr-CA', name: 'French (Canada)' }, @@ -911,17 +292,47 @@ export class CustomizeView extends LitElement { ]; } - getProfileNames() { + getDefaultKeybinds() { + const isMac = cheatingDaddy.isMacOS || navigator.platform.includes('Mac'); return { - interview: 'Job Interview', - sales: 'Sales Call', - meeting: 'Business Meeting', - presentation: 'Presentation', - negotiation: 'Negotiation', - exam: 'Exam Assistant', + 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', }; } + 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' }, + ]; + } + + 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); @@ -934,79 +345,73 @@ export class CustomizeView extends LitElement { handleImageQualitySelect(e) { this.selectedImageQuality = e.target.value; - this.onImageQualityChange(e.target.value); + this.onImageQualityChange(this.selectedImageQuality); } handleLayoutModeSelect(e) { this.layoutMode = e.target.value; - this.onLayoutModeChange(e.target.value); + this.onLayoutModeChange(this.layoutMode); } async handleCustomPromptInput(e) { this.customPrompt = e.target.value; - await mastermind.storage.updatePreference('customPrompt', e.target.value); + await cheatingDaddy.storage.updatePreference('customPrompt', this.customPrompt); } async handleAudioModeSelect(e) { this.audioMode = e.target.value; - await mastermind.storage.updatePreference('audioMode', e.target.value); + await cheatingDaddy.storage.updatePreference('audioMode', this.audioMode); this.requestUpdate(); } - async handleAudioInputModeChange(e) { - this.audioInputMode = e.target.value; - await mastermind.storage.updatePreference('audioInputMode', e.target.value); - this.notifyPushToTalkSettings(); + async handleProviderModeChange(e) { + this.providerMode = e.target.value; + await cheatingDaddy.storage.updatePreference('providerMode', this.providerMode); this.requestUpdate(); } - notifyPushToTalkSettings() { - if (!window.require) { - return; - } - try { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('update-push-to-talk-settings', { - inputMode: this.audioInputMode, - }); - ipcRenderer.send('update-keybinds', this.keybinds); - } catch (error) { - console.error('Failed to notify push-to-talk settings:', error); - } - } - async handleThemeChange(e) { this.theme = e.target.value; - await mastermind.theme.save(this.theme); + await cheatingDaddy.theme.save(this.theme); this.updateBackgroundAppearance(); this.requestUpdate(); } - getDefaultKeybinds() { - const isMac = mastermind.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', - pushToTalk: isMac ? 'Ctrl+Space' : 'Ctrl+Space', - }; + 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 saveKeybinds() { - await mastermind.storage.setKeybinds(this.keybinds); - // Send to main process to update global shortcuts - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('update-keybinds', this.keybinds); - } + 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) { @@ -1015,81 +420,6 @@ export class CustomizeView extends LitElement { this.requestUpdate(); } - async resetKeybinds() { - this.keybinds = this.getDefaultKeybinds(); - await mastermind.storage.setKeybinds(null); - this.requestUpdate(); - if (window.require) { - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('update-keybinds', this.keybinds); - } - } - - getKeybindActions() { - return [ - { - key: 'moveUp', - name: 'Move Window Up', - description: 'Move the application window up', - }, - { - key: 'moveDown', - name: 'Move Window Down', - description: 'Move the application window down', - }, - { - key: 'moveLeft', - name: 'Move Window Left', - description: 'Move the application window left', - }, - { - key: 'moveRight', - name: 'Move Window Right', - description: 'Move the application window right', - }, - { - key: 'toggleVisibility', - name: 'Toggle Window Visibility', - description: 'Show/hide the application window', - }, - { - key: 'toggleClickThrough', - name: 'Toggle Click-through Mode', - description: 'Enable/disable click-through functionality', - }, - { - key: 'nextStep', - name: 'Ask Next Step', - description: 'Take screenshot and ask AI for the next step suggestion', - }, - { - key: 'previousResponse', - name: 'Previous Response', - description: 'Navigate to the previous AI response', - }, - { - key: 'nextResponse', - name: 'Next Response', - description: 'Navigate to the next AI response', - }, - { - key: 'scrollUp', - name: 'Scroll Response Up', - description: 'Scroll the AI response content up', - }, - { - key: 'scrollDown', - name: 'Scroll Response Down', - description: 'Scroll the AI response content down', - }, - { - key: 'pushToTalk', - name: 'Push-to-Talk', - description: 'Activate audio recording (OpenAI SDK only)', - }, - ]; - } - handleKeybindFocus(e) { e.target.placeholder = 'Press key combination...'; e.target.select(); @@ -1097,20 +427,13 @@ export class CustomizeView extends LitElement { handleKeybindInput(e) { e.preventDefault(); - const modifiers = []; - const keys = []; - - // Check 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'); - - // Get the main key let mainKey = e.key; - // Handle special keys switch (e.code) { case 'ArrowUp': mainKey = 'Up'; @@ -1133,139 +456,106 @@ export class CustomizeView extends LitElement { case 'Backslash': mainKey = '\\'; break; - case 'KeyS': - if (e.shiftKey) mainKey = 'S'; - break; - case 'KeyM': - mainKey = 'M'; - break; default: - if (e.key.length === 1) { - mainKey = e.key.toUpperCase(); - } + if (e.key.length === 1) mainKey = e.key.toUpperCase(); break; } - // Skip if only modifier keys are pressed - if (['Control', 'Meta', 'Alt', 'Shift'].includes(e.key)) { - return; - } + if (['Control', 'Meta', 'Alt', 'Shift'].includes(e.key)) return; - // Construct keybind string - const keybind = [...modifiers, mainKey].join('+'); - - // Get the action from the input's data attribute const action = e.target.dataset.action; - - // Update the keybind + const keybind = [...modifiers, mainKey].join('+'); this.handleKeybindChange(action, keybind); - - // Update the input value e.target.value = keybind; e.target.blur(); } - async handleGoogleSearchChange(e) { - this.googleSearchEnabled = e.target.checked; - await mastermind.storage.updatePreference('googleSearchEnabled', this.googleSearchEnabled); - - // Notify main process if available + async resetKeybinds() { + this.keybinds = this.getDefaultKeybinds(); + await cheatingDaddy.storage.setKeybinds(null); 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); - } + const { ipcRenderer } = window.require('electron'); + ipcRenderer.send('update-keybinds', this.keybinds); } - this.requestUpdate(); } - async handleAIProviderChange(e) { - this.aiProvider = e.target.value; - await mastermind.storage.updatePreference('aiProvider', e.target.value); + 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); + } - async handleGeminiApiKeyInput(e) { - this.geminiApiKey = e.target.value; - await mastermind.storage.setApiKey(e.target.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); + } - async handleOpenAIApiKeyInput(e) { - this.openaiApiKey = e.target.value; - await mastermind.storage.setOpenAICredentials({ - apiKey: e.target.value, - }); - } + // 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; - async handleOpenAIBaseUrlInput(e) { - this.openaiBaseUrl = e.target.value; - await mastermind.storage.setOpenAICredentials({ - baseUrl: e.target.value, - }); - } + // Notify parent callbacks + this.onProfileChange(defaults.selectedProfile); + this.onLanguageChange(defaults.selectedLanguage); + this.onImageQualityChange(defaults.selectedImageQuality); - async handleOpenAIModelInput(e) { - this.openaiModel = e.target.value; - await mastermind.storage.setOpenAICredentials({ - model: e.target.value, - }); - } + // Apply visual changes + this.updateBackgroundAppearance(); + this.updateFontSize(); + await cheatingDaddy.theme.save(defaults.theme); - // OpenAI SDK handlers - async handleOpenAISdkApiKeyInput(e) { - this.openaiSdkApiKey = e.target.value; - await mastermind.storage.setOpenAISDKCredentials({ - apiKey: e.target.value, - }); - } - - async handleOpenAISdkBaseUrlInput(e) { - this.openaiSdkBaseUrl = e.target.value; - await mastermind.storage.setOpenAISDKCredentials({ - baseUrl: e.target.value, - }); - } - - async handleOpenAISdkModelInput(e) { - this.openaiSdkModel = e.target.value; - await mastermind.storage.setOpenAISDKCredentials({ - model: e.target.value, - }); - } - - async handleOpenAISdkVisionModelInput(e) { - this.openaiSdkVisionModel = e.target.value; - await mastermind.storage.setOpenAISDKCredentials({ - visionModel: e.target.value, - }); - } - - async handleOpenAISdkWhisperModelInput(e) { - this.openaiSdkWhisperModel = e.target.value; - await mastermind.storage.setOpenAISDKCredentials({ - whisperModel: e.target.value, - }); + 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 mastermind.storage.clearAll(); - + await cheatingDaddy.storage.clearAll(); this.clearStatusMessage = 'Successfully cleared all local data'; this.clearStatusType = 'success'; this.requestUpdate(); - - // Close the application after a short delay setTimeout(() => { this.clearStatusMessage = 'Closing application...'; this.requestUpdate(); @@ -1286,541 +576,170 @@ export class CustomizeView extends LitElement { } } - async handleBackgroundTransparencyChange(e) { - this.backgroundTransparency = parseFloat(e.target.value); - await mastermind.storage.updatePreference('backgroundTransparency', this.backgroundTransparency); - this.updateBackgroundAppearance(); - this.requestUpdate(); - } - - updateBackgroundAppearance() { - // Use theme's background color - const colors = mastermind.theme.get(this.theme); - mastermind.theme.applyBackgrounds(colors.background, this.backgroundTransparency); - } - - // Keep old function name for backwards compatibility - updateBackgroundTransparency() { - this.updateBackgroundAppearance(); - } - - async handleFontSizeChange(e) { - this.fontSize = parseInt(e.target.value, 10); - await mastermind.storage.updatePreference('fontSize', this.fontSize); - this.updateFontSize(); - this.requestUpdate(); - } - - updateFontSize() { - const root = document.documentElement; - root.style.setProperty('--response-font-size', `${this.fontSize}px`); - } - - renderProfileSection() { - const profiles = this.getProfiles(); - const profileNames = this.getProfileNames(); - const currentProfile = profiles.find(p => p.value === this.selectedProfile); - + renderAISection() { return html` -
-
AI Profile
+
+
AI Engine
- - + +
- -
- - -
Personalize the AI's behavior with specific instructions
-
-
+ `; } renderAudioSection() { - const isPushToTalkAvailable = this.aiProvider === 'openai-sdk'; - const pushToTalkDisabled = !isPushToTalkAvailable; - return html` -
Audio Settings
-
-
- - -
Choose which audio sources to capture for the AI.
-
-
- - -
- ${pushToTalkDisabled - ? 'Push-to-Talk is available only with the OpenAI SDK provider.' - : this.audioInputMode === 'auto' - ? 'Audio is continuously recorded and transcribed when silence is detected.' - : 'Audio recording starts when you press and hold/toggle the hotkey.'} +
+
Audio Input
+
+
+ + +
+ ${this.audioMode !== 'speaker_only' ? html` +
May cause unexpected behavior. Only change this if you know what you're doing.
+ ` : ''} +
+ +
- ${this.audioInputMode === 'push-to-talk' - ? html`
Use the Push-to-Talk hotkey (toggle) to start/stop recording.
` - : ''} -
+ `; } renderLanguageSection() { - const languages = this.getLanguages(); - const currentLanguage = languages.find(l => l.value === this.selectedLanguage); - return html` -
Language
-
-
- - -
Language for speech recognition and AI responses
+
+
Language
+
+
+ + +
-
+ `; } renderAppearanceSection() { - const themes = this.getThemes(); - const currentTheme = themes.find(t => t.value === this.theme); - return html` -
Appearance
-
-
- - -
Choose a color theme for the interface
-
- -
- - -
- ${this.layoutMode === 'compact' ? 'Smaller window with reduced padding' : 'Standard layout with comfortable spacing'} +
+
Appearance
+
+
+ +
-
- -
-
+
${Math.round(this.backgroundTransparency * 100)}%
-
- Transparent - Opaque -
-
- -
-
+
${this.fontSize}px
-
- 12px - 32px -
-
- `; - } - - renderCaptureSection() { - return html` -
Screen Capture
-
-
- - -
- ${this.selectedImageQuality === 'high' - ? 'Best quality, uses more tokens' - : this.selectedImageQuality === 'medium' - ? 'Balanced quality and token usage' - : 'Lower quality, uses fewer tokens'} -
-
-
+
`; } renderKeyboardSection() { return html` -
Keyboard Shortcuts
-
- - - - - - - - - ${this.getKeybindActions().map( - action => html` - - - - - ` - )} - - - - -
ActionShortcut
-
${action.name}
-
${action.description}
-
- -
- -
-
- `; - } - - renderAIProviderSection() { - const providerNames = { - gemini: 'Google Gemini', - 'openai-realtime': 'OpenAI Realtime', - 'openai-sdk': 'OpenAI SDK (BotHub, etc.)', - }; - - return html` -
AI Provider
-
-
- - -
Choose which AI provider to use for conversations and screen analysis
-
- - ${this.aiProvider === 'gemini' - ? html` -
- - -
- Get your API key from - Google AI Studio -
-
- ` - : this.aiProvider === 'openai-realtime' - ? html` -
- - -
- Get your API key from - OpenAI Platform -
-
- -
- - -
Override the base URL for OpenAI-compatible APIs
-
- -
- - -
Realtime API model to use
-
- ` - : html` -
- - -
API key for your provider (BotHub, Azure, OpenRouter, etc.)
-
- -
- - -
API endpoint URL (e.g., https://bothub.chat/api/v2/openai/v1)
-
- -
-
- - -
-
- - -
-
- -
- - -
Model for audio transcription
-
- `} - -
- Note: You must restart the AI session for provider changes to take effect. -
-
- `; - } - - renderSearchSection() { - return html` -
Search
-
-
- - -
-
- Allow the AI to search Google for up-to-date information during conversations. -
Note: Changes take effect when starting a new AI session. -
-
- `; - } - - renderAdvancedSection() { - return html` -
Advanced
-
-
- -
- Warning: This action will permanently delete all local data including API keys, preferences, and session - history. This cannot be undone. +
+
Keyboard Shortcuts
+ ${this.getKeybindActions().map(action => html` +
+ ${action.name} +
- - ${this.clearStatusMessage - ? html` -
- ${this.clearStatusMessage} -
- ` - : ''} + `)} +
+
-
+ `; } - renderSectionContent() { - switch (this.activeSection) { - case 'profile': - return this.renderProfileSection(); - case 'ai-provider': - return this.renderAIProviderSection(); - case 'appearance': - return this.renderAppearanceSection(); - case 'audio': - return this.renderAudioSection(); - case 'language': - return this.renderLanguageSection(); - case 'capture': - return this.renderCaptureSection(); - case 'keyboard': - return this.renderKeyboardSection(); - case 'search': - return this.renderSearchSection(); - case 'advanced': - return this.renderAdvancedSection(); - default: - return this.renderProfileSection(); - } + renderPrivacySection() { + return html` +
+
Privacy and Data
+
+ + +
+ ${this.clearStatusMessage ? html` +
${this.clearStatusMessage}
+ ` : ''} +
+ `; } render() { - const sections = this.getSidebarSections(); - return html` -
- -
${this.renderSectionContent()}
+
+
+
Settings
+ ${this.renderAISection()} + ${this.renderAudioSection()} + ${this.renderLanguageSection()} + ${this.renderAppearanceSection()} + ${this.renderKeyboardSection()} + ${this.renderPrivacySection()} +
`; } diff --git a/src/components/views/FeedbackView.js b/src/components/views/FeedbackView.js new file mode 100644 index 0000000..32414a5 --- /dev/null +++ b/src/components/views/FeedbackView.js @@ -0,0 +1,237 @@ +import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; +import { unifiedPageStyles } from './sharedPageStyles.js'; + +export class FeedbackView extends LitElement { + static styles = [ + unifiedPageStyles, + css` + .feedback-form { + display: flex; + flex-direction: column; + gap: var(--space-sm); + } + + .feedback-input { + width: 100%; + padding: var(--space-sm) var(--space-md); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-elevated); + color: var(--text-primary); + font-size: var(--font-size-sm); + font-family: var(--font); + } + + .feedback-input:focus { + outline: none; + border-color: var(--accent); + } + + .feedback-input::placeholder { + color: var(--text-muted); + } + + textarea.feedback-input { + min-height: 140px; + resize: vertical; + line-height: 1.45; + } + + input.feedback-input { + max-width: 260px; + } + + .feedback-row { + display: flex; + align-items: center; + gap: var(--space-sm); + } + + .feedback-submit { + padding: var(--space-sm) var(--space-md); + border: none; + border-radius: var(--radius-sm); + background: var(--accent); + color: var(--btn-primary-text, #fff); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + transition: opacity var(--transition); + white-space: nowrap; + } + + .feedback-submit:hover { + opacity: 0.85; + } + + .feedback-submit:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .feedback-status { + font-size: var(--font-size-xs); + color: var(--text-muted); + } + + .feedback-status.success { + color: var(--success); + } + + .feedback-status.error { + color: var(--danger); + } + + .attach-info { + display: flex; + align-items: center; + gap: var(--space-xs); + font-size: var(--font-size-xs); + color: var(--text-muted); + cursor: pointer; + user-select: none; + } + + .attach-info input[type="checkbox"] { + cursor: pointer; + accent-color: var(--accent); + } + `, + ]; + + static properties = { + _feedbackText: { state: true }, + _feedbackEmail: { state: true }, + _feedbackStatus: { state: true }, + _feedbackSending: { state: true }, + _attachInfo: { state: true }, + _version: { state: true }, + }; + + constructor() { + super(); + this._feedbackText = ''; + this._feedbackEmail = ''; + this._feedbackStatus = ''; + this._feedbackSending = false; + this._attachInfo = true; + this._version = ''; + this._loadVersion(); + } + + async _loadVersion() { + try { + this._version = await cheatingDaddy.getVersion(); + this.requestUpdate(); + } catch (e) {} + } + + _getOS() { + const p = navigator.platform || ''; + if (p.includes('Mac')) return 'macOS'; + if (p.includes('Win')) return 'Windows'; + if (p.includes('Linux')) return 'Linux'; + return p; + } + + async _submitFeedback() { + const text = this._feedbackText.trim(); + if (!text || this._feedbackSending) return; + + let content = text; + if (this._attachInfo) { + content += `\n\nsent from ${this._getOS()} version ${this._version}`; + } + + if (content.length > 2000) { + this._feedbackStatus = 'error:Max 2000 characters'; + this.requestUpdate(); + return; + } + + this._feedbackSending = true; + this._feedbackStatus = ''; + this.requestUpdate(); + + try { + const body = { feedback: content }; + if (this._feedbackEmail.trim()) { + body.email = this._feedbackEmail.trim(); + } + + const res = await fetch('https://api.cheatingdaddy.com/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (res.ok) { + this._feedbackText = ''; + this._feedbackEmail = ''; + this._feedbackStatus = 'success:Feedback sent, thank you!'; + } else if (res.status === 429) { + this._feedbackStatus = 'error:Please wait a few minutes before sending again'; + } else { + this._feedbackStatus = 'error:Failed to send feedback'; + } + } catch (e) { + this._feedbackStatus = 'error:Could not connect to server'; + } + + this._feedbackSending = false; + this.requestUpdate(); + } + + render() { + return html` +
+
+
Feedback
+ +
+ +
+
+
+ `; + } +} + +customElements.define('feedback-view', FeedbackView); diff --git a/src/components/views/HelpView.js b/src/components/views/HelpView.js index 401c50d..8e7eff9 100644 --- a/src/components/views/HelpView.js +++ b/src/components/views/HelpView.js @@ -1,233 +1,95 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; +import { unifiedPageStyles } from './sharedPageStyles.js'; export class HelpView extends LitElement { - static styles = css` - * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - cursor: default; - user-select: none; - } + static styles = [ + unifiedPageStyles, + css` + .shortcut-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--space-sm); + } - :host { - display: block; - padding: 0; - } + .shortcut-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-sm); + padding: var(--space-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-elevated); + } - .help-container { - display: flex; - flex-direction: column; - } + .shortcut-label { + color: var(--text-secondary); + font-size: var(--font-size-xs); + } - .option-group { - padding: 16px 12px; - border-bottom: 1px solid var(--border-color); - } + .shortcut-keys { + display: inline-flex; + gap: 4px; + flex-wrap: wrap; + justify-content: flex-end; + } - .option-group:last-child { - border-bottom: none; - } + .key { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 2px 6px; + font-size: var(--font-size-xs); + color: var(--text-primary); + background: var(--bg-surface); + font-family: var(--font-mono); + } - .option-label { - font-size: 11px; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 12px; - } + .list { + display: grid; + gap: var(--space-sm); + } - .description { - color: var(--text-secondary); - font-size: 12px; - line-height: 1.4; - user-select: text; - cursor: text; - } + .list-item { + padding: var(--space-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: var(--font-size-sm); + line-height: 1.45; + background: var(--bg-elevated); + } - .description strong { - color: var(--text-color); - font-weight: 500; - } + .link-row { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + } - .link { - color: var(--text-color); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; - } + .link-button { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 8px 10px; + background: var(--bg-elevated); + color: var(--text-primary); + font-size: var(--font-size-sm); + cursor: pointer; + transition: border-color var(--transition), color var(--transition), background var(--transition); + } - .key { - background: var(--bg-tertiary); - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 2px 6px; - border-radius: 3px; - font-size: 10px; - font-family: 'SF Mono', Monaco, monospace; - font-weight: 500; - margin: 0 1px; - white-space: nowrap; - } + .link-button:hover { + color: var(--text-primary); + border-color: var(--accent); + background: rgba(63, 125, 229, 0.14); + } - .keyboard-section { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 12px; - margin-top: 8px; - } + @media (max-width: 820px) { + .shortcut-grid { + grid-template-columns: 1fr; + } + } - .keyboard-group { - padding: 10px 0; - border-bottom: 1px solid var(--border-color); - } - - .keyboard-group:last-child { - border-bottom: none; - } - - .keyboard-group-title { - font-weight: 600; - font-size: 12px; - color: var(--text-color); - margin-bottom: 8px; - } - - .shortcut-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 0; - font-size: 11px; - } - - .shortcut-description { - color: var(--text-secondary); - } - - .shortcut-keys { - display: flex; - gap: 2px; - } - - .profiles-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 8px; - margin-top: 8px; - } - - .profile-item { - padding: 8px 0; - border-bottom: 1px solid var(--border-color); - } - - .profile-item:last-child { - border-bottom: none; - } - - .profile-name { - font-weight: 500; - font-size: 12px; - color: var(--text-color); - margin-bottom: 2px; - } - - .profile-description { - font-size: 11px; - color: var(--text-muted); - line-height: 1.3; - } - - .community-links { - display: flex; - gap: 8px; - flex-wrap: wrap; - } - - .community-link { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 10px; - background: transparent; - border: 1px solid var(--border-color); - border-radius: 3px; - color: var(--text-color); - font-size: 11px; - font-weight: 500; - transition: background 0.1s ease; - cursor: pointer; - } - - .community-link:hover { - background: var(--hover-background); - } - - .community-link svg { - width: 14px; - height: 14px; - flex-shrink: 0; - } - - .open-logs-btn { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 8px 14px; - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-color); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s ease; - } - - .open-logs-btn:hover { - background: var(--hover-background); - } - - .usage-steps { - counter-reset: step-counter; - } - - .usage-step { - counter-increment: step-counter; - position: relative; - padding-left: 24px; - margin-bottom: 8px; - font-size: 11px; - line-height: 1.4; - color: var(--text-secondary); - } - - .usage-step::before { - content: counter(step-counter); - position: absolute; - left: 0; - top: 0; - width: 16px; - height: 16px; - background: var(--bg-tertiary); - color: var(--text-color); - border-radius: 3px; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: 600; - } - - .usage-step strong { - color: var(--text-color); - } - `; + `, + ]; static properties = { onExternalLinkClick: { type: Function }, @@ -243,7 +105,7 @@ export class HelpView extends LitElement { async _loadKeybinds() { try { - const keybinds = await mastermind.storage.getKeybinds(); + const keybinds = await cheatingDaddy.storage.getKeybinds(); if (keybinds) { this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds }; this.requestUpdate(); @@ -253,14 +115,8 @@ export class HelpView extends LitElement { } } - connectedCallback() { - super.connectedCallback(); - // Resize window for this view - resizeLayout(); - } - getDefaultKeybinds() { - const isMac = mastermind.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', @@ -276,253 +132,58 @@ export class HelpView extends LitElement { }; } - formatKeybind(keybind) { + _formatKeybind(keybind) { return keybind.split('+').map(key => html`${key}`); } - handleExternalLinkClick(url) { + _open(url) { this.onExternalLinkClick(url); } render() { - const isMacOS = mastermind.isMacOS || false; - const isLinux = mastermind.isLinux || false; + const shortcutRows = [ + ['Move Window Up', this.keybinds.moveUp], + ['Move Window Down', this.keybinds.moveDown], + ['Move Window Left', this.keybinds.moveLeft], + ['Move Window Right', this.keybinds.moveRight], + ['Toggle Visibility', this.keybinds.toggleVisibility], + ['Toggle Click-through', this.keybinds.toggleClickThrough], + ['Ask Next Step', this.keybinds.nextStep], + ['Previous Response', this.keybinds.previousResponse], + ['Next Response', this.keybinds.nextResponse], + ['Scroll Response Up', this.keybinds.scrollUp], + ['Scroll Response Down', this.keybinds.scrollDown], + ]; return html` -
-
-
- Community & Support -
- -
+
+
+
Help
-
-
- Keyboard Shortcuts -
-
-
-
Window Movement
-
- Move window up -
${this.formatKeybind(this.keybinds.moveUp)}
-
-
- Move window down -
${this.formatKeybind(this.keybinds.moveDown)}
-
-
- Move window left -
${this.formatKeybind(this.keybinds.moveLeft)}
-
-
- Move window right -
${this.formatKeybind(this.keybinds.moveRight)}
-
+
+
Support
+ +
-
-
Window Control
-
- Toggle click-through mode -
${this.formatKeybind(this.keybinds.toggleClickThrough)}
-
-
- Toggle window visibility -
${this.formatKeybind(this.keybinds.toggleVisibility)}
-
+
+
Keyboard Shortcuts
+
+ ${shortcutRows.map(([label, keys]) => html` +
+ ${label} + ${this._formatKeybind(keys)} +
+ `)}
- -
-
AI Actions
-
- Take screenshot and ask for next step -
${this.formatKeybind(this.keybinds.nextStep)}
-
-
- -
-
Response Navigation
-
- Previous response -
${this.formatKeybind(this.keybinds.previousResponse)}
-
-
- Next response -
${this.formatKeybind(this.keybinds.nextResponse)}
-
-
- Scroll response up -
${this.formatKeybind(this.keybinds.scrollUp)}
-
-
- Scroll response down -
${this.formatKeybind(this.keybinds.scrollDown)}
-
-
- -
-
Text Input
-
- Send message to AI -
Enter
-
-
- New line in text input -
ShiftEnter
-
-
-
-
You can customize these shortcuts in Settings.
-
- -
-
- How to Use -
-
-
Start a Session: Enter your AI Provider API key and click "Start Session"
-
Customize: Choose your profile and language in the settings
-
- Position Window: Use keyboard shortcuts to move the window to your desired location -
-
- Click-through Mode: Use ${this.formatKeybind(this.keybinds.toggleClickThrough)} to make the window - click-through -
-
Get AI Help: The AI will analyze your screen and audio to provide assistance
-
Text Messages: Type questions or requests to the AI using the text input
-
- Navigate Responses: Use ${this.formatKeybind(this.keybinds.previousResponse)} and - ${this.formatKeybind(this.keybinds.nextResponse)} to browse through AI responses -
-
-
- -
-
- Supported Profiles -
-
-
-
Job Interview
-
Get help with interview questions and responses
-
-
-
Sales Call
-
Assistance with sales conversations and objection handling
-
-
-
Business Meeting
-
Support for professional meetings and discussions
-
-
-
Presentation
-
Help with presentations and public speaking
-
-
-
Negotiation
-
Guidance for business negotiations and deals
-
-
-
Exam Assistant
-
Academic assistance for test-taking and exam questions
-
-
-
- -
-
- Audio Input -
-
The AI listens to conversations and provides contextual assistance based on what it hears.
-
- -
-
- Troubleshooting -
-
- If you're experiencing issues with audio capture or other features, check the application logs for diagnostic information. -
- +
`; } - - async openLogsFolder() { - try { - const { ipcRenderer } = require('electron'); - const result = await ipcRenderer.invoke('open-logs-folder'); - if (!result.success) { - console.error('Failed to open logs folder:', result.error); - } - } catch (err) { - console.error('Error opening logs folder:', err); - } - } } customElements.define('help-view', HelpView); diff --git a/src/components/views/HistoryView.js b/src/components/views/HistoryView.js index 431f396..d2b2dad 100644 --- a/src/components/views/HistoryView.js +++ b/src/components/views/HistoryView.js @@ -1,388 +1,289 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; +import { unifiedPageStyles } from './sharedPageStyles.js'; export class HistoryView extends LitElement { - static styles = css` - * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - cursor: default; - user-select: none; - } + static styles = [ + unifiedPageStyles, + css` + .unified-page { + overflow-y: hidden; + } - :host { - height: 100%; - display: flex; - flex-direction: column; - width: 100%; - } + .unified-wrap { + height: 100%; + } - .history-container { - height: 100%; - display: flex; - flex-direction: column; - } + .search-wrap { + position: relative; + max-width: 280px; + } - .sessions-list { - flex: 1; - overflow-y: auto; - } + .search-icon { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + width: 14px; + height: 14px; + color: var(--text-muted); + pointer-events: none; + } - .session-item { - padding: 12px; - border-bottom: 1px solid var(--border-color); - cursor: pointer; - transition: background 0.1s ease; - } + .search-wrap .control { + padding-left: 30px; + } - .session-item:hover { - background: var(--hover-background); - } + .list-shell { + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--bg-surface); + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } - .session-item.selected { - background: var(--bg-secondary); - } + .sessions-list { + overflow-y: auto; + flex: 1; + } - .session-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 4px; - } + .session-card { + width: 100%; + border: none; + border-bottom: 1px solid var(--border); + background: transparent; + text-align: left; + padding: var(--space-sm) var(--space-md); + cursor: pointer; + transition: background var(--transition); + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-sm); + } - .session-date { - font-size: 12px; - font-weight: 500; - color: var(--text-color); - } + .session-card:hover { + background: var(--bg-hover); + } - .session-time { - font-size: 11px; - color: var(--text-muted); - font-family: 'SF Mono', Monaco, monospace; - } + .session-left { + display: flex; + flex-direction: column; + gap: 2px; + } - .session-preview { - font-size: 11px; - color: var(--text-muted); - line-height: 1.3; - } + .session-profile { + color: var(--text-primary); + font-size: var(--font-size-sm); + } - .conversation-view { - flex: 1; - overflow-y: auto; - background: var(--bg-primary); - padding: 12px 0; - user-select: text; - cursor: text; - } + .session-date { + color: var(--text-muted); + font-size: var(--font-size-xs); + } - .message { - margin-bottom: 8px; - padding: 8px 12px; - border-left: 2px solid transparent; - font-size: 12px; - line-height: 1.4; - background: var(--bg-secondary); - user-select: text; - cursor: text; - white-space: pre-wrap; - word-wrap: break-word; - } + .session-badge { + color: var(--text-secondary); + font-size: var(--font-size-xs); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 2px 8px; + white-space: nowrap; + } - .message.user { - border-left-color: #3b82f6; - } + .detail-top { + display: flex; + align-items: center; + gap: var(--space-sm); + } - .message.ai { - border-left-color: #ef4444; - } + .back-btn { + border: none; + background: none; + color: var(--text-muted); + padding: 0; + font-size: var(--font-size-sm); + cursor: pointer; + display: flex; + align-items: center; + } - .back-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - padding: 12px 12px 12px 12px; - border-bottom: 1px solid var(--border-color); - } + .back-btn svg { + cursor: pointer; + } - .back-button { - background: transparent; - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 6px 12px; - border-radius: 3px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - gap: 6px; - transition: background 0.1s ease; - } + .back-btn:hover { + color: var(--text-primary); + } - .back-button:hover { - background: var(--hover-background); - } + .detail-info { + color: var(--text-secondary); + font-size: var(--font-size-sm); + } - .legend { - display: flex; - gap: 12px; - align-items: center; - } + .tab-row { + display: flex; + gap: 6px; + } - .legend-item { - display: flex; - align-items: center; - gap: 4px; - font-size: 10px; - color: var(--text-muted); - } + .tab-btn { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: transparent; + color: var(--text-muted); + padding: 6px 10px; + cursor: pointer; + font-size: var(--font-size-xs); + } - .legend-dot { - width: 8px; - height: 2px; - } + .tab-btn:hover { + color: var(--text-secondary); + } - .legend-dot.user { - background-color: #3b82f6; - } + .tab-btn.active { + color: var(--text-primary); + border-color: var(--text-secondary); + } - .legend-dot.ai { - background-color: #ef4444; - } + .details-scroll { + overflow-y: auto; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + gap: var(--space-sm); + padding: var(--space-sm) 0; + } - .legend-dot.screen { - background-color: #22c55e; - } + .message-row { + display: flex; + } - .session-context { - padding: 8px 12px; - margin-bottom: 8px; - background: var(--bg-tertiary); - border-radius: 4px; - font-size: 11px; - } + .message-row.user { + justify-content: flex-end; + } - .session-context-row { - display: flex; - gap: 8px; - margin-bottom: 4px; - } + .message-row.ai, + .message-row.screen { + justify-content: flex-start; + } - .session-context-row:last-child { - margin-bottom: 0; - } + .message { + max-width: 75%; + border-radius: 16px; + padding: 8px 12px; + word-break: break-word; + user-select: text; + cursor: text; + font-size: var(--font-size-sm); + line-height: 1.45; + } - .context-label { - color: var(--text-muted); - min-width: 80px; - } + .message-body { + white-space: pre-wrap; + } - .context-value { - color: var(--text-color); - font-weight: 500; - } + .message-meta { + font-size: 10px; + margin-top: 4px; + opacity: 0.5; + } - .custom-prompt-value { - color: var(--text-secondary); - font-style: italic; - word-break: break-word; - white-space: pre-wrap; - } + .message-row.user .message { + background: var(--accent); + color: var(--bg-app); + border-bottom-right-radius: 4px; + } - .view-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border-color); - margin-bottom: 8px; - } + .message-row.user .message-meta { + text-align: right; + } - .view-tab { - background: transparent; - color: var(--text-muted); - border: none; - padding: 8px 16px; - font-size: 11px; - font-weight: 500; - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - transition: color 0.1s ease; - } + .message-row.ai .message { + background: var(--bg-elevated); + color: var(--text-primary); + border: 1px solid var(--border); + border-bottom-left-radius: 4px; + } - .view-tab:hover { - color: var(--text-color); - } + .message-row.screen .message { + background: var(--bg-elevated); + color: var(--text-primary); + border: 1px solid var(--border); + border-bottom-left-radius: 4px; + } - .view-tab.active { - color: var(--text-color); - border-bottom-color: var(--text-color); - } + .context-row { + display: flex; + align-items: flex-start; + gap: var(--space-sm); + padding: var(--space-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-elevated); + } - .message.screen { - border-left-color: #22c55e; - } + .context-key { + width: 84px; + color: var(--text-muted); + font-size: var(--font-size-xs); + text-transform: uppercase; + letter-spacing: 0.4px; + flex-shrink: 0; + } - .analysis-meta { - font-size: 10px; - color: var(--text-muted); - margin-bottom: 4px; - font-family: 'SF Mono', Monaco, monospace; - } + .context-value { + color: var(--text-primary); + font-size: var(--font-size-sm); + line-height: 1.45; + white-space: pre-wrap; + word-break: break-word; + user-select: text; + cursor: text; + } - .empty-state { - text-align: center; - color: var(--text-muted); - font-size: 12px; - margin-top: 32px; - } - - .empty-state-title { - font-size: 14px; - font-weight: 500; - margin-bottom: 6px; - color: var(--text-secondary); - } - - .loading { - text-align: center; - color: var(--text-muted); - font-size: 12px; - margin-top: 32px; - } - - .sessions-list::-webkit-scrollbar, - .conversation-view::-webkit-scrollbar { - width: 8px; - } - - .sessions-list::-webkit-scrollbar-track, - .conversation-view::-webkit-scrollbar-track { - background: transparent; - } - - .sessions-list::-webkit-scrollbar-thumb, - .conversation-view::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 4px; - } - - .sessions-list::-webkit-scrollbar-thumb:hover, - .conversation-view::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); - } - - .tabs-container { - display: flex; - gap: 0; - margin-bottom: 16px; - border-bottom: 1px solid var(--border-color); - } - - .tab { - background: transparent; - color: var(--text-muted); - border: none; - padding: 8px 16px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: color 0.1s ease; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - } - - .tab:hover { - color: var(--text-color); - } - - .tab.active { - color: var(--text-color); - border-bottom-color: var(--text-color); - } - - .saved-response-item { - padding: 12px 0; - border-bottom: 1px solid var(--border-color); - } - - .saved-response-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 6px; - } - - .saved-response-profile { - font-size: 11px; - font-weight: 500; - color: var(--text-secondary); - text-transform: capitalize; - } - - .saved-response-date { - font-size: 10px; - color: var(--text-muted); - font-family: 'SF Mono', Monaco, monospace; - } - - .saved-response-content { - font-size: 12px; - color: var(--text-color); - line-height: 1.4; - user-select: text; - cursor: text; - } - - .delete-button { - background: transparent; - color: var(--text-muted); - border: none; - padding: 4px; - border-radius: 3px; - cursor: pointer; - transition: all 0.1s ease; - } - - .delete-button:hover { - background: rgba(241, 76, 76, 0.1); - color: var(--error-color); - } - `; + .empty { + color: var(--text-muted); + font-size: var(--font-size-sm); + display: flex; + align-items: center; + justify-content: center; + min-height: 120px; + border: 1px dashed var(--border); + border-radius: var(--radius-sm); + } + `, + ]; static properties = { sessions: { type: Array }, selectedSession: { type: Object }, + selectedSessionId: { type: String }, loading: { type: Boolean }, activeTab: { type: String }, + searchQuery: { type: String }, }; constructor() { super(); this.sessions = []; this.selectedSession = null; + this.selectedSessionId = null; this.loading = true; - this.activeTab = 'conversation'; // 'conversation' or 'screen' + this.activeTab = 'conversation'; + this.searchQuery = ''; this.loadSessions(); } - connectedCallback() { - super.connectedCallback(); - // Resize window for this view - resizeLayout(); - } - async loadSessions() { try { this.loading = true; - this.sessions = await mastermind.storage.getAllSessions(); + this.sessions = await cheatingDaddy.storage.getAllSessions(); } catch (error) { - console.error('Error loading conversation sessions:', error); + console.error('Error loading sessions:', error); this.sessions = []; } finally { this.loading = false; @@ -390,11 +291,13 @@ export class HistoryView extends LitElement { } } - async loadSelectedSession(sessionId) { + async openSession(sessionId) { try { - const session = await mastermind.storage.getSession(sessionId); + const session = await cheatingDaddy.storage.getSession(sessionId); if (session) { this.selectedSession = session; + this.selectedSessionId = sessionId; + this.activeTab = 'conversation'; this.requestUpdate(); } } catch (error) { @@ -402,59 +305,29 @@ export class HistoryView extends LitElement { } } + closeSession() { + this.selectedSession = null; + this.selectedSessionId = null; + this.activeTab = 'conversation'; + } + + handleSearchInput(e) { + this.searchQuery = e.target.value; + } + formatDate(timestamp) { const date = new Date(timestamp); - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric', - }); + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } formatTime(timestamp) { const date = new Date(timestamp); - return date.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit', - }); + return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } formatTimestamp(timestamp) { const date = new Date(timestamp); - return date.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }); - } - - getSessionPreview(session) { - const parts = []; - if (session.messageCount > 0) { - parts.push(`${session.messageCount} messages`); - } - if (session.screenAnalysisCount > 0) { - parts.push(`${session.screenAnalysisCount} screen analysis`); - } - if (session.profile) { - const profileNames = this.getProfileNames(); - parts.push(profileNames[session.profile] || session.profile); - } - return parts.length > 0 ? parts.join(' • ') : 'Empty session'; - } - - handleSessionClick(session) { - this.loadSelectedSession(session.sessionId); - } - - handleBackClick() { - this.selectedSession = null; - this.activeTab = 'conversation'; - } - - handleTabClick(tab) { - this.activeTab = tab; + return date.toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } getProfileNames() { @@ -468,181 +341,170 @@ export class HistoryView extends LitElement { }; } - renderSessionsList() { - if (this.loading) { - return html`
Loading conversation history...
`; + _getProfileLabel(session) { + if (session.profile) { + const names = this.getProfileNames(); + return names[session.profile] || session.profile; } - - if (this.sessions.length === 0) { - return html` -
-
No conversations yet
-
Start a session to see your conversation history here
-
- `; - } - - return html` -
- ${this.sessions.map( - session => html` -
this.handleSessionClick(session)}> -
-
${this.formatDate(session.createdAt)}
-
${this.formatTime(session.createdAt)}
-
-
${this.getSessionPreview(session)}
-
- ` - )} -
- `; + return 'Session'; } - renderContextContent() { - const { profile, customPrompt } = this.selectedSession; - const profileNames = this.getProfileNames(); - - if (!profile && !customPrompt) { - return html`
No profile context available
`; + getSessionPreview(session) { + const parts = []; + if (session.messageCount > 0) parts.push(`${session.messageCount} messages`); + if (session.screenAnalysisCount > 0) parts.push(`${session.screenAnalysisCount} screen`); + if (session.profile) { + const profileNames = this.getProfileNames(); + parts.push(profileNames[session.profile] || session.profile); } - - return html` -
- ${profile - ? html` -
- Profile: - ${profileNames[profile] || profile} -
- ` - : ''} - ${customPrompt - ? html` -
- Custom Prompt: - ${customPrompt} -
- ` - : ''} -
- `; + return parts.length > 0 ? parts.join(' · ') : 'Empty session'; } - renderConversationContent() { - const { conversationHistory } = this.selectedSession; + getFilteredSessions() { + if (!this.searchQuery.trim()) return this.sessions; + const q = this.searchQuery.toLowerCase(); + return this.sessions.filter(session => { + const preview = this.getSessionPreview(session).toLowerCase(); + const date = this.formatDate(session.createdAt).toLowerCase(); + return preview.includes(q) || date.includes(q); + }); + } - // Flatten the conversation turns into individual messages + collectConversation(session) { const messages = []; - if (conversationHistory) { - conversationHistory.forEach(turn => { - if (turn.transcription) { - messages.push({ - type: 'user', - content: turn.transcription, - timestamp: turn.timestamp, - }); - } - if (turn.ai_response) { - messages.push({ - type: 'ai', - content: turn.ai_response, - timestamp: turn.timestamp, - }); - } - }); - } - - if (messages.length === 0) { - return html`
No conversation data available
`; - } - - return messages.map(message => html`
${message.content}
`); + const history = session.conversationHistory || []; + history.forEach(turn => { + if (turn.transcription) messages.push({ type: 'user', content: turn.transcription, timestamp: turn.timestamp }); + if (turn.ai_response) messages.push({ type: 'ai', content: turn.ai_response, timestamp: turn.timestamp }); + }); + return messages; } - renderScreenAnalysisContent() { - const { screenAnalysisHistory } = this.selectedSession; + renderTabContent() { + if (!this.selectedSession) return html`
Select a session.
`; - if (!screenAnalysisHistory || screenAnalysisHistory.length === 0) { - return html`
No screen analysis data available
`; - } - - return screenAnalysisHistory.map( - analysis => html` -
-
${this.formatTimestamp(analysis.timestamp)} • ${analysis.model || 'unknown model'}
- ${analysis.response} + if (this.activeTab === 'conversation') { + const messages = this.collectConversation(this.selectedSession); + if (!messages.length) return html`
No conversation data.
`; + return messages.map(msg => html` +
+
+
${msg.content}
+
${this.formatTime(msg.timestamp)}
+
- ` - ); - } + `); + } - renderConversationView() { - if (!this.selectedSession) return html``; + if (this.activeTab === 'screen') { + const screen = this.selectedSession.screenAnalysisHistory || []; + if (!screen.length) return html`
No screen analysis data.
`; + return screen.map(entry => html` +
+
+
${entry.response || ''}
+
${this.formatTime(entry.timestamp)}
+
+
+ `); + } - const { conversationHistory, screenAnalysisHistory, profile, customPrompt } = this.selectedSession; - const hasConversation = conversationHistory && conversationHistory.length > 0; - const hasScreenAnalysis = screenAnalysisHistory && screenAnalysisHistory.length > 0; - const hasContext = profile || customPrompt; + const profile = this.selectedSession.profile; + const prompt = this.selectedSession.customPrompt; + if (!profile && !prompt) return html`
No context saved for this session.
`; return html` -
- -
-
-
- Them -
-
-
- Suggestion -
-
-
- Screen -
+ ${profile ? html` +
+ Profile + ${this.getProfileNames()[profile] || profile}
+ ` : ''} + ${prompt ? html` +
+ Prompt + ${prompt} +
+ ` : ''} + `; + } + + renderListView() { + const filteredSessions = this.getFilteredSessions(); + return html` +
History
+ +
+ + + + +
-
- + `) : ''} +
+ + `; + } + + renderDetailView() { + const conversationCount = this.collectConversation(this.selectedSession).length; + const screenCount = this.selectedSession?.screenAnalysisHistory?.length || 0; + + return html` +
Session Detail
+
+ -
+
+ - +
-
- ${this.activeTab === 'conversation' - ? this.renderConversationContent() - : this.activeTab === 'screen' - ? this.renderScreenAnalysisContent() - : this.renderContextContent()} -
+
+ ${this.renderTabContent()} +
`; } render() { - if (this.selectedSession) { - return html`
${this.renderConversationView()}
`; - } - - return html`
${this.renderSessionsList()}
`; + return html` +
+
+ ${this.selectedSession ? this.renderDetailView() : this.renderListView()} +
+
+ `; } } diff --git a/src/components/views/MainView.js b/src/components/views/MainView.js index dc55646..92289f0 100644 --- a/src/components/views/MainView.js +++ b/src/components/views/MainView.js @@ -1,244 +1,877 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; export class MainView extends LitElement { static styles = css` * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; + font-family: var(--font); cursor: default; user-select: none; - } - - .welcome { - font-size: 20px; - margin-bottom: 6px; - font-weight: 500; - color: var(--text-color); - margin-top: auto; - } - - .input-group { - display: flex; - gap: 10px; - margin-bottom: 16px; - } - - .input-group input { - flex: 1; - } - - input { - background: var(--input-background); - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 10px 12px; - width: 100%; - border-radius: 3px; - font-size: 13px; - transition: border-color 0.1s ease; - } - - input:focus { - outline: none; - border-color: var(--border-default); - } - - input::placeholder { - color: var(--placeholder-color); - } - - /* Red blink animation for empty API key */ - input.api-key-error { - animation: blink-red 0.6s ease-in-out; - border-color: var(--error-color); - } - - @keyframes blink-red { - 0%, - 100% { - border-color: var(--border-color); - } - 50% { - border-color: var(--error-color); - background: rgba(241, 76, 76, 0.1); - } - } - - .start-button { - background: var(--start-button-background); - color: var(--start-button-color); - border: none; - padding: 10px 16px; - border-radius: 3px; - font-size: 13px; - font-weight: 500; - white-space: nowrap; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.1s ease; - } - - .start-button:hover { - background: var(--start-button-hover-background); - } - - .start-button.initializing { - opacity: 0.5; - cursor: not-allowed; - } - - .start-button.initializing:hover { - background: var(--start-button-background); - } - - .shortcut-hint { - font-size: 11px; - color: var(--text-muted); - font-family: 'SF Mono', Monaco, monospace; - } - - .description { - color: var(--text-secondary); - font-size: 13px; - margin-bottom: 20px; - line-height: 1.5; - } - - .link { - color: var(--text-color); - text-decoration: underline; - cursor: pointer; - text-underline-offset: 2px; - } - - .link:hover { - color: var(--text-color); + box-sizing: border-box; } :host { height: 100%; display: flex; flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-xl) var(--space-lg); + } + + .form-wrapper { width: 100%; - max-width: 480px; + max-width: 420px; + display: flex; + flex-direction: column; + gap: var(--space-md); + } + + .page-title { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + margin-bottom: var(--space-xs); + } + + .page-title .mode-suffix { + opacity: 0.5; + } + + .page-subtitle { + font-size: var(--font-size-sm); + color: var(--text-muted); + margin-bottom: var(--space-md); + } + + /* ── Form controls ── */ + + .form-group { + display: flex; + flex-direction: column; + gap: var(--space-xs); + } + + .form-label { + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + } + + input, select, textarea { + background: var(--bg-elevated); + color: var(--text-primary); + border: 1px solid var(--border); + padding: 10px 12px; + width: 100%; + border-radius: var(--radius-sm); + font-size: var(--font-size-sm); + font-family: var(--font); + transition: border-color var(--transition), box-shadow var(--transition); + } + + input:hover:not(:focus), select:hover:not(:focus), textarea:hover:not(:focus) { + border-color: var(--text-muted); + } + + input:focus, select:focus, textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 1px var(--accent); + } + + input::placeholder, textarea::placeholder { + color: var(--text-muted); + } + + input.error { + border-color: var(--danger, #EF4444); + } + + select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23999' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 8px center; + background-repeat: no-repeat; + background-size: 14px; + padding-right: 28px; + } + + textarea { + resize: vertical; + min-height: 80px; + line-height: var(--line-height); + } + + .form-hint { + font-size: var(--font-size-xs); + color: var(--text-muted); + } + + .form-hint a, .form-hint span.link { + color: var(--accent); + text-decoration: none; + cursor: pointer; + } + + .form-hint span.link:hover { + text-decoration: underline; + } + + .whisper-label-row { + display: flex; + align-items: center; + gap: 6px; + } + + .whisper-spinner { + width: 12px; + height: 12px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: whisper-spin 0.8s linear infinite; + } + + @keyframes whisper-spin { + to { transform: rotate(360deg); } + } + + /* ── Start button ── */ + + .start-button { + position: relative; + overflow: hidden; + background: #e8e8e8; + color: #111111; + border: none; + padding: 12px var(--space-md); + border-radius: var(--radius-sm); + font-size: var(--font-size-base); + font-weight: var(--font-weight-semibold); + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + } + + .start-button canvas.btn-aurora { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 0; + } + + .start-button canvas.btn-dither { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 1; + opacity: 0.1; + mix-blend-mode: overlay; + pointer-events: none; + image-rendering: pixelated; + } + + .start-button .btn-label { + position: relative; + z-index: 2; + display: flex; + align-items: center; + gap: var(--space-sm); + } + + .start-button:hover { + opacity: 0.9; + } + + .start-button.disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .start-button.disabled:hover { + opacity: 0.5; + } + + .shortcut-hint { + display: inline-flex; + align-items: center; + gap: 2px; + opacity: 0.5; + font-family: var(--font-mono); + } + + /* ── Divider ── */ + + .divider { + display: flex; + align-items: center; + gap: var(--space-md); + margin: var(--space-sm) 0; + } + + .divider-line { + flex: 1; + height: 1px; + background: var(--border); + } + + .divider-text { + font-size: var(--font-size-xs); + color: var(--text-muted); + text-transform: lowercase; + } + + /* ── Mode switch links ── */ + + .mode-links { + display: flex; + justify-content: center; + gap: var(--space-lg); + } + + .mode-link { + font-size: var(--font-size-sm); + color: var(--text-secondary); + cursor: pointer; + background: none; + border: none; + padding: 0; + transition: color var(--transition); + } + + .mode-link:hover { + color: var(--text-primary); + } + + /* ── Mode option cards ── */ + + .mode-cards { + display: flex; + gap: var(--space-sm); + } + + .mode-card { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px 14px; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-elevated); + cursor: pointer; + transition: border-color 0.2s, background 0.2s; + } + + .mode-card:hover { + border-color: var(--text-muted); + background: var(--bg-hover); + } + + .mode-card-title { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + } + + .mode-card-desc { + font-size: var(--font-size-xs); + color: var(--text-muted); + line-height: var(--line-height); + } + + /* ── Title row with help ── */ + + .title-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-xs); + } + + .title-row .page-title { + margin-bottom: 0; + } + + .help-btn { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 4px; + border-radius: var(--radius-sm); + transition: color 0.2s; + display: flex; + align-items: center; + } + + .help-btn:hover { + color: var(--text-secondary); + } + + .help-btn * { + pointer-events: none; + } + + /* ── Help content ── */ + + .help-content { + display: flex; + flex-direction: column; + gap: var(--space-md); + max-height: 500px; + overflow-y: auto; + } + + .help-section { + display: flex; + flex-direction: column; + gap: 4px; + } + + .help-section-title { + font-size: var(--font-size-xs); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + } + + .help-section-text { + font-size: var(--font-size-xs); + color: var(--text-secondary); + line-height: var(--line-height); + } + + .help-code { + font-family: var(--font-mono); + font-size: 11px; + background: var(--bg-hover); + padding: 6px 8px; + border-radius: var(--radius-sm); + color: var(--text-primary); + display: block; + } + + .help-link { + color: var(--accent); + cursor: pointer; + text-decoration: none; + } + + .help-link:hover { + text-decoration: underline; + } + + .help-models { + display: flex; + flex-direction: column; + gap: 2px; + } + + .help-model { + font-size: var(--font-size-xs); + color: var(--text-secondary); + display: flex; + justify-content: space-between; + } + + .help-model-name { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-primary); + } + + .help-divider { + border: none; + border-top: 1px solid var(--border); + margin: 0; + } + + .help-warn { + font-size: var(--font-size-xs); + color: var(--warning); + line-height: var(--line-height); } `; static properties = { onStart: { type: Function }, - onAPIKeyHelp: { type: Function }, + onExternalLink: { type: Function }, + selectedProfile: { type: String }, + onProfileChange: { type: Function }, isInitializing: { type: Boolean }, - onLayoutModeChange: { type: Function }, - showApiKeyError: { type: Boolean }, + whisperDownloading: { type: Boolean }, + // Internal state + _mode: { state: true }, + _token: { state: true }, + _geminiKey: { state: true }, + _groqKey: { state: true }, + _openaiKey: { state: true }, + _tokenError: { state: true }, + _keyError: { state: true }, + // Local AI state + _ollamaHost: { state: true }, + _ollamaModel: { state: true }, + _whisperModel: { state: true }, + _showLocalHelp: { state: true }, }; constructor() { super(); this.onStart = () => {}; - this.onAPIKeyHelp = () => {}; + this.onExternalLink = () => {}; + this.selectedProfile = 'interview'; + this.onProfileChange = () => {}; this.isInitializing = false; - this.onLayoutModeChange = () => {}; - this.showApiKeyError = false; - this.boundKeydownHandler = this.handleKeydown.bind(this); - this.apiKey = ''; - this._loadApiKey(); + this.whisperDownloading = false; + + this._mode = 'byok'; + this._token = ''; + this._geminiKey = ''; + this._groqKey = ''; + this._openaiKey = ''; + this._tokenError = false; + this._keyError = false; + this._showLocalHelp = false; + this._ollamaHost = 'http://127.0.0.1:11434'; + this._ollamaModel = 'llama3.1'; + this._whisperModel = 'Xenova/whisper-small'; + + this._animId = null; + this._time = 0; + this._mouseX = -1; + this._mouseY = -1; + + this.boundKeydownHandler = this._handleKeydown.bind(this); + this._loadFromStorage(); } - async _loadApiKey() { - this.apiKey = await mastermind.storage.getApiKey(); - this.requestUpdate(); + async _loadFromStorage() { + try { + const [prefs, creds] = await Promise.all([ + cheatingDaddy.storage.getPreferences(), + cheatingDaddy.storage.getCredentials().catch(() => ({})), + ]); + + this._mode = prefs.providerMode || 'byok'; + + // Load keys + this._token = ''; + this._geminiKey = await cheatingDaddy.storage.getApiKey().catch(() => '') || ''; + this._groqKey = await cheatingDaddy.storage.getGroqApiKey().catch(() => '') || ''; + this._openaiKey = creds.openaiKey || ''; + + // Load local AI settings + this._ollamaHost = prefs.ollamaHost || 'http://127.0.0.1:11434'; + this._ollamaModel = prefs.ollamaModel || 'llama3.1'; + this._whisperModel = prefs.whisperModel || 'Xenova/whisper-small'; + + this.requestUpdate(); + } catch (e) { + console.error('Error loading MainView storage:', e); + } } connectedCallback() { super.connectedCallback(); - window.electron?.ipcRenderer?.on('session-initializing', (event, isInitializing) => { - this.isInitializing = isInitializing; - }); - - // Add keyboard event listener for Ctrl+Enter (or Cmd+Enter on Mac) document.addEventListener('keydown', this.boundKeydownHandler); - - // Resize window for this view - resizeLayout(); } disconnectedCallback() { super.disconnectedCallback(); - window.electron?.ipcRenderer?.removeAllListeners('session-initializing'); - // Remove keyboard event listener document.removeEventListener('keydown', this.boundKeydownHandler); + if (this._animId) cancelAnimationFrame(this._animId); } - handleKeydown(e) { + updated(changedProperties) { + super.updated(changedProperties); + if (changedProperties.has('_mode')) { + // Stop old animation when switching modes + if (this._animId) { + cancelAnimationFrame(this._animId); + this._animId = null; + } + } + } + + _initButtonAurora() { + const btn = this.shadowRoot.querySelector('.start-button'); + const aurora = this.shadowRoot.querySelector('canvas.btn-aurora'); + const dither = this.shadowRoot.querySelector('canvas.btn-dither'); + if (!aurora || !dither || !btn) return; + + // Mouse tracking + this._mouseX = -1; + this._mouseY = -1; + btn.addEventListener('mousemove', (e) => { + const rect = btn.getBoundingClientRect(); + this._mouseX = (e.clientX - rect.left) / rect.width; + this._mouseY = (e.clientY - rect.top) / rect.height; + }); + btn.addEventListener('mouseleave', () => { + this._mouseX = -1; + this._mouseY = -1; + }); + + // Dither + const blockSize = 8; + const cols = Math.ceil(aurora.offsetWidth / blockSize); + const rows = Math.ceil(aurora.offsetHeight / blockSize); + dither.width = cols; + dither.height = rows; + const dCtx = dither.getContext('2d'); + const img = dCtx.createImageData(cols, rows); + for (let i = 0; i < img.data.length; i += 4) { + const v = Math.random() > 0.5 ? 255 : 0; + img.data[i] = v; img.data[i+1] = v; img.data[i+2] = v; img.data[i+3] = 255; + } + dCtx.putImageData(img, 0, 0); + + // Aurora + const ctx = aurora.getContext('2d'); + const scale = 0.4; + aurora.width = Math.floor(aurora.offsetWidth * scale); + aurora.height = Math.floor(aurora.offsetHeight * scale); + + const blobs = [ + { color: [120, 160, 230], x: 0.1, y: 0.3, vx: 0.25, vy: 0.2, phase: 0 }, + { color: [150, 120, 220], x: 0.8, y: 0.5, vx: -0.2, vy: 0.25, phase: 1.5 }, + { color: [200, 140, 210], x: 0.5, y: 0.6, vx: 0.18, vy: -0.22, phase: 3.0 }, + { color: [100, 190, 190], x: 0.3, y: 0.7, vx: 0.3, vy: 0.15, phase: 4.5 }, + { color: [220, 170, 130], x: 0.7, y: 0.4, vx: -0.22, vy: -0.25, phase: 6.0 }, + ]; + + const draw = () => { + this._time += 0.008; + const w = aurora.width; + const h = aurora.height; + const maxDim = Math.max(w, h); + + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, w, h); + + const hovering = this._mouseX >= 0; + + for (const blob of blobs) { + const t = this._time; + const cx = (blob.x + Math.sin(t * blob.vx + blob.phase) * 0.4) * w; + const cy = (blob.y + Math.cos(t * blob.vy + blob.phase * 0.7) * 0.4) * h; + const r = maxDim * 0.45; + + let boost = 1; + if (hovering) { + const dx = cx / w - this._mouseX; + const dy = cy / h - this._mouseY; + const dist = Math.sqrt(dx * dx + dy * dy); + boost = 1 + 2.5 * Math.max(0, 1 - dist / 0.6); + } + + const a0 = Math.min(1, 0.18 * boost); + const a1 = Math.min(1, 0.08 * boost); + const a2 = Math.min(1, 0.02 * boost); + + const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r); + grad.addColorStop(0, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, ${a0})`); + grad.addColorStop(0.3, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, ${a1})`); + grad.addColorStop(0.6, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, ${a2})`); + grad.addColorStop(1, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0)`); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, w, h); + } + + this._animId = requestAnimationFrame(draw); + }; + + draw(); + } + + _handleKeydown(e) { const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; - const isStartShortcut = isMac ? e.metaKey && e.key === 'Enter' : e.ctrlKey && e.key === 'Enter'; - - if (isStartShortcut) { + if ((isMac ? e.metaKey : e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); - this.handleStartClick(); + this._handleStart(); } } - async handleInput(e) { - this.apiKey = e.target.value; - await mastermind.storage.setApiKey(e.target.value); - // Clear error state when user starts typing - if (this.showApiKeyError) { - this.showApiKeyError = false; - } + // ── Persistence ── + + async _saveMode(mode) { + this._mode = mode; + this._keyError = false; + await cheatingDaddy.storage.updatePreference('providerMode', mode); + this.requestUpdate(); } - handleStartClick() { - if (this.isInitializing) { - return; + async _saveGeminiKey(val) { + this._geminiKey = val; + this._keyError = false; + await cheatingDaddy.storage.setApiKey(val); + this.requestUpdate(); + } + + async _saveGroqKey(val) { + this._groqKey = val; + await cheatingDaddy.storage.setGroqApiKey(val); + this.requestUpdate(); + } + + async _saveOpenaiKey(val) { + this._openaiKey = val; + try { + const creds = await cheatingDaddy.storage.getCredentials().catch(() => ({})); + await cheatingDaddy.storage.setCredentials({ ...creds, openaiKey: val }); + } catch (e) {} + this.requestUpdate(); + } + + async _saveOllamaHost(val) { + this._ollamaHost = val; + await cheatingDaddy.storage.updatePreference('ollamaHost', val); + this.requestUpdate(); + } + + async _saveOllamaModel(val) { + this._ollamaModel = val; + await cheatingDaddy.storage.updatePreference('ollamaModel', val); + this.requestUpdate(); + } + + async _saveWhisperModel(val) { + this._whisperModel = val; + await cheatingDaddy.storage.updatePreference('whisperModel', val); + this.requestUpdate(); + } + + _handleProfileChange(e) { + this.onProfileChange(e.target.value); + } + + // ── Start ── + + _handleStart() { + if (this.isInitializing) return; + + if (this._mode === 'byok') { + if (!this._geminiKey.trim()) { + this._keyError = true; + this.requestUpdate(); + return; + } + } else if (this._mode === 'local') { + // Local mode doesn't need API keys, just Ollama host + if (!this._ollamaHost.trim()) { + return; + } } + this.onStart(); } - handleAPIKeyHelpClick() { - this.onAPIKeyHelp(); - } - - // Method to trigger the red blink animation triggerApiKeyError() { - this.showApiKeyError = true; - // Remove the error class after 1 second + this._keyError = true; + this.requestUpdate(); setTimeout(() => { - this.showApiKeyError = false; - }, 1000); + this._keyError = false; + this.requestUpdate(); + }, 2000); } - getStartButtonText() { + // ── Render helpers ── + + _renderStartButton() { const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; - const shortcut = isMac ? 'Cmd+Enter' : 'Ctrl+Enter'; - return html`Start ${shortcut}`; + + const cmdIcon = html``; + const ctrlIcon = html``; + const enterIcon = html``; + + return html` + + `; } - render() { - return html` -
Welcome
+ // ── BYOK mode ── -
+ _renderByokMode() { + return html` +
+ this._saveGeminiKey(e.target.value)} + class=${this._keyError ? 'error' : ''} /> - +
+ this.onExternalLink('https://aistudio.google.com/apikey')}>Get Gemini key +
+
+ +
+ + this._saveGroqKey(e.target.value)} + /> +
+ this.onExternalLink('https://console.groq.com/keys')}>Get Groq key +
+
+ + ${this._renderStartButton()} + `; + } + + // ── Local AI mode ── + + _renderLocalMode() { + return html` +
+ + this._saveOllamaHost(e.target.value)} + /> +
Ollama must be running locally
+
+ +
+ + this._saveOllamaModel(e.target.value)} + /> +
Run ollama pull ${this._ollamaModel} first
+
+ +
+
+ + ${this.whisperDownloading ? html`
` : ''} +
+ +
${this.whisperDownloading ? 'Downloading model...' : 'Downloaded automatically on first use'}
+
+ + ${this._renderStartButton()} + `; + } + + // ── Main render ── + + render() { + const helpIcon = html``; + const closeIcon = html``; + + return html` +
+ ${this._mode === 'local' ? html` +
+
Cheating Daddy Local AI
+ +
+ ` : html` +
+ Cheating Daddy BYOK +
+ `} +
+ ${this._mode === 'byok' ? 'Bring your own API keys' : 'Run models locally on your machine'} +
+ + ${this._mode === 'byok' ? this._renderByokMode() : ''} + ${this._mode === 'local' ? (this._showLocalHelp ? this._renderLocalHelp() : this._renderLocalMode()) : ''} + +
+
+
or switch to
+
+
+ + +
+ `; + } + + _renderLocalHelp() { + return html` +
+
+
What is Ollama?
+
Ollama lets you run large language models locally on your machine. Everything stays on your computer — no data leaves your device.
+
+ +
+
Install Ollama
+
Download from this.onExternalLink('https://ollama.com/download')}>ollama.com/download and install it.
+
+ +
+
Ollama must be running
+
Ollama needs to be running before you start a session. If it's not running, open your terminal and type:
+ ollama serve +
+ +
+
Pull a model
+
Download a model before first use:
+ ollama pull gemma3:4b +
+ +
+
Recommended models
+
+
gemma3:4b4B — fast, multimodal (images + text)
+
mistral-small8B — solid all-rounder, text only
+
+
gemma3:4b and above supports images — screenshots will work with these models.
+
+ +
+
Avoid "thinking" models (e.g. deepseek-r1, qwq). Local inference is already slower — a thinking model adds extra delay before responding.
+
+ +
+
Whisper
+
The Whisper speech-to-text model is downloaded automatically the first time you start a session. This is a one-time download.
+
- `; } } diff --git a/src/components/views/OnboardingView.js b/src/components/views/OnboardingView.js index 40a277e..14442d0 100644 --- a/src/components/views/OnboardingView.js +++ b/src/components/views/OnboardingView.js @@ -3,13 +3,7 @@ import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; export class OnboardingView extends LitElement { static styles = css` * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - sans-serif; + font-family: var(--font); cursor: default; user-select: none; margin: 0; @@ -27,44 +21,20 @@ export class OnboardingView extends LitElement { overflow: hidden; } - .onboarding-container { - position: relative; + .onboarding { width: 100%; height: 100%; - background: #0a0a0a; - overflow: hidden; - } - - .close-button { - position: absolute; - top: 12px; - right: 12px; - z-index: 10; - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 6px; - width: 32px; - height: 32px; + position: relative; display: flex; align-items: center; justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - color: rgba(255, 255, 255, 0.6); + border-radius: 12px; + border: 1px solid rgba(0, 0, 0, 0.08); + overflow: hidden; + background: #f0f0f0; } - .close-button:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.2); - color: rgba(255, 255, 255, 0.9); - } - - .close-button svg { - width: 16px; - height: 16px; - } - - .gradient-canvas { + canvas.aurora { position: absolute; top: 0; left: 0; @@ -73,617 +43,308 @@ export class OnboardingView extends LitElement { z-index: 0; } - .content-wrapper { + canvas.dither { position: absolute; top: 0; left: 0; - right: 0; - bottom: 60px; + width: 100%; + height: 100%; z-index: 1; - display: flex; - flex-direction: column; - justify-content: center; - padding: 32px 48px; - max-width: 500px; - color: #e5e5e5; - overflow: hidden; + opacity: 0.12; + mix-blend-mode: overlay; + pointer-events: none; + image-rendering: pixelated; } - .slide-icon { - width: 48px; - height: 48px; - margin-bottom: 16px; - opacity: 0.9; - display: block; + .slide { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + max-width: 400px; + padding: var(--space-xl); + gap: var(--space-md); } .slide-title { font-size: 28px; font-weight: 600; - margin-bottom: 12px; - color: #ffffff; - line-height: 1.3; + color: #111111; + line-height: 1.2; } - .slide-content { - font-size: 16px; + .slide-text { + font-size: 13px; line-height: 1.5; - margin-bottom: 24px; - color: #b8b8b8; - font-weight: 400; + color: #666666; } - .context-textarea { + .context-input { width: 100%; - height: 100px; - padding: 16px; - border: 1px solid rgba(255, 255, 255, 0.1); + min-height: 120px; + padding: 12px; + border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 8px; - background: rgba(255, 255, 255, 0.05); - color: #e5e5e5; - font-size: 14px; - font-family: inherit; + background: rgba(255, 255, 255, 0.7); + backdrop-filter: blur(8px); + color: #111111; + font-size: 13px; + font-family: var(--font); + line-height: 1.5; resize: vertical; - transition: all 0.2s ease; - margin-bottom: 24px; + text-align: left; } - .context-textarea::placeholder { - color: rgba(255, 255, 255, 0.4); - font-size: 14px; + .context-input::placeholder { + color: #999999; } - .context-textarea:focus { + .context-input:focus { outline: none; - border-color: rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.08); + border-color: rgba(0, 0, 0, 0.3); } - .feature-list { - max-width: 100%; - } - - .feature-item { + .actions { display: flex; + flex-direction: column; align-items: center; - margin-bottom: 12px; - font-size: 15px; - color: #b8b8b8; + gap: 8px; + margin-top: 8px; } - .feature-icon { - font-size: 16px; - margin-right: 12px; - opacity: 0.8; - } - - .migration-buttons { - display: flex; - gap: 12px; - margin-top: 24px; - } - - .migration-button { - flex: 1; - padding: 12px 24px; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid rgba(255, 255, 255, 0.1); - } - - .migration-button.primary { - background: rgba(59, 130, 246, 0.8); + .btn-primary { + background: #111111; + border: none; color: #ffffff; - border-color: rgba(59, 130, 246, 0.9); - } - - .migration-button.primary:hover { - background: rgba(59, 130, 246, 0.9); - border-color: rgba(59, 130, 246, 1); - } - - .migration-button.secondary { - background: rgba(255, 255, 255, 0.08); - color: #e5e5e5; - border-color: rgba(255, 255, 255, 0.1); - } - - .migration-button.secondary:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.2); - } - - .migration-button:active { - transform: scale(0.98); - } - - .navigation { - position: absolute; - bottom: 0; - left: 0; - right: 0; - z-index: 2; - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 24px; - background: rgba(0, 0, 0, 0.3); - backdrop-filter: blur(10px); - border-top: 1px solid rgba(255, 255, 255, 0.05); - height: 60px; - box-sizing: border-box; - } - - .nav-button { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.1); - color: #e5e5e5; - padding: 8px 16px; - border-radius: 6px; + padding: 10px 32px; + border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; - min-width: 36px; - min-height: 36px; + transition: opacity 0.15s; } - .nav-button:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.2); + .btn-primary:hover { + opacity: 0.85; } - .nav-button:active { - transform: scale(0.98); - } - - .nav-button:disabled { - opacity: 0.4; - cursor: not-allowed; - } - - .nav-button:disabled:hover { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(255, 255, 255, 0.1); - transform: none; - } - - .progress-dots { - display: flex; - gap: 12px; - align-items: center; - } - - .dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.2); - transition: all 0.2s ease; + .btn-back { + background: none; + border: none; + color: #888888; + font-size: 11px; cursor: pointer; + padding: 4px 8px; } - .dot:hover { - background: rgba(255, 255, 255, 0.4); - } - - .dot.active { - background: rgba(255, 255, 255, 0.8); - transform: scale(1.2); + .btn-back:hover { + color: #555555; } `; static properties = { currentSlide: { type: Number }, contextText: { type: String }, - hasOldConfig: { type: Boolean }, onComplete: { type: Function }, - onClose: { type: Function }, }; constructor() { super(); this.currentSlide = 0; this.contextText = ''; - this.hasOldConfig = false; this.onComplete = () => {}; - this.onClose = () => {}; - this.canvas = null; - this.ctx = null; - this.animationId = null; - - // Transition properties - this.isTransitioning = false; - this.transitionStartTime = 0; - this.transitionDuration = 800; // 800ms fade duration - this.previousColorScheme = null; - - // Subtle dark color schemes for each slide - this.colorSchemes = [ - // Slide 1 - Welcome (Very dark purple/gray) - [ - [25, 25, 35], // Dark gray-purple - [20, 20, 30], // Darker gray - [30, 25, 40], // Slightly purple - [15, 15, 25], // Very dark - [35, 30, 45], // Muted purple - [10, 10, 20], // Almost black - ], - // Slide 2 - Privacy (Dark blue-gray) - [ - [20, 25, 35], // Dark blue-gray - [15, 20, 30], // Darker blue-gray - [25, 30, 40], // Slightly blue - [10, 15, 25], // Very dark blue - [30, 35, 45], // Muted blue - [5, 10, 20], // Almost black - ], - // Slide 3 - Context (Dark neutral) - [ - [25, 25, 25], // Neutral dark - [20, 20, 20], // Darker neutral - [30, 30, 30], // Light dark - [15, 15, 15], // Very dark - [35, 35, 35], // Lighter dark - [10, 10, 10], // Almost black - ], - // Slide 4 - Features (Dark green-gray) - [ - [20, 30, 25], // Dark green-gray - [15, 25, 20], // Darker green-gray - [25, 35, 30], // Slightly green - [10, 20, 15], // Very dark green - [30, 40, 35], // Muted green - [5, 15, 10], // Almost black - ], - // Slide 5 - Migration (Dark teal-gray) - [ - [20, 30, 30], // Dark teal-gray - [15, 25, 25], // Darker teal-gray - [25, 35, 35], // Slightly teal - [10, 20, 20], // Very dark teal - [30, 40, 40], // Muted teal - [5, 15, 15], // Almost black - ], - // Slide 6 - Complete (Dark warm gray) - [ - [30, 25, 20], // Dark warm gray - [25, 20, 15], // Darker warm - [35, 30, 25], // Slightly warm - [20, 15, 10], // Very dark warm - [40, 35, 30], // Muted warm - [15, 10, 5], // Almost black - ], - ]; + this._animId = null; + this._time = 0; } - async firstUpdated() { - this.canvas = this.shadowRoot.querySelector('.gradient-canvas'); - this.ctx = this.canvas.getContext('2d'); - this.resizeCanvas(); - this.startGradientAnimation(); - - window.addEventListener('resize', () => this.resizeCanvas()); - - // Check if old config exists - if (window.mastermind && window.mastermind.storage) { - try { - this.hasOldConfig = await window.mastermind.storage.hasOldConfig(); - console.log('Has old config:', this.hasOldConfig); - this.requestUpdate(); // Force re-render with new hasOldConfig value - } catch (error) { - console.error('Error checking old config:', error); - this.hasOldConfig = false; - } - } + firstUpdated() { + this._startAurora(); + this._drawDither(); } disconnectedCallback() { super.disconnectedCallback(); - if (this.animationId) { - cancelAnimationFrame(this.animationId); + if (this._animId) cancelAnimationFrame(this._animId); + } + + _drawDither() { + const canvas = this.shadowRoot.querySelector('canvas.dither'); + if (!canvas) return; + const blockSize = 5; + const cols = Math.ceil(canvas.offsetWidth / blockSize); + const rows = Math.ceil(canvas.offsetHeight / blockSize); + canvas.width = cols; + canvas.height = rows; + const ctx = canvas.getContext('2d'); + const img = ctx.createImageData(cols, rows); + for (let i = 0; i < img.data.length; i += 4) { + const v = Math.random() > 0.5 ? 255 : 0; + img.data[i] = v; + img.data[i + 1] = v; + img.data[i + 2] = v; + img.data[i + 3] = 255; } - window.removeEventListener('resize', () => this.resizeCanvas()); + ctx.putImageData(img, 0, 0); } - resizeCanvas() { - if (!this.canvas) return; + _startAurora() { + const canvas = this.shadowRoot.querySelector('canvas.aurora'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); - const rect = this.getBoundingClientRect(); - this.canvas.width = rect.width; - this.canvas.height = rect.height; - } + const scale = 0.35; + const resize = () => { + canvas.width = Math.floor(canvas.offsetWidth * scale); + canvas.height = Math.floor(canvas.offsetHeight * scale); + }; + resize(); - startGradientAnimation() { - if (!this.ctx) return; + const blobs = [ + { parts: [ + { ox: 0, oy: 0, r: 1.0 }, + { ox: 0.22, oy: 0.1, r: 0.85 }, + { ox: 0.11, oy: 0.05, r: 0.5 }, + ], color: [180, 200, 230], x: 0.15, y: 0.2, vx: 0.35, vy: 0.25, phase: 0 }, - const animate = timestamp => { - this.drawGradient(timestamp); - this.animationId = requestAnimationFrame(animate); + { parts: [ + { ox: 0, oy: 0, r: 0.95 }, + { ox: 0.18, oy: -0.08, r: 0.75 }, + { ox: 0.09, oy: -0.04, r: 0.4 }, + ], color: [190, 180, 220], x: 0.75, y: 0.2, vx: -0.3, vy: 0.35, phase: 1.2 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.9 }, + { ox: 0.24, oy: 0.12, r: 0.9 }, + { ox: 0.12, oy: 0.06, r: 0.35 }, + ], color: [210, 195, 215], x: 0.5, y: 0.65, vx: 0.25, vy: -0.3, phase: 2.4 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.8 }, + { ox: -0.15, oy: 0.18, r: 0.7 }, + { ox: -0.07, oy: 0.09, r: 0.45 }, + ], color: [175, 210, 210], x: 0.1, y: 0.75, vx: 0.4, vy: 0.2, phase: 3.6 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.75 }, + { ox: 0.12, oy: -0.15, r: 0.65 }, + { ox: 0.06, oy: -0.07, r: 0.35 }, + ], color: [220, 210, 195], x: 0.85, y: 0.55, vx: -0.28, vy: -0.32, phase: 4.8 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.95 }, + { ox: -0.2, oy: -0.12, r: 0.75 }, + { ox: -0.1, oy: -0.06, r: 0.4 }, + ], color: [170, 190, 225], x: 0.6, y: 0.1, vx: -0.2, vy: 0.38, phase: 6.0 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.85 }, + { ox: 0.17, oy: 0.15, r: 0.75 }, + { ox: 0.08, oy: 0.07, r: 0.35 }, + ], color: [200, 190, 220], x: 0.35, y: 0.4, vx: 0.32, vy: -0.22, phase: 7.2 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.75 }, + { ox: -0.13, oy: 0.18, r: 0.65 }, + { ox: -0.06, oy: 0.1, r: 0.4 }, + ], color: [215, 205, 200], x: 0.9, y: 0.85, vx: -0.35, vy: -0.25, phase: 8.4 }, + + { parts: [ + { ox: 0, oy: 0, r: 0.7 }, + { ox: 0.16, oy: -0.1, r: 0.6 }, + { ox: 0.08, oy: -0.05, r: 0.35 }, + ], color: [185, 210, 205], x: 0.45, y: 0.9, vx: 0.22, vy: -0.4, phase: 9.6 }, + ]; + + const baseRadius = 0.32; + + const draw = () => { + this._time += 0.012; + const w = canvas.width; + const h = canvas.height; + const dim = Math.min(w, h); + + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, w, h); + + for (const blob of blobs) { + const t = this._time; + const cx = (blob.x + Math.sin(t * blob.vx + blob.phase) * 0.22) * w; + const cy = (blob.y + Math.cos(t * blob.vy + blob.phase * 0.7) * 0.22) * h; + + for (const part of blob.parts) { + const wobble = Math.sin(t * 2.5 + part.ox * 25 + blob.phase) * 0.02; + const px = cx + (part.ox + wobble) * dim; + const py = cy + (part.oy + wobble * 0.7) * dim; + const pr = part.r * baseRadius * dim; + + const grad = ctx.createRadialGradient(px, py, 0, px, py, pr); + grad.addColorStop(0, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.55)`); + grad.addColorStop(0.4, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.3)`); + grad.addColorStop(0.7, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.1)`); + grad.addColorStop(1, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0)`); + + ctx.fillStyle = grad; + ctx.fillRect(0, 0, w, h); + } + } + + this._animId = requestAnimationFrame(draw); }; - animate(0); - } - - drawGradient(timestamp) { - if (!this.ctx || !this.canvas) return; - - const { width, height } = this.canvas; - let colors = this.colorSchemes[this.currentSlide]; - - // Handle color scheme transitions - if (this.isTransitioning && this.previousColorScheme) { - const elapsed = timestamp - this.transitionStartTime; - const progress = Math.min(elapsed / this.transitionDuration, 1); - - // Use easing function for smoother transition - const easedProgress = this.easeInOutCubic(progress); - - colors = this.interpolateColorSchemes(this.previousColorScheme, this.colorSchemes[this.currentSlide], easedProgress); - - // End transition when complete - if (progress >= 1) { - this.isTransitioning = false; - this.previousColorScheme = null; - } - } - - const time = timestamp * 0.0005; // Much slower animation - - // Create moving gradient with subtle flow - const flowX = Math.sin(time * 0.7) * width * 0.3; - const flowY = Math.cos(time * 0.5) * height * 0.2; - - const gradient = this.ctx.createLinearGradient(flowX, flowY, width + flowX * 0.5, height + flowY * 0.5); - - // Very subtle color variations with movement - colors.forEach((color, index) => { - const offset = index / (colors.length - 1); - const wave = Math.sin(time + index * 0.3) * 0.05; // Very subtle wave - - const r = Math.max(0, Math.min(255, color[0] + wave * 5)); - const g = Math.max(0, Math.min(255, color[1] + wave * 5)); - const b = Math.max(0, Math.min(255, color[2] + wave * 5)); - - gradient.addColorStop(offset, `rgb(${r}, ${g}, ${b})`); - }); - - // Fill with moving gradient - this.ctx.fillStyle = gradient; - this.ctx.fillRect(0, 0, width, height); - - // Add a second layer with radial gradient for more depth - const centerX = width * 0.5 + Math.sin(time * 0.3) * width * 0.15; - const centerY = height * 0.5 + Math.cos(time * 0.4) * height * 0.1; - const radius = Math.max(width, height) * 0.8; - - const radialGradient = this.ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); - - // Very subtle radial overlay - radialGradient.addColorStop(0, `rgba(${colors[0][0] + 10}, ${colors[0][1] + 10}, ${colors[0][2] + 10}, 0.1)`); - radialGradient.addColorStop(0.5, `rgba(${colors[2][0]}, ${colors[2][1]}, ${colors[2][2]}, 0.05)`); - radialGradient.addColorStop( - 1, - `rgba(${colors[colors.length - 1][0]}, ${colors[colors.length - 1][1]}, ${colors[colors.length - 1][2]}, 0.03)` - ); - - this.ctx.globalCompositeOperation = 'overlay'; - this.ctx.fillStyle = radialGradient; - this.ctx.fillRect(0, 0, width, height); - this.ctx.globalCompositeOperation = 'source-over'; - } - - nextSlide() { - if (this.currentSlide < 5) { - this.startColorTransition(this.currentSlide + 1); - } else { - this.completeOnboarding(); - } - } - - prevSlide() { - if (this.currentSlide > 0) { - this.startColorTransition(this.currentSlide - 1); - } - } - - startColorTransition(newSlide) { - this.previousColorScheme = [...this.colorSchemes[this.currentSlide]]; - this.currentSlide = newSlide; - this.isTransitioning = true; - this.transitionStartTime = performance.now(); - } - - // Interpolate between two color schemes - interpolateColorSchemes(scheme1, scheme2, progress) { - return scheme1.map((color1, index) => { - const color2 = scheme2[index]; - return [ - color1[0] + (color2[0] - color1[0]) * progress, - color1[1] + (color2[1] - color1[1]) * progress, - color1[2] + (color2[2] - color1[2]) * progress, - ]; - }); - } - - // Easing function for smooth transitions - easeInOutCubic(t) { - return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; + draw(); } handleContextInput(e) { this.contextText = e.target.value; } - async handleClose() { - if (window.require) { - const { ipcRenderer } = window.require('electron'); - await ipcRenderer.invoke('quit-application'); - } - } - - async handleMigrate() { - const success = await window.mastermind.storage.migrateFromOldConfig(); - if (success) { - console.log('Migration completed successfully'); - } - this.nextSlide(); - } - - async handleSkipMigration() { - this.nextSlide(); - } - async completeOnboarding() { if (this.contextText.trim()) { - await mastermind.storage.updatePreference('customPrompt', this.contextText.trim()); + await cheatingDaddy.storage.updatePreference('customPrompt', this.contextText.trim()); } - await mastermind.storage.updateConfig('onboarded', true); + await cheatingDaddy.storage.updateConfig('onboarded', true); this.onComplete(); } - getSlideContent() { - const slides = [ - { - icon: 'assets/onboarding/welcome.svg', - title: 'Welcome to Mastermind', - content: - 'Your AI assistant that listens and watches, then provides intelligent suggestions automatically during interviews, meetings, and presentations.', - }, - { - icon: 'assets/onboarding/security.svg', - title: 'Completely Private', - content: 'Invisible to screen sharing apps and recording software. Your secret advantage stays completely hidden from others.', - }, - { - icon: 'assets/onboarding/context.svg', - title: 'Add Your Context', - content: 'Share relevant information to help the AI provide better, more personalized assistance.', - showTextarea: true, - }, - { - icon: 'assets/onboarding/customize.svg', - title: 'Additional Features', - content: '', - showFeatures: true, - }, - { - icon: 'assets/onboarding/context.svg', - title: 'Migrate Settings?', - content: this.hasOldConfig - ? 'Mastermind is a fork of Cheating Daddy. We detected existing Cheating Daddy settings on your system. Would you like to automatically migrate your settings, API keys, and history?' - : 'Mastermind is a fork of Cheating Daddy. No previous settings were detected.', - showMigration: this.hasOldConfig, - }, - { - icon: 'assets/onboarding/ready.svg', - title: 'Ready to Go', - content: 'Choose your AI Provider and start getting AI-powered assistance in real-time.', - }, - ]; + renderSlide() { + if (this.currentSlide === 0) { + return html` +
+
Cheating Daddy
+
Real-time AI that listens, watches, and helps during interviews, meetings, and exams.
+
+ +
+
+ `; + } - return slides[this.currentSlide]; + return html` +
+
Add context
+
Paste your resume or any info the AI should know. You can skip this and add it later.
+ +
+ + +
+
+ `; } render() { - const slide = this.getSlideContent(); - return html` -
- - - -
- ${slide.title} icon -
${slide.title}
-
${slide.content}
- - ${slide.showTextarea - ? html` - - ` - : ''} - ${slide.showFeatures - ? html` -
-
- - - Customize AI behavior and responses -
-
- - - Review conversation history -
-
- - - Adjust capture settings and intervals -
-
- ` - : ''} - ${slide.showMigration - ? html` -
- - -
- ` - : ''} -
- - +
+ + + ${this.renderSlide()}
`; } diff --git a/src/components/views/ScreenPickerDialog.js b/src/components/views/ScreenPickerDialog.js deleted file mode 100644 index 8552d8e..0000000 --- a/src/components/views/ScreenPickerDialog.js +++ /dev/null @@ -1,175 +0,0 @@ -import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; - -export class ScreenPickerDialog extends LitElement { - static properties = { - sources: { type: Array }, - visible: { type: Boolean }, - }; - - static styles = css` - :host { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.8); - z-index: 10000; - align-items: center; - justify-content: center; - } - - :host([visible]) { - display: flex; - } - - .dialog { - background: var(--background-color); - border: 1px solid var(--border-color); - border-radius: 8px; - padding: 24px; - max-width: 800px; - max-height: 80vh; - overflow-y: auto; - } - - h2 { - margin: 0 0 16px 0; - color: var(--text-color); - font-size: 18px; - font-weight: 500; - } - - .sources-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 12px; - margin-bottom: 16px; - } - - .source-item { - background: var(--input-background); - border: 2px solid transparent; - border-radius: 6px; - padding: 12px; - cursor: pointer; - transition: all 0.2s ease; - } - - .source-item:hover { - border-color: var(--border-default); - background: var(--button-hover); - } - - .source-item.selected { - border-color: var(--accent-color); - background: var(--button-hover); - } - - .source-thumbnail { - width: 100%; - height: 120px; - object-fit: contain; - background: #1a1a1a; - border-radius: 4px; - margin-bottom: 8px; - } - - .source-name { - color: var(--text-color); - font-size: 13px; - text-align: center; - word-break: break-word; - } - - .buttons { - display: flex; - gap: 8px; - justify-content: flex-end; - } - - button { - background: var(--button-background); - color: var(--text-color); - border: 1px solid var(--border-color); - padding: 8px 16px; - border-radius: 3px; - cursor: pointer; - font-size: 13px; - transition: background-color 0.1s ease; - } - - button:hover { - background: var(--button-hover); - } - - button.primary { - background: var(--accent-color); - color: white; - border-color: var(--accent-color); - } - - button.primary:hover { - background: var(--accent-hover); - } - - button:disabled { - opacity: 0.5; - cursor: not-allowed; - } - `; - - constructor() { - super(); - this.sources = []; - this.visible = false; - this.selectedSource = null; - } - - selectSource(source) { - this.selectedSource = source; - this.requestUpdate(); - } - - confirm() { - if (this.selectedSource) { - this.dispatchEvent( - new CustomEvent('source-selected', { - detail: { source: this.selectedSource }, - }) - ); - } - } - - cancel() { - this.dispatchEvent(new CustomEvent('cancelled')); - } - - render() { - return html` -
-

Choose screen or window to share

-
- ${this.sources.map( - source => html` -
this.selectSource(source)} - > - ${source.name} -
${source.name}
-
- ` - )} -
-
- - -
-
- `; - } -} - -customElements.define('screen-picker-dialog', ScreenPickerDialog); diff --git a/src/components/views/sharedPageStyles.js b/src/components/views/sharedPageStyles.js new file mode 100644 index 0000000..7b84743 --- /dev/null +++ b/src/components/views/sharedPageStyles.js @@ -0,0 +1,172 @@ +import { css } from '../../assets/lit-core-2.7.4.min.js'; + +export const unifiedPageStyles = css` + * { + box-sizing: border-box; + font-family: var(--font); + cursor: default; + user-select: none; + } + + :host { + display: block; + height: 100%; + } + + .unified-page { + height: 100%; + overflow-y: auto; + padding: var(--space-lg); + background: var(--bg-app); + } + + .unified-wrap { + width: 100%; + max-width: 1160px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: var(--space-md); + min-height: 100%; + } + + .page-title { + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + margin-bottom: 4px; + } + + .page-subtitle { + color: var(--text-muted); + font-size: var(--font-size-sm); + } + + .surface { + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--bg-surface); + padding: var(--space-md); + } + + .surface-title { + color: var(--text-primary); + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); + margin-bottom: 4px; + } + + .surface-subtitle { + color: var(--text-muted); + font-size: var(--font-size-xs); + margin-bottom: var(--space-md); + } + + .form-grid { + display: flex; + flex-direction: column; + gap: var(--space-sm); + } + + .form-row { + display: flex; + flex-direction: column; + gap: var(--space-sm); + } + + .form-group { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-md); + } + + .form-group.vertical { + flex-direction: column; + align-items: stretch; + } + + .form-label { + color: var(--text-secondary); + font-size: var(--font-size-sm); + white-space: nowrap; + flex-shrink: 0; + } + + .form-help { + color: var(--text-muted); + font-size: var(--font-size-xs); + line-height: 1.4; + } + + .control { + width: 200px; + background: var(--bg-elevated); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 8px 12px; + font-size: var(--font-size-sm); + transition: border-color var(--transition), box-shadow var(--transition); + } + + .control:hover:not(:focus) { + border-color: var(--border-strong); + } + + .control:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 1px var(--accent); + } + + select.control { + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b6b6b' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 8px center; + background-repeat: no-repeat; + background-size: 12px; + padding-right: 28px; + cursor: pointer; + } + + textarea.control { + width: 100%; + min-height: 100px; + resize: vertical; + line-height: 1.45; + } + + .chip { + display: inline-flex; + align-items: center; + border-radius: var(--radius-sm); + background: var(--bg-elevated); + color: var(--text-secondary); + padding: 2px 8px; + font-size: var(--font-size-xs); + font-family: var(--font-mono); + } + + .pill { + border: 1px solid var(--border); + border-radius: 999px; + padding: 2px 8px; + font-size: var(--font-size-xs); + color: var(--text-muted); + } + + .muted { + color: var(--text-muted); + } + + .danger { + color: var(--danger); + } + + @media (max-width: 640px) { + .unified-page { + padding: var(--space-md); + } + } +`; diff --git a/src/index.html b/src/index.html index 2545d0c..e99b162 100644 --- a/src/index.html +++ b/src/index.html @@ -5,75 +5,112 @@ Screen and Audio Capture - - - -
-
-
Click and drag to select region • ESC to cancel
- - - - `; - - regionSelectionWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(htmlContent)}`); - - return new Promise(resolve => { - ipcMain.once('region-selected', (event, rect) => { - if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { - regionSelectionWindow.close(); - regionSelectionWindow = null; - } - if (wasVisible) { - mainWindow.showInactive(); - } - resolve({ success: true, rect }); - }); - - ipcMain.once('region-selection-cancelled', () => { - if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { - regionSelectionWindow.close(); - regionSelectionWindow = null; - } - if (wasVisible) { - mainWindow.showInactive(); - } - resolve({ success: false, cancelled: true }); - }); - - // Also handle window close - regionSelectionWindow.on('closed', () => { - regionSelectionWindow = null; - if (wasVisible && !mainWindow.isDestroyed()) { - mainWindow.showInactive(); - } - }); - }); - } catch (error) { - console.error('Error starting region selection:', error); - if (regionSelectionWindow && !regionSelectionWindow.isDestroyed()) { - regionSelectionWindow.close(); - regionSelectionWindow = null; - } - if (!mainWindow.isDestroyed()) { - mainWindow.showInactive(); - } - return { success: false, error: error.message }; - } - }); - - // Get available screen sources for picker - ipcMain.handle('get-screen-sources', async () => { - try { - const { desktopCapturer } = require('electron'); - const sources = await desktopCapturer.getSources({ - types: ['screen', 'window'], - thumbnailSize: { width: 150, height: 150 }, - }); - - return { - success: true, - sources: sources.map(source => ({ - id: source.id, - name: source.name, - thumbnail: source.thumbnail.toDataURL(), - })), - }; - } catch (error) { - console.error('Error getting screen sources:', error); - return { success: false, error: error.message }; - } + // 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 }; }); } diff --git a/src/utils/windowResize.js b/src/utils/windowResize.js index c91b517..bffea90 100644 --- a/src/utils/windowResize.js +++ b/src/utils/windowResize.js @@ -12,4 +12,4 @@ export async function resizeLayout() { } catch (error) { console.error('Error resizing window:', error); } -} +} \ No newline at end of file From bfd76dc0c1a125c321e0d0b740d55bbdd7648e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sat, 14 Feb 2026 04:28:29 +0300 Subject: [PATCH 02/12] Add logging for transcription handling and disable proactive audio --- src/utils/gemini.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/utils/gemini.js b/src/utils/gemini.js index 22465a9..572f501 100644 --- a/src/utils/gemini.js +++ b/src/utils/gemini.js @@ -473,10 +473,13 @@ async function initializeGeminiSession(apiKey, customPrompt = '', profile = 'int // Handle input transcription (what was spoken) if (message.serverContent?.inputTranscription?.results) { - currentTranscription += formatSpeakerResults(message.serverContent.inputTranscription.results); + const transcribed = formatSpeakerResults(message.serverContent.inputTranscription.results); + console.log('Got transcription (results):', transcribed); + currentTranscription += transcribed; } else if (message.serverContent?.inputTranscription?.text) { const text = message.serverContent.inputTranscription.text; if (text.trim() !== '') { + console.log('Got transcription (text):', text); currentTranscription += text; } } @@ -485,18 +488,23 @@ async function initializeGeminiSession(apiKey, customPrompt = '', profile = 'int // if (message.serverContent?.outputTranscription?.text) { ... } if (message.serverContent?.generationComplete) { + console.log('Generation complete. Current transcription:', `"${currentTranscription}"`); if (currentTranscription.trim() !== '') { + console.log('Sending to', hasGroqKey() ? 'Groq' : 'Gemma'); if (hasGroqKey()) { sendToGroq(currentTranscription); } else { sendToGemma(currentTranscription); } currentTranscription = ''; + } else { + console.log('Transcription is empty, not sending to LLM'); } messageBuffer = ''; } if (message.serverContent?.turnComplete) { + console.log('Turn complete'); sendToRenderer('update-status', 'Listening...'); } }, @@ -524,15 +532,10 @@ async function initializeGeminiSession(apiKey, customPrompt = '', profile = 'int }, config: { responseModalities: [Modality.AUDIO], - proactivity: { proactiveAudio: true }, + proactivity: { proactiveAudio: false }, outputAudioTranscription: {}, + inputAudioTranscription: {}, tools: enabledTools, - // Enable speaker diarization - // inputAudioTranscription: { - // enableSpeakerDiarization: true, - // minSpeakerCount: 2, - // maxSpeakerCount: 2, - // }, contextWindowCompression: { slidingWindow: {} }, speechConfig: { languageCode: language }, systemInstruction: { From bd62cf552437a798b1e852fb40cc3b26e84b7514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sat, 14 Feb 2026 20:18:02 +0300 Subject: [PATCH 03/12] Add OpenAI-compatible API support with configuration management and response handling --- src/components/views/MainView.js | 119 ++++++++++++++++-- src/storage.js | 25 +++- src/utils/gemini.js | 208 +++++++++++++++++++++++++++++-- src/utils/renderer.js | 7 ++ 4 files changed, 340 insertions(+), 19 deletions(-) diff --git a/src/components/views/MainView.js b/src/components/views/MainView.js index 92289f0..e92f5e8 100644 --- a/src/components/views/MainView.js +++ b/src/components/views/MainView.js @@ -414,6 +414,10 @@ export class MainView extends LitElement { _geminiKey: { state: true }, _groqKey: { state: true }, _openaiKey: { state: true }, + _openaiCompatibleApiKey: { state: true }, + _openaiCompatibleBaseUrl: { state: true }, + _openaiCompatibleModel: { state: true }, + _responseProvider: { state: true }, _tokenError: { state: true }, _keyError: { state: true }, // Local AI state @@ -437,6 +441,10 @@ export class MainView extends LitElement { this._geminiKey = ''; this._groqKey = ''; this._openaiKey = ''; + this._openaiCompatibleApiKey = ''; + this._openaiCompatibleBaseUrl = ''; + this._openaiCompatibleModel = ''; + this._responseProvider = 'gemini'; this._tokenError = false; this._keyError = false; this._showLocalHelp = false; @@ -467,6 +475,15 @@ export class MainView extends LitElement { this._geminiKey = await cheatingDaddy.storage.getApiKey().catch(() => '') || ''; this._groqKey = await cheatingDaddy.storage.getGroqApiKey().catch(() => '') || ''; this._openaiKey = creds.openaiKey || ''; + + // Load OpenAI-compatible config + const openaiConfig = await cheatingDaddy.storage.getOpenAICompatibleConfig().catch(() => ({})); + this._openaiCompatibleApiKey = openaiConfig.apiKey || ''; + this._openaiCompatibleBaseUrl = openaiConfig.baseUrl || ''; + this._openaiCompatibleModel = openaiConfig.model || ''; + + // Load response provider preference + this._responseProvider = prefs.responseProvider || 'gemini'; // Load local AI settings this._ollamaHost = prefs.ollamaHost || 'http://127.0.0.1:11434'; @@ -631,6 +648,42 @@ export class MainView extends LitElement { this.requestUpdate(); } + async _saveOpenAICompatibleApiKey(val) { + this._openaiCompatibleApiKey = val; + await cheatingDaddy.storage.setOpenAICompatibleConfig( + val, + this._openaiCompatibleBaseUrl, + this._openaiCompatibleModel + ); + this.requestUpdate(); + } + + async _saveOpenAICompatibleBaseUrl(val) { + this._openaiCompatibleBaseUrl = val; + await cheatingDaddy.storage.setOpenAICompatibleConfig( + this._openaiCompatibleApiKey, + val, + this._openaiCompatibleModel + ); + this.requestUpdate(); + } + + async _saveOpenAICompatibleModel(val) { + this._openaiCompatibleModel = val; + await cheatingDaddy.storage.setOpenAICompatibleConfig( + this._openaiCompatibleApiKey, + this._openaiCompatibleBaseUrl, + val + ); + this.requestUpdate(); + } + + async _saveResponseProvider(val) { + this._responseProvider = val; + await cheatingDaddy.storage.updatePreference('responseProvider', val); + this.requestUpdate(); + } + async _saveOllamaHost(val) { this._ollamaHost = val; await cheatingDaddy.storage.updatePreference('ollamaHost', val); @@ -715,29 +768,75 @@ export class MainView extends LitElement { this._saveGeminiKey(e.target.value)} class=${this._keyError ? 'error' : ''} />
- this.onExternalLink('https://aistudio.google.com/apikey')}>Get Gemini key + this.onExternalLink('https://aistudio.google.com/apikey')}>Get Gemini key - Always used for audio transcription
- - this._saveGroqKey(e.target.value)} - /> + +
- this.onExternalLink('https://console.groq.com/keys')}>Get Groq key + Choose which API to use for generating responses
+ ${this._responseProvider === 'groq' ? html` +
+ + this._saveGroqKey(e.target.value)} + /> +
+ this.onExternalLink('https://console.groq.com/keys')}>Get Groq key +
+
+ ` : ''} + + ${this._responseProvider === 'openai-compatible' ? html` +
+ +
+ this._saveOpenAICompatibleApiKey(e.target.value)} + /> + this._saveOpenAICompatibleBaseUrl(e.target.value)} + /> + this._saveOpenAICompatibleModel(e.target.value)} + /> +
+
+ Use OpenRouter, DeepSeek, Together AI, or any OpenAI-compatible API +
+
+ ` : ''} + ${this._renderStartButton()} `; } diff --git a/src/storage.js b/src/storage.js index c09ad52..d38918c 100644 --- a/src/storage.js +++ b/src/storage.js @@ -13,7 +13,10 @@ const DEFAULT_CONFIG = { const DEFAULT_CREDENTIALS = { apiKey: '', - groqApiKey: '' + groqApiKey: '', + openaiCompatibleApiKey: '', + openaiCompatibleBaseUrl: '', + openaiCompatibleModel: '' }; const DEFAULT_PREFERENCES = { @@ -27,6 +30,7 @@ const DEFAULT_PREFERENCES = { fontSize: 'medium', backgroundTransparency: 0.8, googleSearchEnabled: false, + responseProvider: 'gemini', ollamaHost: 'http://127.0.0.1:11434', ollamaModel: 'llama3.1', whisperModel: 'Xenova/whisper-small', @@ -204,6 +208,23 @@ function setGroqApiKey(groqApiKey) { return setCredentials({ groqApiKey }); } +function getOpenAICompatibleConfig() { + const creds = getCredentials(); + return { + apiKey: creds.openaiCompatibleApiKey || '', + baseUrl: creds.openaiCompatibleBaseUrl || '', + model: creds.openaiCompatibleModel || '' + }; +} + +function setOpenAICompatibleConfig(apiKey, baseUrl, model) { + return setCredentials({ + openaiCompatibleApiKey: apiKey, + openaiCompatibleBaseUrl: baseUrl, + openaiCompatibleModel: model + }); +} + // ============ PREFERENCES ============ function getPreferences() { @@ -500,6 +521,8 @@ module.exports = { setApiKey, getGroqApiKey, setGroqApiKey, + getOpenAICompatibleConfig, + setOpenAICompatibleConfig, // Preferences getPreferences, diff --git a/src/utils/gemini.js b/src/utils/gemini.js index 572f501..a06315c 100644 --- a/src/utils/gemini.js +++ b/src/utils/gemini.js @@ -3,7 +3,7 @@ const { BrowserWindow, ipcMain } = require('electron'); const { spawn } = require('child_process'); const { saveDebugAudio } = require('../audioUtils'); const { getSystemPrompt } = require('./prompts'); -const { getAvailableModel, incrementLimitCount, getApiKey, getGroqApiKey, incrementCharUsage, getModelForToday } = require('../storage'); +const { getAvailableModel, incrementLimitCount, getApiKey, getGroqApiKey, getOpenAICompatibleConfig, incrementCharUsage, getModelForToday } = require('../storage'); // Lazy-loaded to avoid circular dependency (localai.js imports from gemini.js) let _localai = null; @@ -15,6 +15,9 @@ function getLocalAi() { // Provider mode: 'byok' or 'local' let currentProviderMode = 'byok'; +// Response provider: 'gemini', 'groq', or 'openai-compatible' +let currentResponseProvider = 'gemini'; + // Groq conversation history for context let groqConversationHistory = []; @@ -205,6 +208,14 @@ function hasGroqKey() { return key && key.trim() != '' } +// helper to check if OpenAI-compatible API has been configured +function hasOpenAICompatibleConfig() { + const config = getOpenAICompatibleConfig(); + return config.apiKey && config.apiKey.trim() !== '' && + config.baseUrl && config.baseUrl.trim() !== '' && + config.model && config.model.trim() !== ''; +} + function trimConversationHistoryForGemma(history, maxChars=42000) { if(!history || history.length === 0) return []; let totalChars = 0; @@ -344,6 +355,128 @@ async function sendToGroq(transcription) { } } +async function sendToOpenAICompatible(transcription) { + const config = getOpenAICompatibleConfig(); + + if (!config.apiKey || !config.baseUrl || !config.model) { + console.log('OpenAI-compatible API not fully configured'); + return; + } + + if (!transcription || transcription.trim() === '') { + console.log('Empty transcription, skipping OpenAI-compatible API'); + return; + } + + console.log(`Sending to OpenAI-compatible API (${config.model}):`, transcription.substring(0, 100) + '...'); + + groqConversationHistory.push({ + role: 'user', + content: transcription.trim() + }); + + if (groqConversationHistory.length > 20) { + groqConversationHistory = groqConversationHistory.slice(-20); + } + + try { + // Ensure baseUrl ends with /v1/chat/completions or contains the full endpoint + let apiUrl = config.baseUrl.trim(); + if (!apiUrl.includes('/chat/completions')) { + // Remove trailing slash if present + apiUrl = apiUrl.replace(/\/$/, ''); + // Add OpenAI-compatible endpoint path + apiUrl = `${apiUrl}/v1/chat/completions`; + } + + console.log(`Using OpenAI-compatible endpoint: ${apiUrl}`); + + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${config.apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: config.model, + messages: [ + { role: 'system', content: currentSystemPrompt || 'You are a helpful assistant.' }, + ...groqConversationHistory + ], + stream: true, + temperature: 0.7, + max_tokens: 2048 + }) + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('OpenAI-compatible API error:', response.status, errorText); + sendToRenderer('update-status', `OpenAI API error: ${response.status}`); + return; + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let fullText = ''; + let isFirst = true; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n').filter(line => line.trim() !== ''); + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') continue; + + try { + const parsed = JSON.parse(data); + const content = parsed.choices?.[0]?.delta?.content; + + if (content) { + fullText += content; + sendToRenderer(isFirst ? 'new-response' : 'update-response', fullText); + isFirst = false; + } + } catch (e) { + // Ignore JSON parse errors from partial chunks + } + } + } + } + + // Clean up tags if present (for DeepSeek-style reasoning models) + const cleanText = stripThinkingTags(fullText); + if (cleanText !== fullText) { + sendToRenderer('update-response', cleanText); + } + + if (fullText.trim()) { + groqConversationHistory.push({ + role: 'assistant', + content: fullText.trim() + }); + + if (groqConversationHistory.length > 40) { + groqConversationHistory = groqConversationHistory.slice(-40); + } + + saveConversationTurn(transcription, fullText); + } + + console.log(`OpenAI-compatible API response completed (${config.model})`); + sendToRenderer('update-status', 'Listening...'); + + } catch (error) { + console.error('Error calling OpenAI-compatible API:', error); + sendToRenderer('update-status', 'OpenAI API error: ' + error.message); + } +} + async function sendToGemma(transcription) { const apiKey = getApiKey(); if (!apiKey) { @@ -442,6 +575,14 @@ async function initializeGeminiSession(apiKey, customPrompt = '', profile = 'int sessionParams = { apiKey, customPrompt, profile, language }; reconnectAttempts = 0; } + + // Load response provider preference + if (!isReconnect) { + const { getPreferences } = require('../storage'); + const prefs = getPreferences(); + currentResponseProvider = prefs.responseProvider || 'gemini'; + console.log('🔧 Response provider set to:', currentResponseProvider); + } const client = new GoogleGenAI({ vertexai: false, @@ -488,17 +629,32 @@ async function initializeGeminiSession(apiKey, customPrompt = '', profile = 'int // if (message.serverContent?.outputTranscription?.text) { ... } if (message.serverContent?.generationComplete) { - console.log('Generation complete. Current transcription:', `"${currentTranscription}"`); + console.log('✅ Generation complete. Current transcription:', `"${currentTranscription}"`); if (currentTranscription.trim() !== '') { - console.log('Sending to', hasGroqKey() ? 'Groq' : 'Gemma'); - if (hasGroqKey()) { - sendToGroq(currentTranscription); + // Use explicit user choice for response provider + if (currentResponseProvider === 'openai-compatible') { + if (hasOpenAICompatibleConfig()) { + console.log('📤 Sending to OpenAI-compatible API (user selected)'); + sendToOpenAICompatible(currentTranscription); + } else { + console.log('⚠️ OpenAI-compatible selected but not configured, falling back to Gemini'); + sendToGemma(currentTranscription); + } + } else if (currentResponseProvider === 'groq') { + if (hasGroqKey()) { + console.log('📤 Sending to Groq (user selected)'); + sendToGroq(currentTranscription); + } else { + console.log('⚠️ Groq selected but not configured, falling back to Gemini'); + sendToGemma(currentTranscription); + } } else { + console.log('📤 Sending to Gemini (user selected)'); sendToGemma(currentTranscription); } currentTranscription = ''; } else { - console.log('Transcription is empty, not sending to LLM'); + console.log('⚠️ Transcription is empty, not sending to LLM'); } messageBuffer = ''; } @@ -954,8 +1110,19 @@ function setupGeminiIpcHandlers(geminiSessionRef) { try { console.log('Sending text message:', text); - if (hasGroqKey()) { - sendToGroq(text.trim()); + // Use explicit user choice for response provider + if (currentResponseProvider === 'openai-compatible') { + if (hasOpenAICompatibleConfig()) { + sendToOpenAICompatible(text.trim()); + } else { + sendToGemma(text.trim()); + } + } else if (currentResponseProvider === 'groq') { + if (hasGroqKey()) { + sendToGroq(text.trim()); + } else { + sendToGemma(text.trim()); + } } else { sendToGemma(text.trim()); } @@ -1053,6 +1220,29 @@ function setupGeminiIpcHandlers(geminiSessionRef) { return { success: false, error: error.message }; } }); + + // OpenAI-compatible API configuration handlers + ipcMain.handle('set-openai-compatible-config', async (event, apiKey, baseUrl, model) => { + try { + const { setOpenAICompatibleConfig } = require('../storage'); + setOpenAICompatibleConfig(apiKey, baseUrl, model); + console.log('OpenAI-compatible config saved:', { baseUrl, model: model.substring(0, 30) }); + return { success: true }; + } catch (error) { + console.error('Error setting OpenAI-compatible config:', error); + return { success: false, error: error.message }; + } + }); + + ipcMain.handle('get-openai-compatible-config', async (event) => { + try { + const config = getOpenAICompatibleConfig(); + return { success: true, config }; + } catch (error) { + console.error('Error getting OpenAI-compatible config:', error); + return { success: false, error: error.message }; + } + }); } module.exports = { @@ -1071,4 +1261,6 @@ module.exports = { sendImageToGeminiHttp, setupGeminiIpcHandlers, formatSpeakerResults, + hasOpenAICompatibleConfig, + sendToOpenAICompatible, }; diff --git a/src/utils/renderer.js b/src/utils/renderer.js index 3ada501..b07a51b 100644 --- a/src/utils/renderer.js +++ b/src/utils/renderer.js @@ -56,6 +56,13 @@ const storage = { async setGroqApiKey(groqApiKey) { return ipcRenderer.invoke('storage:set-groq-api-key', groqApiKey); }, + async getOpenAICompatibleConfig() { + const result = await ipcRenderer.invoke('get-openai-compatible-config'); + return result.success ? result.config : { apiKey: '', baseUrl: '', model: '' }; + }, + async setOpenAICompatibleConfig(apiKey, baseUrl, model) { + return ipcRenderer.invoke('set-openai-compatible-config', apiKey, baseUrl, model); + }, // Preferences async getPreferences() { From 8b216bbb33add8fe9260ef28d8d5836c8168cf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=20=D0=93=D0=BB=D0=B0=D0=B7=D1=83?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sat, 14 Feb 2026 20:31:35 +0300 Subject: [PATCH 04/12] Rename project from "Cheating Daddy" to "Mastermind" across all configurations and components to reflect the new branding. --- forge.config.js | 12 ++++++------ package.json | 20 ++++++++++---------- src/components/app/AppHeader.js | 8 ++++---- src/components/app/CheatingDaddyApp.js | 4 ++-- src/components/views/MainView.js | 4 ++-- src/components/views/OnboardingView.js | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/forge.config.js b/forge.config.js index 576643f..cdf9d62 100644 --- a/forge.config.js +++ b/forge.config.js @@ -7,7 +7,7 @@ module.exports = { unpack: '**/{onnxruntime-node,onnxruntime-common,@huggingface/transformers,sharp,@img}/**', }, extraResource: ['./src/assets/SystemAudioDump'], - name: 'Cheating Daddy', + name: 'Mastermind', icon: 'src/assets/logo', // use `security find-identity -v -p codesigning` to find your identity // for macos signing @@ -32,9 +32,9 @@ module.exports = { { name: '@electron-forge/maker-squirrel', config: { - name: 'cheating-daddy', - productName: 'Cheating Daddy', - shortcutName: 'Cheating Daddy', + name: 'mastermind', + productName: 'Mastermind', + shortcutName: 'Mastermind', createDesktopShortcut: true, createStartMenuShortcut: true, }, @@ -48,8 +48,8 @@ module.exports = { platforms: ['linux'], config: { options: { - name: 'Cheating Daddy', - productName: 'Cheating Daddy', + name: 'Mastermind', + productName: 'Mastermind', genericName: 'AI Assistant', description: 'AI assistant for interviews and learning', categories: ['Development', 'Education'], diff --git a/package.json b/package.json index b8314af..7860d3a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "cheating-daddy", - "productName": "cheating-daddy", + "name": "mastermind", + "productName": "Mastermind", "version": "0.7.0", - "description": "cheating daddy", + "description": "Mastermind AI assistant", "main": "src/index.js", "scripts": { "start": "electron-forge start", @@ -12,15 +12,15 @@ "lint": "echo \"No linting configured\"" }, "keywords": [ - "cheating daddy", - "cheating daddy ai", - "cheating daddy ai assistant", - "cheating daddy ai assistant for interviews", - "cheating daddy ai assistant for interviews" + "mastermind", + "mastermind ai", + "mastermind ai assistant", + "mastermind ai assistant for interviews", + "mastermind ai assistant for interviews" ], "author": { - "name": "sohzm", - "email": "sohambharambe9@gmail.com" + "name": "ShiftyX1", + "email": "lead@pyserve.org" }, "license": "GPL-3.0", "dependencies": { diff --git a/src/components/app/AppHeader.js b/src/components/app/AppHeader.js index c6bc2ff..3088573 100644 --- a/src/components/app/AppHeader.js +++ b/src/components/app/AppHeader.js @@ -242,15 +242,15 @@ export class AppHeader extends LitElement { getViewTitle() { const titles = { - onboarding: 'Welcome to Cheating Daddy', - main: 'Cheating Daddy', + onboarding: 'Welcome to Mastermind', + main: 'Mastermind', customize: 'Customize', help: 'Help & Shortcuts', history: 'Conversation History', advanced: 'Advanced Tools', - assistant: 'Cheating Daddy', + assistant: 'Mastermind', }; - return titles[this.currentView] || 'Cheating Daddy'; + return titles[this.currentView] || 'Mastermind'; } getElapsedTime() { diff --git a/src/components/app/CheatingDaddyApp.js b/src/components/app/CheatingDaddyApp.js index 8f29f40..7e56c45 100644 --- a/src/components/app/CheatingDaddyApp.js +++ b/src/components/app/CheatingDaddyApp.js @@ -399,7 +399,7 @@ export class CheatingDaddyApp extends LitElement { this._localVersion = await cheatingDaddy.getVersion(); this.requestUpdate(); - const res = await fetch('https://raw.githubusercontent.com/sohzm/cheating-daddy/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; @@ -782,7 +782,7 @@ export class CheatingDaddyApp extends LitElement { return html`