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..952a21c 100644 --- a/forge.config.js +++ b/forge.config.js @@ -1,101 +1,83 @@ -const { FusesPlugin } = require('@electron-forge/plugin-fuses'); -const { FuseV1Options, FuseVersion } = require('@electron/fuses'); -const path = require('path'); -const fs = require('fs'); +const { FusesPlugin } = require("@electron-forge/plugin-fuses"); +const { FuseV1Options, FuseVersion } = require("@electron/fuses"); module.exports = { - packagerConfig: { - asar: true, - extraResource: ['./src/assets/SystemAudioDump'], - name: 'Mastermind', - 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 - // osxSign: { - // identity: '-', // ad-hoc signing (no Apple Developer account needed) - // optionsForFile: (filePath) => { - // return { - // entitlements: 'entitlements.plist', - // }; - // }, - // }, - // notarize is off - requires Apple Developer account - // osxNotarize: { - // appleId: 'your apple id', - // appleIdPassword: 'app specific password', - // teamId: 'your team id', - // }, + packagerConfig: { + asar: { + unpack: + "**/{onnxruntime-node,onnxruntime-common,@huggingface/transformers,sharp,@img}/**", }, - rebuildConfig: {}, - makers: [ - { - name: '@electron-forge/maker-squirrel', - config: { - name: 'mastermind', - productName: 'Mastermind', - shortcutName: 'Mastermind', - createDesktopShortcut: true, - createStartMenuShortcut: true, - }, + extraResource: ["./src/assets/SystemAudioDump"], + name: "Mastermind", + icon: "src/assets/logo", + // use `security find-identity -v -p codesigning` to find your identity + // for macos signing + // also fuck apple + // osxSign: { + // identity: '', + // optionsForFile: (filePath) => { + // return { + // entitlements: 'entitlements.plist', + // }; + // }, + // }, + // notarize if off cuz i ran this for 6 hours and it still didnt finish + // osxNotarize: { + // appleId: 'your apple id', + // appleIdPassword: 'app specific password', + // teamId: 'your team id', + // }, + }, + rebuildConfig: { + // Ensure onnxruntime-node is rebuilt against Electron's Node.js headers + // so the native binding matches the ABI used in packaged builds. + onlyModules: ["onnxruntime-node", "sharp"], + }, + makers: [ + { + name: "@electron-forge/maker-squirrel", + config: { + name: "mastermind", + productName: "Mastermind", + shortcutName: "Mastermind", + createDesktopShortcut: true, + createStartMenuShortcut: true, + }, + }, + { + name: "@electron-forge/maker-dmg", + platforms: ["darwin"], + }, + { + name: "@reforged/maker-appimage", + platforms: ["linux"], + config: { + options: { + name: "Mastermind", + productName: "Mastermind", + genericName: "AI Assistant", + description: "AI assistant for interviews and learning", + categories: ["Development", "Education"], + icon: "src/assets/logo.png", }, - { - name: '@electron-forge/maker-dmg', - platforms: ['darwin'], - config: { - name: 'Mastermind', - format: 'ULFO', - }, - }, - { - name: '@reforged/maker-appimage', - platforms: ['linux'], - config: { - options: { - name: 'Mastermind', - productName: 'Mastermind', - genericName: 'AI Assistant', - description: 'AI assistant for video calls, interviews, presentations, and meetings', - categories: ['Development', 'Education'], - icon: 'src/assets/logo.png', - }, - }, - }, - ], - plugins: [ - { - name: '@electron-forge/plugin-auto-unpack-natives', - config: {}, - }, - // Fuses are used to enable/disable various Electron functionality - // at package time, before code signing the application - new FusesPlugin({ - version: FuseVersion.V1, - [FuseV1Options.RunAsNode]: false, - [FuseV1Options.EnableCookieEncryption]: true, - [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, - [FuseV1Options.EnableNodeCliInspectArguments]: false, - [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, - [FuseV1Options.OnlyLoadAppFromAsar]: true, - }), - ], + }, + }, + ], + plugins: [ + { + name: "@electron-forge/plugin-auto-unpack-natives", + config: {}, + }, + // Fuses are used to enable/disable various Electron functionality + // at package time, before code signing the application + new FusesPlugin({ + version: FuseVersion.V1, + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + }), + ], }; diff --git a/package.json b/package.json index a1738d9..2cc937d 100644 --- a/package.json +++ b/package.json @@ -1,46 +1,55 @@ { - "name": "mastermind", - "productName": "mastermind", - "version": "0.6.0", - "description": "Mastermind", - "main": "src/index.js", - "scripts": { - "start": "electron-forge start", - "package": "electron-forge package", - "make": "electron-forge make", - "publish": "electron-forge publish", - "lint": "echo \"No linting configured\"" - }, - "keywords": [ - "mastermind", - "mastermind ai", - "mastermind ai assistant", - "mastermind ai assistant for interviews", - "mastermind ai assistant for interviews" - ], - "author": { - "name": "ShiftyX1", - "email": "lead@pyserve.org" - }, - "license": "GPL-3.0", - "dependencies": { - "@google/genai": "^1.35.0", - "electron-squirrel-startup": "^1.0.1", - "openai": "^6.16.0", - "ws": "^8.18.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" + "name": "mastermind", + "productName": "Mastermind", + "version": "0.7.0", + "description": "Mastermind AI assistant", + "main": "src/index.js", + "scripts": { + "start": "electron-forge start", + "package": "electron-forge package", + "make": "electron-forge make", + "publish": "electron-forge publish", + "lint": "echo \"No linting configured\"", + "postinstall": "electron-rebuild -f -w onnxruntime-node" + }, + "keywords": [ + "mastermind", + "mastermind ai", + "mastermind ai assistant", + "mastermind ai assistant for interviews", + "mastermind ai assistant for interviews" + ], + "author": { + "name": "ShiftyX1", + "email": "lead@pyserve.org" + }, + "license": "GPL-3.0", + "dependencies": { + "@google/genai": "^1.41.0", + "@huggingface/transformers": "^3.8.1", + "electron-squirrel-startup": "^1.0.1", + "ollama": "^0.6.3", + "openai": "^6.22.0", + "p-retry": "^4.6.2", + "ws": "^8.19.0" + }, + "devDependencies": { + "@electron/rebuild": "^3.7.1", + "@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..27608cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,59 +4,68 @@ 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 + ollama: + specifier: ^0.6.3 + version: 0.6.3 openai: - specifier: ^6.16.0 - version: 6.16.0(ws@8.19.0) + specifier: ^6.22.0 + version: 6.22.0(ws@8.19.0) + 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 +157,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 +184,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 +203,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 +426,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 +485,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 +555,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 +567,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 +742,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 +801,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 +815,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 +994,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 +1021,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 +1077,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 +1143,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 +1250,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 +1297,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 +1517,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 +1528,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 +1643,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 +1669,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 +1682,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 +1730,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,8 +1740,21 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - openai@6.16.0: - resolution: {integrity: sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==} + 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==} + + openai@6.22.0: + resolution: {integrity: sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw==} hasBin: true peerDependencies: ws: ^8.18.0 @@ -1585,6 +1809,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 +1880,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 +1892,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 +1917,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 +1996,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 +2049,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 +2061,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 +2188,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 +2214,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 +2251,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 +2313,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 +2331,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 +2341,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 +2402,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 +2449,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 +2470,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 +2504,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 +2597,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 +2630,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 +2660,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 +2688,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 +2731,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 +2757,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 +2775,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 +2792,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 +2825,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 +2966,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 +3057,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 +3109,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 +3119,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 +3166,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 +3190,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 +3404,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 +3416,7 @@ snapshots: bluebird@3.7.2: {} - boolean@3.2.0: - optional: true + boolean@3.2.0: {} bplist-creator@0.0.8: dependencies: @@ -3041,9 +3438,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 +3490,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 +3501,8 @@ snapshots: chownr@2.0.0: {} + chownr@3.0.0: {} + chrome-trace-event@1.0.4: {} clean-stack@2.2.0: {} @@ -3210,19 +3609,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 +3645,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 +3662,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 +3686,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 +3699,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 +3714,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 +3738,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 +3751,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 +3778,7 @@ snapshots: estraverse@5.3.0: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} events@3.3.0: {} @@ -3466,6 +3858,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 +4030,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 +4041,6 @@ snapshots: dependencies: define-properties: 1.2.1 gopd: 1.2.0 - optional: true google-auth-library@10.5.0: dependencies: @@ -3664,8 +4056,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 +4081,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 +4217,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 +4233,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 +4269,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 +4294,7 @@ snapshots: lodash.get@4.4.2: {} - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -3918,6 +4309,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 +4319,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 +4351,6 @@ snapshots: matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 - optional: true mem@4.3.0: dependencies: @@ -4038,6 +4430,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 +4454,7 @@ snapshots: mute-stream@1.0.0: {} - nan@2.24.0: + nan@2.25.0: optional: true negotiator@0.6.4: {} @@ -4067,13 +4463,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 +4504,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,7 +4518,26 @@ snapshots: dependencies: mimic-fn: 2.1.0 - openai@6.16.0(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 + + openai@6.22.0(ws@8.19.0): optionalDependencies: ws: 8.19.0 @@ -4165,6 +4583,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 +4636,8 @@ snapshots: pify@2.3.0: {} + platform@1.3.6: {} + plist@3.1.0: dependencies: '@xmldom/xmldom': 0.8.11 @@ -4223,7 +4648,7 @@ snapshots: dependencies: commander: 9.5.0 - prettier@3.7.4: {} + prettier@3.8.1: {} proc-log@2.0.1: {} @@ -4236,6 +4661,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 +4757,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -4342,7 +4784,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 +4800,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 +4904,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 +4972,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 +5035,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 +5083,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 +5098,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 +5110,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 +5122,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 +5179,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..3088573 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() { @@ -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..6fecf2d --- /dev/null +++ b/src/components/app/CheatingDaddyApp.js @@ -0,0 +1,1128 @@ +import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js"; +import { MainView } from "../views/MainView.js"; +import { CustomizeView } from "../views/CustomizeView.js"; +import { HelpView } from "../views/HelpView.js"; +import { HistoryView } from "../views/HistoryView.js"; +import { AssistantView } from "../views/AssistantView.js"; +import { OnboardingView } from "../views/OnboardingView.js"; +import { AICustomizeView } from "../views/AICustomizeView.js"; +import { FeedbackView } from "../views/FeedbackView.js"; + +export class CheatingDaddyApp extends LitElement { + static styles = css` + * { + box-sizing: border-box; + font-family: var(--font); + margin: 0; + padding: 0; + cursor: default; + user-select: none; + } + + :host { + display: block; + width: 100%; + height: 100vh; + background: var(--bg-app); + color: var(--text-primary); + } + + /* ── Full app shell: top bar + sidebar/content ── */ + + .app-shell { + display: flex; + height: 100vh; + overflow: hidden; + } + + .top-drag-bar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 9999; + display: flex; + align-items: center; + height: 38px; + background: transparent; + } + + .drag-region { + flex: 1; + height: 100%; + -webkit-app-region: drag; + } + + .top-drag-bar.hidden { + display: none; + } + + .traffic-lights { + display: flex; + align-items: center; + gap: 8px; + padding: 0 var(--space-md); + height: 100%; + -webkit-app-region: no-drag; + } + + .traffic-light { + width: 12px; + height: 12px; + border-radius: 50%; + border: none; + cursor: pointer; + padding: 0; + transition: opacity 0.15s ease; + } + + .traffic-light:hover { + opacity: 0.8; + } + + .traffic-light.close { + background: #ff5f57; + } + + .traffic-light.minimize { + background: #febc2e; + } + + .traffic-light.maximize { + background: #28c840; + } + + .sidebar { + width: var(--sidebar-width); + min-width: var(--sidebar-width); + background: var(--bg-surface); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + padding: 42px 0 var(--space-md) 0; + transition: + width var(--transition), + min-width var(--transition), + opacity var(--transition); + } + + .sidebar.hidden { + width: 0; + min-width: 0; + padding: 0; + overflow: hidden; + border-right: none; + opacity: 0; + } + + .sidebar-brand { + padding: var(--space-sm) var(--space-lg); + padding-top: var(--space-md); + margin-bottom: var(--space-lg); + } + + .sidebar-brand h1 { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + letter-spacing: -0.01em; + } + + .sidebar-nav { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-xs); + padding: 0 var(--space-sm); + -webkit-app-region: no-drag; + } + + .nav-item { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-md); + color: var(--text-secondary); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + transition: + color var(--transition), + background var(--transition); + border: none; + background: none; + width: 100%; + text-align: left; + } + + .nav-item:hover { + color: var(--text-primary); + background: var(--bg-hover); + } + + .nav-item.active { + color: var(--text-primary); + background: var(--bg-elevated); + } + + .nav-item svg { + width: 20px; + height: 20px; + flex-shrink: 0; + } + + .sidebar-footer { + padding: var(--space-sm); + margin-top: var(--space-sm); + -webkit-app-region: no-drag; + } + + .update-btn { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + padding: var(--space-sm) var(--space-md); + border-radius: var(--radius-md); + border: 1px solid rgba(239, 68, 68, 0.2); + background: rgba(239, 68, 68, 0.08); + color: var(--danger); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + cursor: pointer; + text-align: left; + transition: + background var(--transition), + border-color var(--transition); + animation: update-wobble 5s ease-in-out infinite; + } + + .update-btn:hover { + background: rgba(239, 68, 68, 0.14); + border-color: rgba(239, 68, 68, 0.35); + } + + @keyframes update-wobble { + 0%, + 90%, + 100% { + transform: rotate(0deg); + } + 92% { + transform: rotate(-2deg); + } + 94% { + transform: rotate(2deg); + } + 96% { + transform: rotate(-1.5deg); + } + 98% { + transform: rotate(1.5deg); + } + } + + .update-btn svg { + width: 20px; + height: 20px; + flex-shrink: 0; + } + + .version-text { + font-size: var(--font-size-xs); + color: var(--text-muted); + padding: var(--space-xs) var(--space-md); + } + + /* ── Main content area ── */ + + .content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + background: var(--bg-app); + } + + /* Live mode top bar */ + .live-bar { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-md); + background: var(--bg-surface); + border-bottom: 1px solid var(--border); + height: 36px; + -webkit-app-region: drag; + } + + .live-bar-left { + display: flex; + align-items: center; + -webkit-app-region: no-drag; + z-index: 1; + } + + .live-bar-back { + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + cursor: pointer; + background: none; + border: none; + padding: var(--space-xs); + border-radius: var(--radius-sm); + transition: color var(--transition); + } + + .live-bar-back:hover { + color: var(--text-primary); + } + + .live-bar-back svg { + width: 14px; + height: 14px; + } + + .live-bar-center { + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: var(--font-size-xs); + color: var(--text-muted); + font-weight: var(--font-weight-medium); + white-space: nowrap; + pointer-events: none; + } + + .live-bar-right { + display: flex; + align-items: center; + gap: var(--space-md); + -webkit-app-region: no-drag; + z-index: 1; + } + + .live-bar-text { + font-size: var(--font-size-xs); + color: var(--text-muted); + font-family: var(--font-mono); + white-space: nowrap; + } + + .live-bar-text.clickable { + cursor: pointer; + transition: color var(--transition); + } + + .live-bar-text.clickable:hover { + color: var(--text-primary); + } + + /* Content inner */ + .content-inner { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + } + + .content-inner.live { + overflow: hidden; + display: flex; + flex-direction: column; + } + + /* Onboarding fills everything */ + .fullscreen { + position: fixed; + inset: 0; + z-index: 100; + background: var(--bg-app); + } + + ::-webkit-scrollbar { + width: 6px; + height: 6px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: 3px; + } + + ::-webkit-scrollbar-thumb:hover { + background: #444444; + } + `; + + static properties = { + currentView: { type: String }, + statusText: { type: String }, + startTime: { type: Number }, + isRecording: { type: Boolean }, + sessionActive: { type: Boolean }, + selectedProfile: { type: String }, + selectedLanguage: { type: String }, + responses: { type: Array }, + currentResponseIndex: { type: Number }, + selectedScreenshotInterval: { type: String }, + selectedImageQuality: { type: String }, + layoutMode: { type: String }, + _viewInstances: { type: Object, state: true }, + _isClickThrough: { state: true }, + _awaitingNewResponse: { state: true }, + shouldAnimateResponse: { type: Boolean }, + _storageLoaded: { state: true }, + _updateAvailable: { state: true }, + _whisperDownloading: { state: true }, + _whisperProgress: { state: true }, + }; + + constructor() { + super(); + this.currentView = "main"; + this.statusText = ""; + this.startTime = null; + this.isRecording = false; + this.sessionActive = false; + this.selectedProfile = "interview"; + this.selectedLanguage = "en-US"; + this.selectedScreenshotInterval = "5"; + this.selectedImageQuality = "medium"; + this.layoutMode = "normal"; + this.responses = []; + this.currentResponseIndex = -1; + this._viewInstances = new Map(); + this._isClickThrough = false; + this._awaitingNewResponse = false; + this._currentResponseIsComplete = true; + this.shouldAnimateResponse = false; + this._storageLoaded = false; + this._timerInterval = null; + this._updateAvailable = false; + this._whisperDownloading = false; + this._whisperProgress = null; + this._localVersion = ""; + + this._loadFromStorage(); + this._checkForUpdates(); + } + + async _checkForUpdates() { + try { + this._localVersion = await cheatingDaddy.getVersion(); + this.requestUpdate(); + + const res = await fetch( + "https://raw.githubusercontent.com/ShiftyX1/Mastermind/refs/heads/master/package.json", + ); + if (!res.ok) return; + const remote = await res.json(); + const remoteVersion = remote.version; + + const toNum = (v) => v.split(".").map(Number); + const [rMaj, rMin, rPatch] = toNum(remoteVersion); + const [lMaj, lMin, lPatch] = toNum(this._localVersion); + + if ( + rMaj > lMaj || + (rMaj === lMaj && rMin > lMin) || + (rMaj === lMaj && rMin === lMin && rPatch > lPatch) + ) { + this._updateAvailable = true; + this.requestUpdate(); + } + } catch (e) { + // silently ignore + } + } + + async _loadFromStorage() { + try { + const [config, prefs] = await Promise.all([ + cheatingDaddy.storage.getConfig(), + cheatingDaddy.storage.getPreferences(), + ]); + + this.currentView = config.onboarded ? "main" : "onboarding"; + this.selectedProfile = prefs.selectedProfile || "interview"; + this.selectedLanguage = prefs.selectedLanguage || "en-US"; + this.selectedScreenshotInterval = prefs.selectedScreenshotInterval || "5"; + this.selectedImageQuality = prefs.selectedImageQuality || "medium"; + this.layoutMode = config.layout || "normal"; + + this._storageLoaded = true; + this.requestUpdate(); + } catch (error) { + console.error("Error loading from storage:", error); + this._storageLoaded = true; + this.requestUpdate(); + } + } + + connectedCallback() { + super.connectedCallback(); + + if (window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.on("new-response", (_, response) => + this.addNewResponse(response), + ); + ipcRenderer.on("update-response", (_, response) => + this.updateCurrentResponse(response), + ); + ipcRenderer.on("update-status", (_, status) => this.setStatus(status)); + ipcRenderer.on("click-through-toggled", (_, isEnabled) => { + this._isClickThrough = isEnabled; + }); + ipcRenderer.on("reconnect-failed", (_, data) => + this.addNewResponse(data.message), + ); + ipcRenderer.on("whisper-downloading", (_, downloading) => { + this._whisperDownloading = downloading; + if (!downloading) this._whisperProgress = null; + }); + ipcRenderer.on("whisper-progress", (_, progress) => { + this._whisperProgress = progress; + }); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + this._stopTimer(); + if (window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.removeAllListeners("new-response"); + ipcRenderer.removeAllListeners("update-response"); + ipcRenderer.removeAllListeners("update-status"); + ipcRenderer.removeAllListeners("click-through-toggled"); + ipcRenderer.removeAllListeners("reconnect-failed"); + ipcRenderer.removeAllListeners("whisper-downloading"); + ipcRenderer.removeAllListeners("whisper-progress"); + } + } + + // ── Timer ── + + _startTimer() { + this._stopTimer(); + if (this.startTime) { + this._timerInterval = setInterval(() => this.requestUpdate(), 1000); + } + } + + _stopTimer() { + if (this._timerInterval) { + clearInterval(this._timerInterval); + this._timerInterval = null; + } + } + + getElapsedTime() { + if (!this.startTime) return "0:00"; + const elapsed = Math.floor((Date.now() - this.startTime) / 1000); + const h = Math.floor(elapsed / 3600); + const m = Math.floor((elapsed % 3600) / 60); + const s = elapsed % 60; + const pad = (n) => String(n).padStart(2, "0"); + if (h > 0) return `${h}:${pad(m)}:${pad(s)}`; + return `${m}:${pad(s)}`; + } + + // ── Status & Responses ── + + setStatus(text) { + this.statusText = text; + if ( + text.includes("Ready") || + text.includes("Listening") || + text.includes("Error") + ) { + this._currentResponseIsComplete = true; + } + } + + addNewResponse(response) { + const wasOnLatest = this.currentResponseIndex === this.responses.length - 1; + this.responses = [...this.responses, response]; + if (wasOnLatest || this.currentResponseIndex === -1) { + this.currentResponseIndex = this.responses.length - 1; + } + this._awaitingNewResponse = false; + this.requestUpdate(); + } + + updateCurrentResponse(response) { + if (this.responses.length > 0) { + this.responses = [...this.responses.slice(0, -1), response]; + } else { + this.addNewResponse(response); + } + this.requestUpdate(); + } + + // ── Navigation ── + + navigate(view) { + this.currentView = view; + this.requestUpdate(); + } + + async handleClose() { + if (this.currentView === "assistant") { + cheatingDaddy.stopCapture(); + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("close-session"); + } + this.sessionActive = false; + this._stopTimer(); + this.currentView = "main"; + } else { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("quit-application"); + } + } + } + + async _handleMinimize() { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("window-minimize"); + } + } + + async handleHideToggle() { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("toggle-window-visibility"); + } + } + + // ── Session start ── + + async handleStart() { + const prefs = await cheatingDaddy.storage.getPreferences(); + const providerMode = prefs.providerMode || "byok"; + + if (providerMode === "local") { + const success = await cheatingDaddy.initializeLocal(this.selectedProfile); + if (!success) { + const mainView = this.shadowRoot.querySelector("main-view"); + if (mainView && mainView.triggerApiKeyError) { + mainView.triggerApiKeyError(); + } + return; + } + } else { + const apiKey = await cheatingDaddy.storage.getApiKey(); + if (!apiKey || apiKey === "") { + const mainView = this.shadowRoot.querySelector("main-view"); + if (mainView && mainView.triggerApiKeyError) { + mainView.triggerApiKeyError(); + } + return; + } + + await cheatingDaddy.initializeGemini( + this.selectedProfile, + this.selectedLanguage, + ); + } + + cheatingDaddy.startCapture( + this.selectedScreenshotInterval, + this.selectedImageQuality, + ); + this.responses = []; + this.currentResponseIndex = -1; + this.startTime = Date.now(); + this.sessionActive = true; + this.currentView = "assistant"; + this._startTimer(); + } + + async handleAPIKeyHelp() { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke( + "open-external", + "https://cheatingdaddy.com/help/api-key", + ); + } + } + + async handleGroqAPIKeyHelp() { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke( + "open-external", + "https://console.groq.com/keys", + ); + } + } + + // ── Settings handlers ── + + async handleProfileChange(profile) { + this.selectedProfile = profile; + await cheatingDaddy.storage.updatePreference("selectedProfile", profile); + } + + async handleLanguageChange(language) { + this.selectedLanguage = language; + await cheatingDaddy.storage.updatePreference("selectedLanguage", language); + } + + async handleScreenshotIntervalChange(interval) { + this.selectedScreenshotInterval = interval; + await cheatingDaddy.storage.updatePreference( + "selectedScreenshotInterval", + interval, + ); + } + + async handleImageQualityChange(quality) { + this.selectedImageQuality = quality; + await cheatingDaddy.storage.updatePreference( + "selectedImageQuality", + quality, + ); + } + + async handleLayoutModeChange(layoutMode) { + this.layoutMode = layoutMode; + await cheatingDaddy.storage.updateConfig("layout", layoutMode); + if (window.require) { + try { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("update-sizes"); + } catch (error) { + console.error("Failed to update sizes:", error); + } + } + this.requestUpdate(); + } + + async handleExternalLinkClick(url) { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("open-external", url); + } + } + + async handleSendText(message) { + const result = await window.cheatingDaddy.sendTextMessage(message); + if (!result.success) { + this.setStatus("Error sending message: " + result.error); + } else { + this.setStatus("Message sent..."); + this._awaitingNewResponse = true; + } + } + + async handleExpandResponse() { + const result = await window.cheatingDaddy.expandLastResponse(); + if (!result.success) { + this.setStatus("Error expanding: " + (result.error || "Unknown error")); + } else { + this.setStatus("Expanding response..."); + this._awaitingNewResponse = true; + } + } + + handleResponseIndexChanged(e) { + this.currentResponseIndex = e.detail.index; + this.shouldAnimateResponse = false; + this.requestUpdate(); + } + + handleOnboardingComplete() { + this.currentView = "main"; + } + + updated(changedProperties) { + super.updated(changedProperties); + + if (changedProperties.has("currentView") && window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.send("view-changed", this.currentView); + } + } + + // ── Helpers ── + + _isLiveMode() { + return this.currentView === "assistant"; + } + + // ── Render ── + + renderCurrentView() { + switch (this.currentView) { + case "onboarding": + return html` + this.handleOnboardingComplete()} + .onClose=${() => this.handleClose()} + > + `; + + case "main": + return html` + this.handleProfileChange(p)} + .onStart=${() => this.handleStart()} + .onExternalLink=${(url) => this.handleExternalLinkClick(url)} + .whisperDownloading=${this._whisperDownloading} + .whisperProgress=${this._whisperProgress} + > + `; + + case "ai-customize": + return html` + this.handleProfileChange(p)} + > + `; + + case "customize": + return html` + this.handleProfileChange(p)} + .onLanguageChange=${(l) => this.handleLanguageChange(l)} + .onScreenshotIntervalChange=${(i) => + this.handleScreenshotIntervalChange(i)} + .onImageQualityChange=${(q) => this.handleImageQualityChange(q)} + .onLayoutModeChange=${(lm) => this.handleLayoutModeChange(lm)} + > + `; + + case "feedback": + return html``; + + case "help": + return html` this.handleExternalLinkClick(url)} + >`; + + case "history": + return html``; + + case "assistant": + return html` + this.handleSendText(msg)} + .onExpandResponse=${() => this.handleExpandResponse()} + .shouldAnimateResponse=${this.shouldAnimateResponse} + @response-index-changed=${this.handleResponseIndexChanged} + @response-animation-complete=${() => { + this.shouldAnimateResponse = false; + this._currentResponseIsComplete = true; + this.requestUpdate(); + }} + > + `; + + default: + return html`
Unknown view: ${this.currentView}
`; + } + } + + renderSidebar() { + const items = [ + { + id: "main", + label: "Home", + icon: html` + + + + + `, + }, + { + id: "ai-customize", + label: "AI Customization", + icon: html` + + `, + }, + { + id: "history", + label: "History", + icon: html` + + + + + `, + }, + { + id: "customize", + label: "Settings", + icon: html` + + + + + `, + }, + { + id: "feedback", + label: "Feedback", + icon: html` + + + + + `, + }, + { + id: "help", + label: "Help", + icon: html` + + + + + `, + }, + ]; + + return html` + + `; + } + + renderLiveBar() { + if (!this._isLiveMode()) return ""; + + const profileLabels = { + interview: "Interview", + sales: "Sales Call", + meeting: "Meeting", + presentation: "Presentation", + negotiation: "Negotiation", + exam: "Exam", + }; + + return html` +
+
+ +
+
+ ${profileLabels[this.selectedProfile] || "Session"} +
+
+ ${this.statusText + ? html`${this.statusText}` + : ""} + ${this.getElapsedTime()} + ${this._isClickThrough + ? html`[click through]` + : ""} + this.handleHideToggle()} + >[hide] +
+
+ `; + } + + render() { + // Onboarding is fullscreen, no sidebar + if (this.currentView === "onboarding") { + return html`
${this.renderCurrentView()}
`; + } + + const isLive = this._isLiveMode(); + + return html` +
+
+
+ + + +
+
+
+ ${this.renderSidebar()} +
+ ${isLive ? this.renderLiveBar() : ""} +
+ ${this.renderCurrentView()} +
+
+
+ `; + } +} + +customElements.define("cheating-daddy-app", CheatingDaddyApp); 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..3770192 100644 --- a/src/components/views/AssistantView.js +++ b/src/components/views/AssistantView.js @@ -1,830 +1,1189 @@ -import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; +import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js"; export class AssistantView extends LitElement { - static styles = css` - :host { - height: 100%; - display: flex; - flex-direction: column; - } + static styles = css` + :host { + height: 100%; + display: flex; + flex-direction: column; + } - * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - cursor: default; - } + * { + font-family: var(--font); + cursor: default; + } - .response-container { - height: calc(100% - 50px); - overflow-y: auto; - font-size: var(--response-font-size, 16px); - line-height: 1.6; - background: var(--bg-primary); - padding: 12px; - scroll-behavior: smooth; - user-select: text; - cursor: text; - } + /* ── Response area ── */ - .response-container * { - user-select: text; - cursor: text; - } + .response-container { + flex: 1; + overflow-y: auto; + 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 a { - cursor: pointer; - } + .response-container * { + user-select: text; + cursor: text; + } - /* Word display (no animation) */ - .response-container [data-word] { - display: inline-block; - } + .response-container a { + cursor: pointer; + } - /* Markdown styling */ - .response-container h1, - .response-container h2, - .response-container h3, - .response-container h4, - .response-container h5, - .response-container h6 { - margin: 1em 0 0.5em 0; - color: var(--text-color); - font-weight: 600; - } + .response-container [data-word] { + display: inline-block; + } - .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; - } + /* ── Markdown ── */ - .response-container p { - margin: 0.6em 0; - color: var(--text-color); - } + .response-container h1, + .response-container h2, + .response-container h3, + .response-container h4, + .response-container h5, + .response-container h6 { + margin: 1em 0 0.5em 0; + color: var(--text-primary); + font-weight: var(--font-weight-semibold); + } - .response-container ul, - .response-container ol { - margin: 0.6em 0; - padding-left: 1.5em; - color: var(--text-color); - } + .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 li { - margin: 0.3em 0; - } + .response-container p { + margin: 0.6em 0; + color: var(--text-primary); + } - .response-container blockquote { - margin: 0.8em 0; - padding: 0.5em 1em; - border-left: 2px solid var(--border-default); - background: var(--bg-secondary); - } + .response-container ul, + .response-container ol { + margin: 0.6em 0; + padding-left: 1.5em; + color: var(--text-primary); + } - .response-container code { - background: var(--bg-tertiary); - padding: 0.15em 0.4em; - border-radius: 3px; - font-family: 'SF Mono', Monaco, monospace; - font-size: 0.85em; - } + .response-container li { + margin: 0.3em 0; + } - .response-container pre { - background: var(--bg-secondary); - border: 1px solid var(--border-color); - border-radius: 3px; - padding: 12px; - overflow-x: auto; - margin: 0.8em 0; - } + .response-container blockquote { + margin: 0.8em 0; + padding: 0.5em 1em; + border-left: 2px solid var(--border-strong); + background: var(--bg-surface); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; + } - .response-container pre code { - background: none; - padding: 0; - } + .response-container code { + background: var(--bg-elevated); + padding: 0.15em 0.4em; + border-radius: var(--radius-sm); + font-family: var(--font-mono); + font-size: 0.85em; + } - .response-container a { - color: var(--text-color); - text-decoration: underline; - text-underline-offset: 2px; - } + .response-container pre { + 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; + position: relative; + } - .response-container strong, - .response-container b { - font-weight: 600; - } + .response-container pre::before { + content: attr(data-language); + position: absolute; + top: 0; + right: 0; + background: var(--bg-elevated); + color: var(--text-secondary); + padding: 4px 12px; + font-size: var(--font-size-xs); + font-family: var(--font-mono); + border: 1px solid var(--border); + border-top: none; + border-right: none; + border-bottom-left-radius: var(--radius-sm); + text-transform: uppercase; + letter-spacing: 0.5px; + } - .response-container hr { - border: none; - border-top: 1px solid var(--border-color); - margin: 1.5em 0; - } + .response-container pre code { + background: none; + padding: 0; + font-family: var(--font-mono); + font-size: 0.9em; + line-height: 1.5; + color: var(--text-primary); + } - .response-container table { - border-collapse: collapse; - width: 100%; - margin: 0.8em 0; - } + /* ── Syntax highlighting for code blocks ── */ + /* Default (Dark theme) */ + .response-container .hljs { + color: #c9d1d9; + background: transparent; + } - .response-container th, - .response-container td { - border: 1px solid var(--border-color); - padding: 8px; - text-align: left; - } + .response-container .hljs-doctag, + .response-container .hljs-keyword, + .response-container .hljs-meta .hljs-keyword, + .response-container .hljs-template-tag, + .response-container .hljs-template-variable, + .response-container .hljs-type, + .response-container .hljs-variable.language_ { + color: #ff7b72; + } - .response-container th { - background: var(--bg-secondary); - font-weight: 600; - } + .response-container .hljs-title, + .response-container .hljs-title.class_, + .response-container .hljs-title.class_.inherited__, + .response-container .hljs-title.function_ { + color: #d2a8ff; + } - .response-container::-webkit-scrollbar { - width: 8px; - } + .response-container .hljs-attr, + .response-container .hljs-attribute, + .response-container .hljs-literal, + .response-container .hljs-meta, + .response-container .hljs-number, + .response-container .hljs-operator, + .response-container .hljs-selector-attr, + .response-container .hljs-selector-class, + .response-container .hljs-selector-id, + .response-container .hljs-variable { + color: #79c0ff; + } - .response-container::-webkit-scrollbar-track { - background: transparent; - } + .response-container .hljs-meta .hljs-string, + .response-container .hljs-regexp, + .response-container .hljs-string { + color: #a5d6ff; + } - .response-container::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 4px; - } + .response-container .hljs-built_in, + .response-container .hljs-symbol { + color: #ffa657; + } - .response-container::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); - } + .response-container .hljs-code, + .response-container .hljs-comment, + .response-container .hljs-formula { + color: #8b949e; + } - .text-input-container { - display: flex; - gap: 8px; - margin-top: 8px; - align-items: center; - } + .response-container .hljs-name, + .response-container .hljs-quote, + .response-container .hljs-selector-pseudo, + .response-container .hljs-selector-tag { + color: #7ee787; + } - .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; - } + .response-container .hljs-subst { + color: #c9d1d9; + } - .text-input-container input:focus { - outline: none; - border-bottom-color: var(--text-color); - } + .response-container .hljs-section { + color: #1f6feb; + font-weight: 700; + } - .text-input-container input::placeholder { - color: var(--placeholder-color); - } + .response-container .hljs-bullet { + color: #f2cc60; + } - .nav-button { - background: transparent; - color: var(--text-secondary); - border: none; - padding: 6px; - border-radius: 3px; - font-size: 12px; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.1s ease; - } + .response-container .hljs-emphasis { + color: #c9d1d9; + font-style: italic; + } - .nav-button:hover { - background: var(--hover-background); - color: var(--text-color); - } + .response-container .hljs-strong { + color: #c9d1d9; + font-weight: 700; + } - .nav-button:disabled { - opacity: 0.3; - } + .response-container .hljs-addition { + color: #aff5b4; + background-color: #033a16; + } - .nav-button svg { - width: 18px; - height: 18px; - stroke: currentColor; - } + .response-container .hljs-deletion { + color: #ffdcd7; + background-color: #67060c; + } - .response-counter { - font-size: 11px; - color: var(--text-muted); - white-space: nowrap; - min-width: 50px; - text-align: center; - font-family: 'SF Mono', Monaco, monospace; - } + /* Light theme syntax highlighting */ + :host-context(body[data-theme-type="light"]) .response-container .hljs { + color: #24292f; + } - .screen-answer-btn { - display: flex; - align-items: center; - gap: 6px; - background: var(--btn-primary-bg, #ffffff); - color: var(--btn-primary-text, #000000); - border: none; - padding: 6px 12px; - border-radius: 20px; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s ease; - white-space: nowrap; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-doctag, + :host-context(body[data-theme-type="light"]) .response-container .hljs-keyword, + :host-context(body[data-theme-type="light"]) .response-container .hljs-meta .hljs-keyword, + :host-context(body[data-theme-type="light"]) .response-container .hljs-template-tag, + :host-context(body[data-theme-type="light"]) .response-container .hljs-template-variable, + :host-context(body[data-theme-type="light"]) .response-container .hljs-type, + :host-context(body[data-theme-type="light"]) .response-container .hljs-variable.language_ { + color: #cf222e; + } - .screen-answer-btn:hover { - background: var(--btn-primary-hover, #f0f0f0); - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-title, + :host-context(body[data-theme-type="light"]) .response-container .hljs-title.class_, + :host-context(body[data-theme-type="light"]) .response-container .hljs-title.class_.inherited__, + :host-context(body[data-theme-type="light"]) .response-container .hljs-title.function_ { + color: #8250df; + } - .screen-answer-btn svg { - width: 16px; - height: 16px; - flex-shrink: 0; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-attr, + :host-context(body[data-theme-type="light"]) .response-container .hljs-attribute, + :host-context(body[data-theme-type="light"]) .response-container .hljs-literal, + :host-context(body[data-theme-type="light"]) .response-container .hljs-meta, + :host-context(body[data-theme-type="light"]) .response-container .hljs-number, + :host-context(body[data-theme-type="light"]) .response-container .hljs-operator, + :host-context(body[data-theme-type="light"]) .response-container .hljs-selector-attr, + :host-context(body[data-theme-type="light"]) .response-container .hljs-selector-class, + :host-context(body[data-theme-type="light"]) .response-container .hljs-selector-id, + :host-context(body[data-theme-type="light"]) .response-container .hljs-variable { + color: #0550ae; + } - .screen-answer-btn .usage-count { - font-size: 11px; - opacity: 0.7; - font-family: 'SF Mono', Monaco, monospace; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-meta .hljs-string, + :host-context(body[data-theme-type="light"]) .response-container .hljs-regexp, + :host-context(body[data-theme-type="light"]) .response-container .hljs-string { + color: #0a3069; + } - .screen-answer-btn-wrapper { - position: relative; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-built_in, + :host-context(body[data-theme-type="light"]) .response-container .hljs-symbol { + color: #953800; + } - .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; - 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; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-code, + :host-context(body[data-theme-type="light"]) .response-container .hljs-comment, + :host-context(body[data-theme-type="light"]) .response-container .hljs-formula { + color: #6e7781; + } - .screen-answer-btn-wrapper .tooltip::after { - content: ''; - position: absolute; - top: 100%; - right: 16px; - border: 6px solid transparent; - border-top-color: var(--tooltip-bg, #1a1a1a); - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-name, + :host-context(body[data-theme-type="light"]) .response-container .hljs-quote, + :host-context(body[data-theme-type="light"]) .response-container .hljs-selector-pseudo, + :host-context(body[data-theme-type="light"]) .response-container .hljs-selector-tag { + color: #116329; + } - .screen-answer-btn-wrapper:hover .tooltip { - opacity: 1; - visibility: visible; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-subst { + color: #24292f; + } - .tooltip-row { - display: flex; - justify-content: space-between; - gap: 16px; - margin-bottom: 4px; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-section { + color: #0969da; + font-weight: 700; + } - .tooltip-row:last-child { - margin-bottom: 0; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-bullet { + color: #953800; + } - .tooltip-label { - opacity: 0.7; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-emphasis { + color: #24292f; + font-style: italic; + } - .tooltip-value { - font-family: 'SF Mono', Monaco, monospace; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-strong { + color: #24292f; + font-weight: 700; + } - .tooltip-note { - margin-top: 6px; - padding-top: 6px; - border-top: 1px solid rgba(255, 255, 255, 0.1); - opacity: 0.5; - font-size: 10px; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-addition { + color: #116329; + background-color: #dafbe1; + } - .capture-buttons { - display: flex; - gap: 6px; - } + :host-context(body[data-theme-type="light"]) .response-container .hljs-deletion { + color: #82071e; + background-color: #ffebe9; + } - .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; - } + .response-container a { + color: var(--accent); + text-decoration: underline; + text-underline-offset: 2px; + } - .region-select-btn:hover { - background: var(--hover-background); - color: var(--text-color); - border-color: var(--text-color); - } + .response-container strong, + .response-container b { + font-weight: var(--font-weight-semibold); + } - .region-select-btn svg { - width: 16px; - height: 16px; - } + .response-container hr { + border: none; + border-top: 1px solid var(--border); + margin: 1.5em 0; + } - .region-select-btn span { - margin-left: 4px; - } + .response-container table { + border-collapse: collapse; + width: 100%; + margin: 0.8em 0; + } - .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; - } + .response-container th, + .response-container td { + border: 1px solid var(--border); + padding: var(--space-sm); + text-align: left; + } - .ptt-toggle-btn:hover { - background: var(--hover-background); - color: var(--text-color); - border-color: var(--text-color); - } + .response-container th { + background: var(--bg-surface); + font-weight: var(--font-weight-semibold); + } - .ptt-toggle-btn.active { - color: var(--error-color); - border-color: var(--error-color); - } + .response-container::-webkit-scrollbar { + width: 6px; + } - .ptt-indicator { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - color: var(--text-secondary); - margin-bottom: 6px; - } + .response-container::-webkit-scrollbar-track { + background: transparent; + } - .ptt-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--border-color); - box-shadow: 0 0 0 1px var(--border-color); - } + .response-container::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: 3px; + } - .ptt-dot.active { - background: var(--error-color); - box-shadow: 0 0 0 1px var(--error-color); - } + .response-container::-webkit-scrollbar-thumb:hover { + background: #444444; + } - .ptt-label { - font-family: 'SF Mono', Monaco, monospace; - } - `; + /* ── Response navigation strip ── */ - static properties = { - responses: { type: Array }, - currentResponseIndex: { type: Number }, - 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 }, + .response-nav { + display: flex; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: var(--space-xs) var(--space-md); + border-top: 1px solid var(--border); + background: var(--bg-app); + } + + .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-btn:hover:not(:disabled) { + color: var(--text-primary); + } + + .nav-btn:disabled { + opacity: 0.25; + cursor: default; + } + + .nav-btn svg { + width: 14px; + height: 14px; + } + + .response-counter { + font-size: var(--font-size-xs); + color: var(--text-muted); + font-family: var(--font-mono); + min-width: 40px; + text-align: center; + } + + /* ── Bottom input bar ── */ + + .input-bar { + display: flex; + align-items: center; + 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: 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; + font-size: var(--font-size-xs); + font-family: var(--font-mono); + white-space: nowrap; + 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; + } + + .analyze-btn:hover:not(.analyzing) { + border-color: var(--accent); + background: var(--bg-surface); + } + + .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; + } + + .analyze-btn.analyzing .analyze-btn-content { + opacity: 0; + } + + .analyze-canvas { + position: absolute; + inset: -1px; + width: calc(100% + 2px); + height: calc(100% + 2px); + pointer-events: none; + } + + /* ── Expand button ── */ + + .expand-bar { + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-xs) var(--space-md); + border-top: 1px solid var(--border); + background: var(--bg-app); + } + + .expand-btn { + display: flex; + align-items: center; + gap: 4px; + background: none; + border: 1px solid var(--border); + color: var(--text-secondary); + cursor: pointer; + font-size: var(--font-size-xs); + font-family: var(--font-mono); + padding: var(--space-xs) var(--space-md); + border-radius: 100px; + height: 26px; + transition: + color var(--transition), + border-color var(--transition), + background var(--transition); + } + + .expand-btn:hover:not(:disabled) { + color: var(--text-primary); + border-color: var(--accent); + background: var(--bg-surface); + } + + .expand-btn:disabled { + opacity: 0.4; + cursor: default; + } + + .expand-btn svg { + width: 12px; + height: 12px; + } + `; + + static properties = { + responses: { type: Array }, + currentResponseIndex: { type: Number }, + selectedProfile: { type: String }, + onSendText: { type: Function }, + onExpandResponse: { type: Function }, + shouldAnimateResponse: { type: Boolean }, + isAnalyzing: { type: Boolean, state: true }, + isExpanding: { type: Boolean, state: true }, + }; + + constructor() { + super(); + this.responses = []; + this.currentResponseIndex = -1; + this.selectedProfile = "interview"; + this.onSendText = () => {}; + this.onExpandResponse = () => {}; + this.isAnalyzing = false; + this.isExpanding = false; + this._animFrame = null; + } + + getProfileNames() { + return { + interview: "Job Interview", + sales: "Sales Call", + meeting: "Business Meeting", + presentation: "Presentation", + negotiation: "Negotiation", + exam: "Exam Assistant", + }; + } + + getCurrentResponse() { + const profileNames = this.getProfileNames(); + return this.responses.length > 0 && this.currentResponseIndex >= 0 + ? this.responses[this.currentResponseIndex] + : `Listening to your ${profileNames[this.selectedProfile] || "session"}...`; + } + + renderMarkdown(content) { + if (typeof window !== "undefined" && window.marked) { + try { + // Configure marked to use highlight.js for syntax highlighting + window.marked.setOptions({ + breaks: true, + gfm: true, + sanitize: false, + highlight: (code, lang) => { + if (window.hljs && lang) { + try { + return window.hljs.highlight(code, { language: lang }).value; + } catch (e) { + // If language is not recognized, try auto-detection + try { + return window.hljs.highlightAuto(code).value; + } catch (err) { + return window.hljs.escapeHtml(code); + } + } + } else if (window.hljs) { + // Auto-detect language if not specified + try { + return window.hljs.highlightAuto(code).value; + } catch (e) { + return window.hljs.escapeHtml(code); + } + } + return code; + }, + }); + let rendered = window.marked.parse(content); + rendered = this.wrapWordsInSpans(rendered); + return rendered; + } catch (error) { + console.warn("Error parsing markdown:", error); + return content; + } + } + return content; + } + + wrapWordsInSpans(html) { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const tagsToSkip = ["PRE", "CODE"]; + + function wrap(node) { + if ( + node.nodeType === Node.TEXT_NODE && + node.textContent.trim() && + !tagsToSkip.includes(node.parentNode.tagName) + ) { + const words = node.textContent.split(/(\s+)/); + const frag = document.createDocumentFragment(); + words.forEach((word) => { + if (word.trim()) { + const span = document.createElement("span"); + span.setAttribute("data-word", ""); + span.textContent = word; + frag.appendChild(span); + } else { + frag.appendChild(document.createTextNode(word)); + } + }); + node.parentNode.replaceChild(frag, node); + } else if ( + node.nodeType === Node.ELEMENT_NODE && + !tagsToSkip.includes(node.tagName) + ) { + Array.from(node.childNodes).forEach(wrap); + } + } + Array.from(doc.body.childNodes).forEach(wrap); + return doc.body.innerHTML; + } + + applyCodeHighlighting(container) { + if (!window.hljs) return; + + // Find all code blocks in the rendered content + const codeBlocks = container.querySelectorAll("pre code"); + + codeBlocks.forEach((block) => { + const pre = block.parentElement; + if (!pre || pre.tagName !== "PRE") return; + + // Skip if already highlighted + if (block.classList.contains("hljs")) { + return; + } + + const code = block.textContent; + let lang = block.className.replace(/language-|lang-/, "") || ""; + + try { + if (lang && window.hljs.getLanguage(lang)) { + block.innerHTML = window.hljs.highlight(code, { language: lang }).value; + } else { + // Auto-detect language + const result = window.hljs.highlightAuto(code); + block.innerHTML = result.value; + if (result.language && !lang) { + lang = result.language; + block.className = `language-${lang}`; + } + } + block.classList.add("hljs"); + + // Set data-language attribute on pre tag for display + if (lang) { + pre.setAttribute("data-language", lang); + } + } catch (e) { + console.warn("Error highlighting code block:", e); + // Leave block as-is if highlighting fails + } + }); + } + + navigateToPreviousResponse() { + if (this.currentResponseIndex > 0) { + this.currentResponseIndex--; + this.dispatchEvent( + new CustomEvent("response-index-changed", { + detail: { index: this.currentResponseIndex }, + }), + ); + this.requestUpdate(); + } + } + + navigateToNextResponse() { + if (this.currentResponseIndex < this.responses.length - 1) { + this.currentResponseIndex++; + this.dispatchEvent( + new CustomEvent("response-index-changed", { + detail: { index: this.currentResponseIndex }, + }), + ); + this.requestUpdate(); + } + } + + scrollResponseUp() { + const container = this.shadowRoot.querySelector(".response-container"); + if (container) { + const scrollAmount = container.clientHeight * 0.3; + container.scrollTop = Math.max(0, container.scrollTop - scrollAmount); + } + } + + scrollResponseDown() { + const container = this.shadowRoot.querySelector(".response-container"); + if (container) { + const scrollAmount = container.clientHeight * 0.3; + container.scrollTop = Math.min( + container.scrollHeight - container.clientHeight, + container.scrollTop + scrollAmount, + ); + } + } + + connectedCallback() { + super.connectedCallback(); + + if (window.require) { + const { ipcRenderer } = window.require("electron"); + + this.handlePreviousResponse = () => this.navigateToPreviousResponse(); + this.handleNextResponse = () => this.navigateToNextResponse(); + this.handleScrollUp = () => this.scrollResponseUp(); + this.handleScrollDown = () => this.scrollResponseDown(); + this.handleExpandHotkey = () => this.handleExpandResponse(); + + 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("expand-response", this.handleExpandHotkey); + } + } + + disconnectedCallback() { + super.disconnectedCallback(); + this._stopWaveformAnimation(); + + 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.handleExpandHotkey) + ipcRenderer.removeListener("expand-response", this.handleExpandHotkey); + } + } + + async handleSendText() { + const textInput = this.shadowRoot.querySelector("#textInput"); + if (textInput && textInput.value.trim()) { + const message = textInput.value.trim(); + textInput.value = ""; + await this.onSendText(message); + } + } + + handleTextKeydown(e) { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + this.handleSendText(); + } + } + + async handleScreenAnswer() { + if (this.isAnalyzing) return; + if (window.captureManualScreenshot) { + this.isAnalyzing = true; + this._responseCountWhenStarted = this.responses.length; + window.captureManualScreenshot(); + } + } + + async handleExpandResponse() { + if ( + this.isExpanding || + this.responses.length === 0 || + this.currentResponseIndex < 0 + ) + return; + this.isExpanding = true; + this._responseCountWhenStarted = this.responses.length; + await this.onExpandResponse(); + } + + _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), + }; }; - constructor() { - super(); - this.responses = []; - 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 = ''; + // 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(), + }); } - getProfileNames() { - return { - interview: 'Job Interview', - sales: 'Sales Call', - meeting: 'Business Meeting', - presentation: 'Presentation', - negotiation: 'Negotiation', - exam: 'Exam Assistant', - }; - } + const draw = (now) => { + const elapsed = (now - startTime) / 1000; + const fade = Math.min(1, elapsed / FADE_IN); - getCurrentResponse() { - 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'}?`; - } + ctx.clearRect(0, 0, w, h); - 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 - }); - 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 - } + // ── 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); } - console.log('Marked not available, using plain text'); - return content; // Fallback if marked is not available + ctx.stroke(); + } + + ctx.globalAlpha = 1; + this._animFrame = requestAnimationFrame(draw); + }; + + this._animFrame = requestAnimationFrame(draw); + } + + _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); + } + } + + scrollToBottom() { + setTimeout(() => { + const container = this.shadowRoot.querySelector(".response-container"); + if (container) { + container.scrollTop = container.scrollHeight; + } + }, 0); + } + + firstUpdated() { + super.firstUpdated(); + this.updateResponseContent(); + } + + updated(changedProperties) { + super.updated(changedProperties); + if ( + changedProperties.has("responses") || + changedProperties.has("currentResponseIndex") + ) { + this.updateResponseContent(); } - wrapWordsInSpans(html) { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const tagsToSkip = ['PRE']; - - function wrap(node) { - if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && !tagsToSkip.includes(node.parentNode.tagName)) { - const words = node.textContent.split(/(\s+)/); - const frag = document.createDocumentFragment(); - words.forEach(word => { - if (word.trim()) { - const span = document.createElement('span'); - span.setAttribute('data-word', ''); - span.textContent = word; - frag.appendChild(span); - } else { - frag.appendChild(document.createTextNode(word)); - } - }); - node.parentNode.replaceChild(frag, node); - } else if (node.nodeType === Node.ELEMENT_NODE && !tagsToSkip.includes(node.tagName)) { - Array.from(node.childNodes).forEach(wrap); - } - } - Array.from(doc.body.childNodes).forEach(wrap); - return doc.body.innerHTML; + if (changedProperties.has("isAnalyzing")) { + if (this.isAnalyzing) { + this._startWaveformAnimation(); + } else { + this._stopWaveformAnimation(); + } } - getResponseCounter() { - return this.responses.length > 0 ? `${this.currentResponseIndex + 1}/${this.responses.length}` : ''; + if ( + changedProperties.has("responses") && + (this.isAnalyzing || this.isExpanding) + ) { + if (this.responses.length > this._responseCountWhenStarted) { + this.isAnalyzing = false; + this.isExpanding = false; + } } + } - navigateToPreviousResponse() { - if (this.currentResponseIndex > 0) { - this.currentResponseIndex--; - this.dispatchEvent( - new CustomEvent('response-index-changed', { - detail: { index: this.currentResponseIndex }, - }) - ); - this.requestUpdate(); - } + updateResponseContent() { + const container = this.shadowRoot.querySelector("#responseContainer"); + if (container) { + const currentResponse = this.getCurrentResponse(); + const renderedResponse = this.renderMarkdown(currentResponse); + container.innerHTML = renderedResponse; + + // Apply syntax highlighting to code blocks + this.applyCodeHighlighting(container); + + if (this.shouldAnimateResponse) { + this.dispatchEvent( + new CustomEvent("response-animation-complete", { + bubbles: true, + composed: true, + }), + ); + } } + } - navigateToNextResponse() { - if (this.currentResponseIndex < this.responses.length - 1) { - this.currentResponseIndex++; - this.dispatchEvent( - new CustomEvent('response-index-changed', { - detail: { index: this.currentResponseIndex }, - }) - ); - this.requestUpdate(); - } - } + render() { + const hasMultipleResponses = this.responses.length > 1; + const hasResponse = + this.responses.length > 0 && this.currentResponseIndex >= 0; - scrollResponseUp() { - const container = this.shadowRoot.querySelector('.response-container'); - if (container) { - const scrollAmount = container.clientHeight * 0.3; // Scroll 30% of container height - container.scrollTop = Math.max(0, container.scrollTop - scrollAmount); - } - } + return html` +
- scrollResponseDown() { - const container = this.shadowRoot.querySelector('.response-container'); - if (container) { - const scrollAmount = container.clientHeight * 0.3; // Scroll 30% of container height - container.scrollTop = Math.min(container.scrollHeight - container.clientHeight, container.scrollTop + scrollAmount); - } - } - - 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(); - }; - - 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(); - - // 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); - } - } - } - - async handleSendText() { - const textInput = this.shadowRoot.querySelector('#textInput'); - if (textInput && textInput.value.trim()) { - const message = textInput.value.trim(); - textInput.value = ''; // Clear input - await this.onSendText(message); - } - } - - handleTextKeydown(e) { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - this.handleSendText(); - } - } - - 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 (window.captureManualScreenshot) { - 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); - } - } - - handlePushToTalkToggle() { - if (!window.require) { - return; - } - const { ipcRenderer } = window.require('electron'); - ipcRenderer.send('push-to-talk-toggle'); - } - - scrollToBottom() { - setTimeout(() => { - const container = this.shadowRoot.querySelector('.response-container'); - if (container) { - container.scrollTop = container.scrollHeight; - } - }, 0); - } - - firstUpdated() { - super.firstUpdated(); - this.updateResponseContent(); - } - - updated(changedProperties) { - super.updated(changedProperties); - if (changedProperties.has('responses') || changedProperties.has('currentResponseIndex')) { - this.updateResponseContent(); - } - } - - 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'; - - return html` -
- - ${showPushToTalk + ${hasMultipleResponses || hasResponse + ? html` +
+ ${hasMultipleResponses ? html` -
- - Push-to-Talk: - ${pushToTalkLabel} -
- ` - : ''} - -
- - - ${this.responses.length > 0 ? html`${responseCounter}` : ''} - - - - - -
- ${showPushToTalk - ? html` - - ` - : ''} - -
- ${this.aiProvider === 'gemini' - ? html` -
-
- Flash - ${this.flashCount}/20 -
-
- Flash Lite - ${this.flashLiteCount}/20 -
-
Resets every 24 hours
-
- ` - : ''} - -
-
+ ${this.currentResponseIndex + 1} of + ${this.responses.length} + + ` + : ""} + ${hasResponse + ? html` + + ` + : ""}
- `; - } + ` + : ""} + +
+
+ +
+ +
+ `; + } } -customElements.define('assistant-view', AssistantView); +customElements.define("assistant-view", AssistantView); diff --git a/src/components/views/CustomizeView.js b/src/components/views/CustomizeView.js index ee698ae..68b0bf5 100644 --- a/src/components/views/CustomizeView.js +++ b/src/components/views/CustomizeView.js @@ -1,1829 +1,882 @@ -import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; +import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js"; +import { unifiedPageStyles } from "./sharedPageStyles.js"; export class CustomizeView extends LitElement { - static styles = 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; - } - } - - .form-group { - display: flex; - flex-direction: column; - gap: 6px; - } - - .form-group.full-width { - grid-column: 1 / -1; - } - - .form-label { - font-weight: 500; - font-size: 12px; - color: var(--text-color); - display: flex; - align-items: center; - gap: 6px; - } - - .form-description { - font-size: 11px; - color: var(--text-muted); - line-height: 1.4; - margin-top: 2px; - } - - .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; - } - - .form-control:focus { - outline: none; - border-color: var(--border-default); - } - - .form-control:hover:not(:focus) { - border-color: var(--border-default); - } - - 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; - } - - textarea.form-control { - resize: vertical; - min-height: 60px; - line-height: 1.4; - font-family: inherit; - } - - /* Profile section with expanding textarea */ - .profile-section { - display: flex; - flex-direction: column; - height: 100%; - } - - .profile-section .form-grid { - flex: 1; - display: flex; - flex-direction: column; - } - - .profile-section .form-group.expand { - flex: 1; - display: flex; - flex-direction: column; - } - - .profile-section .form-group.expand textarea { - flex: 1; - resize: none; - } - - textarea.form-control::placeholder { - color: var(--placeholder-color); - } - - .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 { - cursor: pointer; - font-family: 'SF Mono', Monaco, monospace; - text-align: center; - letter-spacing: 0.5px; - font-weight: 500; - } - - .keybind-input:focus { - cursor: text; - } - - .keybind-input::placeholder { - color: var(--placeholder-color); - font-style: italic; - } - - .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; - } - - .reset-keybinds-button:hover { - background: var(--hover-background); - } - - .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); - } - - `; - - static properties = { - selectedProfile: { type: String }, - selectedLanguage: { type: String }, - selectedImageQuality: { type: String }, - layoutMode: { type: String }, - keybinds: { type: Object }, - googleSearchEnabled: { type: Boolean }, - 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 }, - clearStatusMessage: { type: String }, - clearStatusType: { type: String }, + static styles = [ + unifiedPageStyles, + css` + .danger-surface { + border-color: var(--danger); + } + + .warning-callout { + position: relative; + margin-top: 4px; + padding: 8px 12px; + border: 1px solid var(--danger); + border-radius: var(--radius-sm); + color: var(--danger); + font-size: var(--font-size-xs); + line-height: 1.4; + background: rgba(239, 68, 68, 0.06); + } + + .warning-callout::before { + content: ""; + position: absolute; + top: -6px; + left: 16px; + width: 10px; + height: 10px; + background: var(--bg-surface); + border-top: 1px solid var(--danger); + border-left: 1px solid var(--danger); + transform: rotate(45deg); + } + + .toggle-row { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-elevated); + } + + .toggle-input { + width: 14px; + height: 14px; + accent-color: var(--text-primary); + cursor: pointer; + } + + .toggle-label { + color: var(--text-primary); + font-size: var(--font-size-sm); + cursor: pointer; + user-select: none; + } + + .slider-wrap { + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--space-xs); + } + + .slider-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-sm); + } + + .slider-value { + font-family: var(--font-mono); + font-size: var(--font-size-xs); + color: var(--text-secondary); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 2px 8px; + } + + .slider-input { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 4px; + border-radius: 2px; + background: var(--border); + outline: none; + cursor: pointer; + } + + .slider-input::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--text-primary); + border: none; + } + + .slider-input::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--text-primary); + border: none; + } + + .keybind-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-sm) 0; + border-bottom: 1px solid var(--border); + } + + .keybind-row:last-of-type { + border-bottom: none; + } + + .keybind-name { + color: var(--text-secondary); + font-size: var(--font-size-sm); + } + + .keybind-input { + width: 140px; + text-align: center; + font-family: var(--font-mono); + font-size: var(--font-size-xs); + } + + .danger-button { + border: 1px solid var(--danger); + color: var(--danger); + background: transparent; + border-radius: var(--radius-sm); + padding: 9px 12px; + font-size: var(--font-size-sm); + cursor: pointer; + transition: background var(--transition); + } + + .danger-button:hover { + background: rgba(241, 76, 76, 0.11); + } + + .danger-button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .status { + margin-top: var(--space-sm); + padding: var(--space-sm); + border-radius: var(--radius-sm); + border: 1px solid var(--border); + font-size: var(--font-size-xs); + } + + .status.success { + border-color: var(--success); + color: var(--success); + } + + .status.error { + border-color: var(--danger); + color: var(--danger); + } + `, + ]; + + static properties = { + selectedProfile: { type: String }, + selectedLanguage: { type: String }, + providerMode: { type: String }, + selectedImageQuality: { type: String }, + layoutMode: { type: String }, + keybinds: { type: Object }, + googleSearchEnabled: { type: Boolean }, + backgroundTransparency: { type: Number }, + fontSize: { type: Number }, + theme: { type: String }, + onProfileChange: { type: Function }, + onLanguageChange: { type: Function }, + onImageQualityChange: { type: Function }, + onLayoutModeChange: { type: Function }, + isClearing: { type: Boolean }, + isRestoring: { type: Boolean }, + clearStatusMessage: { type: String }, + clearStatusType: { type: String }, + }; + + constructor() { + super(); + this.selectedProfile = "interview"; + this.selectedLanguage = "en-US"; + this.selectedImageQuality = "medium"; + this.layoutMode = "normal"; + this.keybinds = this.getDefaultKeybinds(); + this.onProfileChange = () => {}; + this.onLanguageChange = () => {}; + this.onImageQualityChange = () => {}; + this.onLayoutModeChange = () => {}; + this.googleSearchEnabled = true; + this.providerMode = "byok"; + this.isClearing = false; + this.isRestoring = false; + this.clearStatusMessage = ""; + this.clearStatusType = ""; + this.backgroundTransparency = 0.8; + this.fontSize = 20; + this.audioMode = "speaker_only"; + this.customPrompt = ""; + this.theme = "dark"; + this._loadFromStorage(); + } + + getThemes() { + return cheatingDaddy.theme.getAll(); + } + + async _loadFromStorage() { + try { + const [prefs, keybinds] = await Promise.all([ + cheatingDaddy.storage.getPreferences(), + cheatingDaddy.storage.getKeybinds(), + ]); + this.googleSearchEnabled = prefs.googleSearchEnabled ?? true; + this.providerMode = prefs.providerMode || "byok"; + this.backgroundTransparency = prefs.backgroundTransparency ?? 0.8; + this.fontSize = prefs.fontSize ?? 20; + this.audioMode = prefs.audioMode ?? "speaker_only"; + this.customPrompt = prefs.customPrompt ?? ""; + this.theme = prefs.theme ?? "dark"; + if (keybinds) { + this.keybinds = { ...this.getDefaultKeybinds(), ...keybinds }; + } + this.updateBackgroundAppearance(); + this.updateFontSize(); + this.requestUpdate(); + } catch (error) { + console.error("Error loading settings:", error); + } + } + + getProfiles() { + return [ + { value: "interview", name: "Job Interview" }, + { value: "sales", name: "Sales Call" }, + { value: "meeting", name: "Business Meeting" }, + { value: "presentation", name: "Presentation" }, + { value: "negotiation", name: "Negotiation" }, + { value: "exam", name: "Exam Assistant" }, + ]; + } + + getLanguages() { + return [ + { value: "auto", name: "Auto (Multilingual)" }, + { value: "en-US", name: "English (US)" }, + { value: "en-GB", name: "English (UK)" }, + { value: "en-AU", name: "English (Australia)" }, + { value: "en-IN", name: "English (India)" }, + { value: "de-DE", name: "German (Germany)" }, + { value: "es-US", name: "Spanish (US)" }, + { value: "es-ES", name: "Spanish (Spain)" }, + { value: "fr-FR", name: "French (France)" }, + { value: "fr-CA", name: "French (Canada)" }, + { value: "hi-IN", name: "Hindi (India)" }, + { value: "pt-BR", name: "Portuguese (Brazil)" }, + { value: "ar-XA", name: "Arabic (Generic)" }, + { value: "id-ID", name: "Indonesian (Indonesia)" }, + { value: "it-IT", name: "Italian (Italy)" }, + { value: "ja-JP", name: "Japanese (Japan)" }, + { value: "tr-TR", name: "Turkish (Turkey)" }, + { value: "vi-VN", name: "Vietnamese (Vietnam)" }, + { value: "bn-IN", name: "Bengali (India)" }, + { value: "gu-IN", name: "Gujarati (India)" }, + { value: "kn-IN", name: "Kannada (India)" }, + { value: "ml-IN", name: "Malayalam (India)" }, + { value: "mr-IN", name: "Marathi (India)" }, + { value: "ta-IN", name: "Tamil (India)" }, + { value: "te-IN", name: "Telugu (India)" }, + { value: "nl-NL", name: "Dutch (Netherlands)" }, + { value: "ko-KR", name: "Korean (South Korea)" }, + { value: "cmn-CN", name: "Mandarin Chinese (China)" }, + { value: "pl-PL", name: "Polish (Poland)" }, + { value: "ru-RU", name: "Russian (Russia)" }, + { value: "th-TH", name: "Thai (Thailand)" }, + ]; + } + + getDefaultKeybinds() { + const isMac = cheatingDaddy.isMacOS || navigator.platform.includes("Mac"); + return { + moveUp: isMac ? "Alt+Up" : "Ctrl+Up", + moveDown: isMac ? "Alt+Down" : "Ctrl+Down", + moveLeft: isMac ? "Alt+Left" : "Ctrl+Left", + moveRight: isMac ? "Alt+Right" : "Ctrl+Right", + toggleVisibility: isMac ? "Cmd+\\" : "Ctrl+\\", + toggleClickThrough: isMac ? "Cmd+M" : "Ctrl+M", + nextStep: isMac ? "Cmd+Enter" : "Ctrl+Enter", + previousResponse: isMac ? "Cmd+[" : "Ctrl+[", + nextResponse: isMac ? "Cmd+]" : "Ctrl+]", + scrollUp: isMac ? "Cmd+Shift+Up" : "Ctrl+Shift+Up", + scrollDown: isMac ? "Cmd+Shift+Down" : "Ctrl+Shift+Down", + expandResponse: isMac ? "Cmd+E" : "Ctrl+E", }; + } - constructor() { - super(); - this.selectedProfile = 'interview'; - this.selectedLanguage = 'en-US'; - this.selectedImageQuality = 'medium'; - this.layoutMode = 'normal'; - this.keybinds = this.getDefaultKeybinds(); - this.onProfileChange = () => {}; - this.onLanguageChange = () => {}; - this.onImageQualityChange = () => {}; - this.onLayoutModeChange = () => {}; + getKeybindActions() { + return [ + { + key: "moveUp", + name: "Move Window Up", + description: "Move the app window up", + }, + { + key: "moveDown", + name: "Move Window Down", + description: "Move the app window down", + }, + { + key: "moveLeft", + name: "Move Window Left", + description: "Move the app window left", + }, + { + key: "moveRight", + name: "Move Window Right", + description: "Move the app window right", + }, + { + key: "toggleVisibility", + name: "Toggle Visibility", + description: "Show or hide the app window", + }, + { + key: "toggleClickThrough", + name: "Toggle Click-through", + description: "Enable or disable click-through mode", + }, + { + key: "nextStep", + name: "Ask Next Step", + description: "Take screenshot and ask for next step", + }, + { + key: "previousResponse", + name: "Previous Response", + description: "Move to previous AI response", + }, + { + key: "nextResponse", + name: "Next Response", + description: "Move to next AI response", + }, + { + key: "scrollUp", + name: "Scroll Response Up", + description: "Scroll response content upward", + }, + { + key: "scrollDown", + name: "Scroll Response Down", + description: "Scroll response content downward", + }, + { + key: "expandResponse", + name: "Expand Response", + description: "Expand the current response with more detail", + }, + ]; + } - // Google Search default - this.googleSearchEnabled = true; + async saveKeybinds() { + await cheatingDaddy.storage.setKeybinds(this.keybinds); + if (window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.send("update-keybinds", this.keybinds); + } + } - // Clear data state - this.isClearing = false; - this.clearStatusMessage = ''; - this.clearStatusType = ''; + handleProfileSelect(e) { + this.selectedProfile = e.target.value; + this.onProfileChange(this.selectedProfile); + } - // Background transparency default - this.backgroundTransparency = 0.8; + handleLanguageSelect(e) { + this.selectedLanguage = e.target.value; + this.onLanguageChange(this.selectedLanguage); + } - // Font size default (in pixels) - this.fontSize = 20; + handleImageQualitySelect(e) { + this.selectedImageQuality = e.target.value; + this.onImageQualityChange(this.selectedImageQuality); + } - // Audio mode default - this.audioMode = 'speaker_only'; - this.audioInputMode = 'auto'; + handleLayoutModeSelect(e) { + this.layoutMode = e.target.value; + this.onLayoutModeChange(this.layoutMode); + } - // Custom prompt - this.customPrompt = ''; + async handleCustomPromptInput(e) { + this.customPrompt = e.target.value; + await cheatingDaddy.storage.updatePreference( + "customPrompt", + this.customPrompt, + ); + } - // Active section for sidebar navigation - this.activeSection = 'profile'; + async handleAudioModeSelect(e) { + this.audioMode = e.target.value; + await cheatingDaddy.storage.updatePreference("audioMode", this.audioMode); + this.requestUpdate(); + } - // Theme default - this.theme = 'dark'; + async handleProviderModeChange(e) { + this.providerMode = e.target.value; + await cheatingDaddy.storage.updatePreference( + "providerMode", + this.providerMode, + ); + this.requestUpdate(); + } - // 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'; + async handleThemeChange(e) { + this.theme = e.target.value; + await cheatingDaddy.theme.save(this.theme); + this.updateBackgroundAppearance(); + this.requestUpdate(); + } - this._loadFromStorage(); + async handleGoogleSearchChange(e) { + this.googleSearchEnabled = e.target.checked; + await cheatingDaddy.storage.updatePreference( + "googleSearchEnabled", + this.googleSearchEnabled, + ); + if (window.require) { + try { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke( + "update-google-search-setting", + this.googleSearchEnabled, + ); + } catch (error) { + console.error("Failed to notify main process:", error); + } + } + this.requestUpdate(); + } + + async handleBackgroundTransparencyChange(e) { + this.backgroundTransparency = parseFloat(e.target.value); + await cheatingDaddy.storage.updatePreference( + "backgroundTransparency", + this.backgroundTransparency, + ); + this.updateBackgroundAppearance(); + this.requestUpdate(); + } + + updateBackgroundAppearance() { + const colors = cheatingDaddy.theme.get(this.theme); + cheatingDaddy.theme.applyBackgrounds( + colors.background, + this.backgroundTransparency, + ); + } + + async handleFontSizeChange(e) { + this.fontSize = parseInt(e.target.value, 10); + await cheatingDaddy.storage.updatePreference("fontSize", this.fontSize); + this.updateFontSize(); + this.requestUpdate(); + } + + updateFontSize() { + document.documentElement.style.setProperty( + "--response-font-size", + `${this.fontSize}px`, + ); + } + + handleKeybindChange(action, value) { + this.keybinds = { ...this.keybinds, [action]: value }; + this.saveKeybinds(); + this.requestUpdate(); + } + + handleKeybindFocus(e) { + e.target.placeholder = "Press key combination..."; + e.target.select(); + } + + handleKeybindInput(e) { + e.preventDefault(); + const modifiers = []; + if (e.ctrlKey) modifiers.push("Ctrl"); + if (e.metaKey) modifiers.push("Cmd"); + if (e.altKey) modifiers.push("Alt"); + if (e.shiftKey) modifiers.push("Shift"); + let mainKey = e.key; + + switch (e.code) { + case "ArrowUp": + mainKey = "Up"; + break; + case "ArrowDown": + mainKey = "Down"; + break; + case "ArrowLeft": + mainKey = "Left"; + break; + case "ArrowRight": + mainKey = "Right"; + break; + case "Enter": + mainKey = "Enter"; + break; + case "Space": + mainKey = "Space"; + break; + case "Backslash": + mainKey = "\\"; + break; + default: + if (e.key.length === 1) mainKey = e.key.toUpperCase(); + break; } - getThemes() { - return mastermind.theme.getAll(); - } + if (["Control", "Meta", "Alt", "Shift"].includes(e.key)) return; - setActiveSection(section) { - this.activeSection = section; + const action = e.target.dataset.action; + const keybind = [...modifiers, mainKey].join("+"); + this.handleKeybindChange(action, keybind); + e.target.value = keybind; + e.target.blur(); + } + + async resetKeybinds() { + this.keybinds = this.getDefaultKeybinds(); + await cheatingDaddy.storage.setKeybinds(null); + if (window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.send("update-keybinds", this.keybinds); + } + this.requestUpdate(); + } + + async restoreAllSettings() { + if (this.isRestoring) return; + this.isRestoring = true; + this.clearStatusMessage = ""; + this.clearStatusType = ""; + this.requestUpdate(); + try { + // Restore all preferences to defaults + const defaults = { + customPrompt: "", + selectedProfile: "interview", + selectedLanguage: "en-US", + selectedScreenshotInterval: "5", + selectedImageQuality: "medium", + audioMode: "speaker_only", + fontSize: 20, + backgroundTransparency: 0.8, + googleSearchEnabled: false, + theme: "dark", + }; + for (const [key, value] of Object.entries(defaults)) { + await cheatingDaddy.storage.updatePreference(key, value); + } + + // Restore keybinds + this.keybinds = this.getDefaultKeybinds(); + await cheatingDaddy.storage.setKeybinds(null); + if (window.require) { + const { ipcRenderer } = window.require("electron"); + ipcRenderer.send("update-keybinds", this.keybinds); + } + + // Apply to local state + this.selectedProfile = defaults.selectedProfile; + this.selectedLanguage = defaults.selectedLanguage; + this.selectedImageQuality = defaults.selectedImageQuality; + this.audioMode = defaults.audioMode; + this.fontSize = defaults.fontSize; + this.backgroundTransparency = defaults.backgroundTransparency; + this.googleSearchEnabled = defaults.googleSearchEnabled; + this.customPrompt = defaults.customPrompt; + this.theme = defaults.theme; + + // Notify parent callbacks + this.onProfileChange(defaults.selectedProfile); + this.onLanguageChange(defaults.selectedLanguage); + this.onImageQualityChange(defaults.selectedImageQuality); + + // Apply visual changes + this.updateBackgroundAppearance(); + this.updateFontSize(); + await cheatingDaddy.theme.save(defaults.theme); + + this.clearStatusMessage = "All settings restored to defaults"; + this.clearStatusType = "success"; + } catch (error) { + console.error("Error restoring settings:", error); + this.clearStatusMessage = `Error restoring settings: ${error.message}`; + this.clearStatusType = "error"; + } finally { + this.isRestoring = false; + this.requestUpdate(); + } + } + + async clearLocalData() { + if (this.isClearing) return; + this.isClearing = true; + this.clearStatusMessage = ""; + this.clearStatusType = ""; + this.requestUpdate(); + try { + await cheatingDaddy.storage.clearAll(); + this.clearStatusMessage = "Successfully cleared all local data"; + this.clearStatusType = "success"; + this.requestUpdate(); + setTimeout(() => { + this.clearStatusMessage = "Closing application..."; this.requestUpdate(); + setTimeout(async () => { + if (window.require) { + const { ipcRenderer } = window.require("electron"); + await ipcRenderer.invoke("quit-application"); + } + }, 1000); + }, 2000); + } catch (error) { + console.error("Error clearing data:", error); + this.clearStatusMessage = `Error clearing data: ${error.message}`; + this.clearStatusType = "error"; + } finally { + this.isClearing = false; + this.requestUpdate(); } + } - 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` +
AI Engine
+
+
+ + +
+
+ + `; + } + + renderAudioSection() { + return html` +
+
Audio Input
+
+
+ + +
+ ${this.audioMode !== "speaker_only" + ? html` +
+ May cause unexpected behavior. Only change this if you know + what you're doing. +
+ ` + : ""} +
+ + +
+
+
+ `; + } + + renderLanguageSection() { + return html` +
+
Language
+
+
+ + +
+
+
+ `; + } + + renderAppearanceSection() { + return html` +
+
Appearance
+
+
+ + - ${profiles.map( - profile => html` - - ` - )} - -
- -
- - -
Personalize the AI's behavior with specific instructions
-
-
+ ${this.getThemes().map( + (theme) => + html``, + )} + +
+
+
+ + ${Math.round(this.backgroundTransparency * 100)}%
- `; - } - - 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.'} -
-
- ${this.audioInputMode === 'push-to-talk' - ? html`
Use the Push-to-Talk hotkey (toggle) to start/stop recording.
` - : ''} + +
+
+
+ + ${this.fontSize}px
- `; - } + +
+
+ + `; + } - renderLanguageSection() { - const languages = this.getLanguages(); - const currentLanguage = languages.find(l => l.value === this.selectedLanguage); - - return html` -
Language
-
-
- - -
Language for speech recognition and AI responses
-
+ renderKeyboardSection() { + return html` +
+
Keyboard Shortcuts
+ ${this.getKeybindActions().map( + (action) => html` +
+ ${action.name} +
- `; - } + `, + )} +
+ +
+
+ `; + } - renderAppearanceSection() { - const themes = this.getThemes(); - const currentTheme = themes.find(t => t.value === this.theme); + renderPrivacySection() { + return html` +
+
Privacy and Data
+
+ + +
+ ${this.clearStatusMessage + ? html` +
+ ${this.clearStatusMessage} +
+ ` + : ""} +
+ `; + } - return html` -
Appearance
-
-
- - -
Choose a color theme for the interface
-
- -
- - -
- ${this.layoutMode === 'compact' ? 'Smaller window with reduced padding' : 'Standard layout with comfortable spacing'} -
-
- -
-
-
- - ${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. -
- - ${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(); - } - } - - render() { - const sections = this.getSidebarSections(); - - return html` -
- -
${this.renderSectionContent()}
-
- `; - } + render() { + return html` +
+
+
Settings
+ ${this.renderAISection()} ${this.renderAudioSection()} + ${this.renderLanguageSection()} ${this.renderAppearanceSection()} + ${this.renderKeyboardSection()} ${this.renderPrivacySection()} +
+
+ `; + } } -customElements.define('customize-view', CustomizeView); +customElements.define("customize-view", CustomizeView); 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..ac82a35 100644 --- a/src/components/views/MainView.js +++ b/src/components/views/MainView.js @@ -1,246 +1,1536 @@ -import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; -import { resizeLayout } from '../../utils/windowResize.js'; +import { html, css, LitElement } from "../../assets/lit-core-2.7.4.min.js"; export class MainView extends LitElement { - static styles = css` - * { - font-family: - 'Inter', - -apple-system, - BlinkMacSystemFont, - sans-serif; - cursor: default; - user-select: none; + static styles = css` + * { + font-family: var(--font); + cursor: default; + user-select: none; + 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: 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); + } + } + + /* ── Whisper download progress ── */ + + .whisper-progress-container { + margin-top: 8px; + padding: 8px 10px; + background: var(--bg-elevated, rgba(255, 255, 255, 0.05)); + border-radius: var(--radius-sm, 6px); + border: 1px solid var(--border, rgba(255, 255, 255, 0.1)); + } + + .whisper-progress-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + font-size: 11px; + color: var(--text-secondary, #999); + } + + .whisper-progress-file { + font-family: var(--font-mono, monospace); + font-size: 10px; + color: var(--text-secondary, #999); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 200px; + } + + .whisper-progress-pct { + font-variant-numeric: tabular-nums; + font-weight: 600; + color: var(--accent, #6cb4ee); + } + + .whisper-progress-track { + height: 4px; + background: var(--border, rgba(255, 255, 255, 0.1)); + border-radius: 2px; + overflow: hidden; + } + + .whisper-progress-bar { + height: 100%; + background: var(--accent, #6cb4ee); + border-radius: 2px; + transition: width 0.3s ease; + min-width: 0; + } + + .whisper-progress-size { + margin-top: 4px; + font-size: 10px; + color: var(--text-tertiary, #666); + text-align: right; + } + + /* ── 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 }, + onExternalLink: { type: Function }, + selectedProfile: { type: String }, + onProfileChange: { type: Function }, + isInitializing: { type: Boolean }, + whisperDownloading: { type: Boolean }, + whisperProgress: { type: Object }, + // Internal state + _mode: { state: true }, + _token: { state: true }, + _geminiKey: { state: true }, + _groqKey: { state: true }, + _openaiKey: { state: true }, + _openaiCompatibleApiKey: { state: true }, + _openaiCompatibleBaseUrl: { state: true }, + _openaiCompatibleModel: { state: true }, + _availableModels: { state: true }, + _loadingModels: { state: true }, + _manualModelInput: { state: true }, + _responseProvider: { state: true }, + _tokenError: { state: true }, + _keyError: { state: true }, + // Local AI state + _ollamaHost: { state: true }, + _ollamaModel: { state: true }, + _whisperModel: { state: true }, + _customWhisperModel: { state: true }, + _showLocalHelp: { state: true }, + }; + + constructor() { + super(); + this.onStart = () => {}; + this.onExternalLink = () => {}; + this.selectedProfile = "interview"; + this.onProfileChange = () => {}; + this.isInitializing = false; + this.whisperDownloading = false; + this.whisperProgress = null; + + this._mode = "byok"; + this._token = ""; + this._geminiKey = ""; + this._groqKey = ""; + this._openaiKey = ""; + this._openaiCompatibleApiKey = ""; + this._openaiCompatibleBaseUrl = ""; + this._openaiCompatibleModel = ""; + this._availableModels = []; + this._loadingModels = false; + this._manualModelInput = false; + this._responseProvider = "gemini"; + 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._customWhisperModel = ""; + + this._animId = null; + this._time = 0; + this._mouseX = -1; + this._mouseY = -1; + + this.boundKeydownHandler = this._handleKeydown.bind(this); + this._loadFromStorage(); + } + + 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 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"; + this._ollamaModel = prefs.ollamaModel || "llama3.1"; + this._whisperModel = prefs.whisperModel || "Xenova/whisper-small"; + // If the saved model isn't one of the presets, it's a custom HF model + const presets = [ + "Xenova/whisper-tiny", + "Xenova/whisper-base", + "Xenova/whisper-small", + "Xenova/whisper-medium", + ]; + if (!presets.includes(this._whisperModel)) { + this._customWhisperModel = this._whisperModel; + this._whisperModel = "__custom__"; + } + + this.requestUpdate(); + + // Auto-load models if OpenAI-compatible is selected and URL is set + if ( + this._responseProvider === "openai-compatible" && + this._openaiCompatibleBaseUrl + ) { + this._loadModels(); + } + } catch (e) { + console.error("Error loading MainView storage:", e); + } + } + + connectedCallback() { + super.connectedCallback(); + document.addEventListener("keydown", this.boundKeydownHandler); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener("keydown", this.boundKeydownHandler); + if (this._animId) cancelAnimationFrame(this._animId); + if (this._loadModelsTimeout) clearTimeout(this._loadModelsTimeout); + } + + 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); } - .welcome { - font-size: 20px; - margin-bottom: 6px; - font-weight: 500; - color: var(--text-color); - margin-top: auto; - } + const a0 = Math.min(1, 0.18 * boost); + const a1 = Math.min(1, 0.08 * boost); + const a2 = Math.min(1, 0.02 * boost); - .input-group { - display: flex; - gap: 10px; - margin-bottom: 16px; - } + 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); + } - .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); - } - - :host { - height: 100%; - display: flex; - flex-direction: column; - width: 100%; - max-width: 480px; - } - `; - - static properties = { - onStart: { type: Function }, - onAPIKeyHelp: { type: Function }, - isInitializing: { type: Boolean }, - onLayoutModeChange: { type: Function }, - showApiKeyError: { type: Boolean }, + this._animId = requestAnimationFrame(draw); }; - constructor() { - super(); - this.onStart = () => {}; - this.onAPIKeyHelp = () => {}; - this.isInitializing = false; - this.onLayoutModeChange = () => {}; - this.showApiKeyError = false; - this.boundKeydownHandler = this.handleKeydown.bind(this); - this.apiKey = ''; - this._loadApiKey(); + draw(); + } + + _handleKeydown(e) { + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + if ((isMac ? e.metaKey : e.ctrlKey) && e.key === "Enter") { + e.preventDefault(); + this._handleStart(); + } + } + + // ── Persistence ── + + async _saveMode(mode) { + this._mode = mode; + this._keyError = false; + await cheatingDaddy.storage.updatePreference("providerMode", mode); + this.requestUpdate(); + } + + 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 _saveOpenAICompatibleApiKey(val) { + this._openaiCompatibleApiKey = val; + await cheatingDaddy.storage.setOpenAICompatibleConfig( + val, + this._openaiCompatibleBaseUrl, + this._openaiCompatibleModel, + ); + this.requestUpdate(); + // Auto-load models when both key and URL are set + this._debouncedLoadModels(); + } + + async _saveOpenAICompatibleBaseUrl(val) { + this._openaiCompatibleBaseUrl = val; + await cheatingDaddy.storage.setOpenAICompatibleConfig( + this._openaiCompatibleApiKey, + val, + this._openaiCompatibleModel, + ); + this.requestUpdate(); + // Auto-load models when both key and URL are set + this._debouncedLoadModels(); + } + + 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(); + + // Auto-load models when switching to openai-compatible + if (val === "openai-compatible" && this._openaiCompatibleBaseUrl) { + this._loadModels(); + } + } + + async _loadModels() { + if ( + this._responseProvider !== "openai-compatible" || + !this._openaiCompatibleBaseUrl + ) { + return; } - async _loadApiKey() { - this.apiKey = await mastermind.storage.getApiKey(); + this._loadingModels = true; + this._availableModels = []; + this.requestUpdate(); + + try { + let modelsUrl = this._openaiCompatibleBaseUrl.trim(); + modelsUrl = modelsUrl.replace(/\/$/, ""); + if (!modelsUrl.includes("/models")) { + modelsUrl = modelsUrl.includes("/v1") + ? modelsUrl + "/models" + : modelsUrl + "/v1/models"; + } + + console.log("Loading models from:", modelsUrl); + + const headers = { + "Content-Type": "application/json", + }; + + if (this._openaiCompatibleApiKey) { + headers["Authorization"] = `Bearer ${this._openaiCompatibleApiKey}`; + } + + const response = await fetch(modelsUrl, { headers }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = await response.json(); + + if (data.data && Array.isArray(data.data)) { + this._availableModels = data.data + .map((m) => m.id || m.model || m.name) + .filter(Boolean); + } else if (Array.isArray(data)) { + this._availableModels = data + .map((m) => m.id || m.model || m.name || m) + .filter(Boolean); + } + + console.log("Loaded models:", this._availableModels.length); + + if ( + this._availableModels.length > 0 && + !this._availableModels.includes(this._openaiCompatibleModel) + ) { + await this._saveOpenAICompatibleModel(this._availableModels[0]); + } + } catch (error) { + console.log("Could not load models:", error.message); + this._availableModels = []; + } finally { + this._loadingModels = false; + this.requestUpdate(); + } + } + + _debouncedLoadModels() { + if (this._loadModelsTimeout) { + clearTimeout(this._loadModelsTimeout); + } + this._loadModelsTimeout = setTimeout(() => { + this._loadModels(); + }, 500); + } + + _toggleManualInput() { + this._manualModelInput = !this._manualModelInput; + 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; + if (val === "__custom__") { + // Don't save yet — wait for the custom input + this.requestUpdate(); + return; + } + this._customWhisperModel = ""; + await cheatingDaddy.storage.updatePreference("whisperModel", val); + this.requestUpdate(); + } + + async _saveCustomWhisperModel(val) { + this._customWhisperModel = val.trim(); + if (this._customWhisperModel) { + await cheatingDaddy.storage.updatePreference( + "whisperModel", + this._customWhisperModel, + ); + } + this.requestUpdate(); + } + + _formatBytes(bytes) { + if (!bytes || bytes === 0) return "0 B"; + const units = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, i)).toFixed(i > 1 ? 1 : 0) + " " + units[i]; + } + + _renderWhisperProgress() { + const p = this.whisperProgress; + if (!p) return ""; + + const pct = Math.round(p.progress || 0); + const fileName = p.file ? p.file.split("/").pop() : ""; + + return html` +
+
+ ${fileName || "Preparing..."} + ${pct}% +
+
+
+
+ ${p.total + ? html`
+ ${this._formatBytes(p.loaded)} / ${this._formatBytes(p.total)} +
` + : ""} +
+ `; + } + + _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; + } } - connectedCallback() { - super.connectedCallback(); - window.electron?.ipcRenderer?.on('session-initializing', (event, isInitializing) => { - this.isInitializing = isInitializing; - }); + this.onStart(); + } - // Add keyboard event listener for Ctrl+Enter (or Cmd+Enter on Mac) - document.addEventListener('keydown', this.boundKeydownHandler); + triggerApiKeyError() { + this._keyError = true; + this.requestUpdate(); + setTimeout(() => { + this._keyError = false; + this.requestUpdate(); + }, 2000); + } - // Resize window for this view - resizeLayout(); - } + // ── Render helpers ── - disconnectedCallback() { - super.disconnectedCallback(); - window.electron?.ipcRenderer?.removeAllListeners('session-initializing'); - // Remove keyboard event listener - document.removeEventListener('keydown', this.boundKeydownHandler); - } + _renderStartButton() { + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; - handleKeydown(e) { - const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; - const isStartShortcut = isMac ? e.metaKey && e.key === 'Enter' : e.ctrlKey && e.key === 'Enter'; + const cmdIcon = html` + + `; + const ctrlIcon = html` + + `; + const enterIcon = html` + + + `; - if (isStartShortcut) { - e.preventDefault(); - this.handleStartClick(); - } - } + return html` + + `; + } - 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; - } - } + // ── BYOK mode ── - handleStartClick() { - if (this.isInitializing) { - return; - } - this.onStart(); - } + _renderByokMode() { + return html` +
+ + this._saveGeminiKey(e.target.value)} + class=${this._keyError ? "error" : ""} + /> +
+ + this.onExternalLink("https://aistudio.google.com/apikey")} + >Get Gemini key + - Always used for audio transcription +
+
- handleAPIKeyHelpClick() { - this.onAPIKeyHelp(); - } +
+ + +
+ Choose which API to use for generating responses +
+
- // Method to trigger the red blink animation - triggerApiKeyError() { - this.showApiKeyError = true; - // Remove the error class after 1 second - setTimeout(() => { - this.showApiKeyError = false; - }, 1000); - } - - getStartButtonText() { - const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; - const shortcut = isMac ? 'Cmd+Enter' : 'Ctrl+Enter'; - return html`Start ${shortcut}`; - } - - render() { - return html` -
Welcome
- -
- - + ${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._loadingModels + ? html` + + ` + : this._availableModels.length > 0 && !this._manualModelInput + ? html` +
+ + +
+ ` + : html` +
+ + this._saveOpenAICompatibleModel(e.target.value)} + /> + ${this._availableModels.length > 0 + ? html` + + ` + : ""} +
+ `} +
+
+ ${this._loadingModels + ? "Loading available models..." + : this._availableModels.length > 0 + ? `${this._availableModels.length} models available` + : "Use OpenRouter, DeepSeek, Together AI, or any OpenAI-compatible API"} +
+
+ ` + : ""} + ${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._whisperModel === "__custom__" + ? html` + this._saveCustomWhisperModel(e.target.value)} + @input=${(e) => { + this._customWhisperModel = e.target.value; + }} + style="margin-top: 6px;" + /> +
+ Enter a HuggingFace model ID compatible with + @huggingface/transformers speech-to-text pipeline +
+ ` + : html` +
+ ${this.whisperDownloading + ? "Downloading model..." + : "Downloaded automatically on first use"} +
+ `} + ${this.whisperDownloading && this.whisperProgress + ? this._renderWhisperProgress() + : ""} +
+ + ${this._renderStartButton()} + `; + } + + // ── Main render ── + + render() { + const helpIcon = html` + + + + + `; + const closeIcon = html` + + `; + + return html` +
+ ${this._mode === "local" + ? html` +
+
+ Mastermind Local AI +
+ +
+ ` + : html` +
+ Mastermind 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. +
+
+
+ `; + } } -customElements.define('main-view', MainView); +customElements.define("main-view", MainView); diff --git a/src/components/views/OnboardingView.js b/src/components/views/OnboardingView.js index 40a277e..5771ba0 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` +
+
Mastermind
+
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 }; - } - }); + ipcMain.handle("update-sizes", async (event) => { + // 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 }; + }); } module.exports = { - createWindow, - getDefaultKeybinds, - updateGlobalShortcuts, - setupWindowIpcHandlers, + createWindow, + getDefaultKeybinds, + updateGlobalShortcuts, + setupWindowIpcHandlers, }; 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