Merge branch 'main' into pr-107
This commit is contained in:
		
						commit
						6ec45e138f
					
				
							
								
								
									
										2
									
								
								.github/workflows/assign-on-comment.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/assign-on-comment.yml
									
									
									
									
										vendored
									
									
								
							@ -8,7 +8,7 @@ jobs:
 | 
				
			|||||||
  # Job 1: Any contributor can self-assign
 | 
					  # Job 1: Any contributor can self-assign
 | 
				
			||||||
  self-assign:
 | 
					  self-assign:
 | 
				
			||||||
    # Only run if the comment is exactly '/assign'
 | 
					    # Only run if the comment is exactly '/assign'
 | 
				
			||||||
    if: github.event.comment.body == '/assign'
 | 
					    if: startsWith(github.event.comment.body, '/assign') && !contains(github.event.comment.body, '@')
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
      issues: write
 | 
					      issues: write
 | 
				
			||||||
 | 
				
			|||||||
@ -46,8 +46,11 @@ win:
 | 
				
			|||||||
        - target: portable
 | 
					        - target: portable
 | 
				
			||||||
          arch: x64
 | 
					          arch: x64
 | 
				
			||||||
    requestedExecutionLevel: asInvoker
 | 
					    requestedExecutionLevel: asInvoker
 | 
				
			||||||
    # Disable code signing to avoid symbolic link issues on Windows
 | 
					    signAndEditExecutable: true
 | 
				
			||||||
    signAndEditExecutable: false
 | 
					    cscLink: build\certs\glass-dev.pfx
 | 
				
			||||||
 | 
					    cscKeyPassword: "${env.CSC_KEY_PASSWORD}"
 | 
				
			||||||
 | 
					    signtoolOptions:
 | 
				
			||||||
 | 
					      certificateSubjectName: "Glass Dev Code Signing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NSIS installer configuration for Windows
 | 
					# NSIS installer configuration for Windows
 | 
				
			||||||
nsis:
 | 
					nsis:
 | 
				
			||||||
 | 
				
			|||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
 | 
					 | 
				
			||||||
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
 | 
					 | 
				
			||||||
const { notarizeApp } = require('./notarize');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
    packagerConfig: {
 | 
					 | 
				
			||||||
        asar: {
 | 
					 | 
				
			||||||
            unpack: '**/*.node,**/*.dylib,' + '**/node_modules/{sharp,@img}/**/*',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        extraResource: ['./src/assets/SystemAudioDump', './pickleglass_web/out'],
 | 
					 | 
				
			||||||
        name: 'Glass',
 | 
					 | 
				
			||||||
        icon: 'src/assets/logo',
 | 
					 | 
				
			||||||
        appBundleId: 'com.pickle.glass',
 | 
					 | 
				
			||||||
        arch: 'universal',
 | 
					 | 
				
			||||||
        protocols: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                name: 'PickleGlass Protocol',
 | 
					 | 
				
			||||||
                schemes: ['pickleglass'],
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        asarUnpack: [
 | 
					 | 
				
			||||||
            '**/*.node',
 | 
					 | 
				
			||||||
            '**/*.dylib',
 | 
					 | 
				
			||||||
            'node_modules/@img/sharp-darwin-x64/**',
 | 
					 | 
				
			||||||
            'node_modules/@img/sharp-libvips-darwin-x64/**',
 | 
					 | 
				
			||||||
            'node_modules/@img/sharp-darwin-arm64/**',
 | 
					 | 
				
			||||||
            'node_modules/@img/sharp-libvips-darwin-arm64/**',
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        osxSign: {
 | 
					 | 
				
			||||||
            identity: process.env.APPLE_SIGNING_IDENTITY,
 | 
					 | 
				
			||||||
            'hardened-runtime': true,
 | 
					 | 
				
			||||||
            entitlements: 'entitlements.plist',
 | 
					 | 
				
			||||||
            'entitlements-inherit': 'entitlements.plist',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        osxNotarize: {
 | 
					 | 
				
			||||||
            tool: 'notarytool',
 | 
					 | 
				
			||||||
            appleId: process.env.APPLE_ID,
 | 
					 | 
				
			||||||
            appleIdPassword: process.env.APPLE_ID_PASSWORD,
 | 
					 | 
				
			||||||
            teamId: process.env.APPLE_TEAM_ID,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    rebuildConfig: {},
 | 
					 | 
				
			||||||
    makers: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: '@electron-forge/maker-squirrel',
 | 
					 | 
				
			||||||
            config: {
 | 
					 | 
				
			||||||
                name: 'pickle-glass',
 | 
					 | 
				
			||||||
                productName: 'Glass',
 | 
					 | 
				
			||||||
                shortcutName: 'Glass',
 | 
					 | 
				
			||||||
                createDesktopShortcut: true,
 | 
					 | 
				
			||||||
                createStartMenuShortcut: true,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: '@electron-forge/maker-dmg',
 | 
					 | 
				
			||||||
            platforms: ['darwin'],
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: '@electron-forge/maker-deb',
 | 
					 | 
				
			||||||
            config: {},
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: '@electron-forge/maker-rpm',
 | 
					 | 
				
			||||||
            config: {},
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    hooks: {
 | 
					 | 
				
			||||||
        afterSign: async (context, forgeConfig, platform, arch, appPath) => {
 | 
					 | 
				
			||||||
            await notarizeApp(context, forgeConfig, platform, arch, appPath);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    plugins: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            name: '@electron-forge/plugin-auto-unpack-natives',
 | 
					 | 
				
			||||||
            config: {},
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        new FusesPlugin({
 | 
					 | 
				
			||||||
            version: FuseVersion.V1,
 | 
					 | 
				
			||||||
            [FuseV1Options.RunAsNode]: false,
 | 
					 | 
				
			||||||
            [FuseV1Options.EnableCookieEncryption]: true,
 | 
					 | 
				
			||||||
            [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
 | 
					 | 
				
			||||||
            [FuseV1Options.EnableNodeCliInspectArguments]: false,
 | 
					 | 
				
			||||||
            [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
 | 
					 | 
				
			||||||
            [FuseV1Options.OnlyLoadAppFromAsar]: false,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										2389
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2389
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							@ -1,15 +1,13 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "name": "pickle-glass",
 | 
					    "name": "pickle-glass",
 | 
				
			||||||
    "productName": "Glass",
 | 
					    "productName": "Glass",
 | 
				
			||||||
 | 
					 | 
				
			||||||
    "version": "0.2.4",
 | 
					    "version": "0.2.4",
 | 
				
			||||||
 | 
					 | 
				
			||||||
    "description": "Cl*ely for Free",
 | 
					    "description": "Cl*ely for Free",
 | 
				
			||||||
    "main": "src/index.js",
 | 
					    "main": "src/index.js",
 | 
				
			||||||
    "scripts": {
 | 
					    "scripts": {
 | 
				
			||||||
        "setup": "npm install && cd pickleglass_web && npm install && npm run build && cd .. && npm start",
 | 
					        "setup": "npm install && cd pickleglass_web && npm install && npm run build && cd .. && npm start",
 | 
				
			||||||
        "start": "npm run build:renderer && electron-forge start",
 | 
					        "start": "npm run build:renderer && electron .",
 | 
				
			||||||
        "package": "npm run build:renderer && electron-forge package",
 | 
					        "package": "npm run build:all && electron-builder --dir",
 | 
				
			||||||
        "make": "npm run build:renderer && electron-forge make",
 | 
					        "make": "npm run build:renderer && electron-forge make",
 | 
				
			||||||
        "build": "npm run build:all && electron-builder --config electron-builder.yml --publish never",
 | 
					        "build": "npm run build:all && electron-builder --config electron-builder.yml --publish never",
 | 
				
			||||||
        "build:win": "npm run build:all && electron-builder --win --x64 --publish never",
 | 
					        "build:win": "npm run build:all && electron-builder --win --x64 --publish never",
 | 
				
			||||||
@ -58,14 +56,6 @@
 | 
				
			|||||||
        "ws": "^8.18.0"
 | 
					        "ws": "^8.18.0"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "devDependencies": {
 | 
					    "devDependencies": {
 | 
				
			||||||
        "@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",
 | 
					        "@electron/fuses": "^1.8.0",
 | 
				
			||||||
        "@electron/notarize": "^2.5.0",
 | 
					        "@electron/notarize": "^2.5.0",
 | 
				
			||||||
        "electron": "^30.5.1",
 | 
					        "electron": "^30.5.1",
 | 
				
			||||||
@ -77,4 +67,4 @@
 | 
				
			|||||||
    "optionalDependencies": {
 | 
					    "optionalDependencies": {
 | 
				
			||||||
        "electron-liquid-glass": "^1.0.1"
 | 
					        "electron-liquid-glass": "^1.0.1"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -43,9 +43,10 @@ export default function LoginPage() {
 | 
				
			|||||||
            
 | 
					            
 | 
				
			||||||
            window.location.href = deepLinkUrl
 | 
					            window.location.href = deepLinkUrl
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            setTimeout(() => {
 | 
					            // Maybe we don't need this
 | 
				
			||||||
              alert('Login completed. Please return to Pickle Glass app.')
 | 
					            // setTimeout(() => {
 | 
				
			||||||
            }, 1000)
 | 
					            //   alert('Login completed. Please return to Pickle Glass app.')
 | 
				
			||||||
 | 
					            // }, 1000)
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
          } catch (error) {
 | 
					          } catch (error) {
 | 
				
			||||||
            console.error('❌ Deep link processing failed:', error)
 | 
					            console.error('❌ Deep link processing failed:', error)
 | 
				
			||||||
 | 
				
			|||||||
@ -25,15 +25,14 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  static styles = css`
 | 
					  static styles = css`
 | 
				
			||||||
        :host {
 | 
					        :host {
 | 
				
			||||||
            display: block;
 | 
					          display: block;
 | 
				
			||||||
            transform: translate3d(0, 0, 0);
 | 
					          transition: opacity 0.3s ease-in, transform 0.3s ease-in;
 | 
				
			||||||
            backface-visibility: hidden;
 | 
					          will-change: opacity, transform;
 | 
				
			||||||
            transition: opacity 0.25s ease-out;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :host(.sliding-out) {
 | 
					        :host(.sliding-out) {
 | 
				
			||||||
            animation: slideOutUp 0.3s ease-in forwards;
 | 
					            opacity: 0;
 | 
				
			||||||
            will-change: opacity, transform;
 | 
					            transform: translateY(-20px);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :host(.hidden) {
 | 
					        :host(.hidden) {
 | 
				
			||||||
@ -41,17 +40,6 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideOutUp {
 | 
					 | 
				
			||||||
            from {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            to {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-20px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        * {
 | 
					        * {
 | 
				
			||||||
            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
					            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
				
			||||||
            cursor: default;
 | 
					            cursor: default;
 | 
				
			||||||
@ -60,6 +48,7 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .container {
 | 
					        .container {
 | 
				
			||||||
 | 
					            -webkit-app-region: drag;
 | 
				
			||||||
            width: 350px;
 | 
					            width: 350px;
 | 
				
			||||||
            min-height: 260px;
 | 
					            min-height: 260px;
 | 
				
			||||||
            padding: 18px 20px;
 | 
					            padding: 18px 20px;
 | 
				
			||||||
@ -89,6 +78,7 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .close-button {
 | 
					        .close-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            position: absolute;
 | 
					            position: absolute;
 | 
				
			||||||
            top: 10px;
 | 
					            top: 10px;
 | 
				
			||||||
            right: 10px;
 | 
					            right: 10px;
 | 
				
			||||||
@ -168,6 +158,7 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .api-input {
 | 
					        .api-input {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
            height: 34px;
 | 
					            height: 34px;
 | 
				
			||||||
            background: rgba(255, 255, 255, 0.1);
 | 
					            background: rgba(255, 255, 255, 0.1);
 | 
				
			||||||
@ -195,6 +186,7 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
        .provider-column { flex: 1; display: flex; flex-direction: column; align-items: center; }
 | 
					        .provider-column { flex: 1; display: flex; flex-direction: column; align-items: center; }
 | 
				
			||||||
        .provider-label { color: rgba(255, 255, 255, 0.7); font-size: 11px; font-weight: 500; margin-bottom: 6px; }
 | 
					        .provider-label { color: rgba(255, 255, 255, 0.7); font-size: 11px; font-weight: 500; margin-bottom: 6px; }
 | 
				
			||||||
        .api-input, .provider-select {
 | 
					        .api-input, .provider-select {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
            height: 34px;
 | 
					            height: 34px;
 | 
				
			||||||
            text-align: center;
 | 
					            text-align: center;
 | 
				
			||||||
@ -221,6 +213,7 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .action-button {
 | 
					        .action-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
            height: 34px;
 | 
					            height: 34px;
 | 
				
			||||||
            background: rgba(255, 255, 255, 0.2);
 | 
					            background: rgba(255, 255, 255, 0.2);
 | 
				
			||||||
@ -266,37 +259,10 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
            font-weight: 500; /* Medium */
 | 
					            font-weight: 500; /* Medium */
 | 
				
			||||||
            margin: 10px 0;
 | 
					            margin: 10px 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .api-input,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .provider-select,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .container::after,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button::after {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .provider-select:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button:hover {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    `
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    super()
 | 
					    super()
 | 
				
			||||||
    this.dragState = null
 | 
					 | 
				
			||||||
    this.wasJustDragged = false
 | 
					 | 
				
			||||||
    this.isLoading = false
 | 
					    this.isLoading = false
 | 
				
			||||||
    this.errorMessage = ""
 | 
					    this.errorMessage = ""
 | 
				
			||||||
    this.successMessage = ""
 | 
					    this.successMessage = ""
 | 
				
			||||||
@ -358,8 +324,6 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
    this.loadProviderConfig();
 | 
					    this.loadProviderConfig();
 | 
				
			||||||
    //////// after_modelStateService ////////
 | 
					    //////// after_modelStateService ////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.handleMouseMove = this.handleMouseMove.bind(this)
 | 
					 | 
				
			||||||
    this.handleMouseUp = this.handleMouseUp.bind(this)
 | 
					 | 
				
			||||||
    this.handleKeyPress = this.handleKeyPress.bind(this)
 | 
					    this.handleKeyPress = this.handleKeyPress.bind(this)
 | 
				
			||||||
    this.handleSubmit = this.handleSubmit.bind(this)
 | 
					    this.handleSubmit = this.handleSubmit.bind(this)
 | 
				
			||||||
    this.handleInput = this.handleInput.bind(this)
 | 
					    this.handleInput = this.handleInput.bind(this)
 | 
				
			||||||
@ -1533,7 +1497,6 @@ export class ApiKeyHeader extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  handleUsePicklesKey(e) {
 | 
					  handleUsePicklesKey(e) {
 | 
				
			||||||
    e.preventDefault()
 | 
					    e.preventDefault()
 | 
				
			||||||
    if (this.wasJustDragged) return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log("Requesting Firebase authentication from main process...")
 | 
					    console.log("Requesting Firebase authentication from main process...")
 | 
				
			||||||
    if (window.require) {
 | 
					    if (window.require) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,17 +2,16 @@ import { html, css, LitElement } from '../assets/lit-core-2.7.4.min.js';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class MainHeader extends LitElement {
 | 
					export class MainHeader extends LitElement {
 | 
				
			||||||
    static properties = {
 | 
					    static properties = {
 | 
				
			||||||
        isSessionActive: { type: Boolean, state: true },
 | 
					        // isSessionActive: { type: Boolean, state: true },
 | 
				
			||||||
 | 
					        isTogglingSession: { type: Boolean, state: true },
 | 
				
			||||||
 | 
					        actionText: { type: String, state: true },
 | 
				
			||||||
        shortcuts: { type: Object, state: true },
 | 
					        shortcuts: { type: Object, state: true },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static styles = css`
 | 
					    static styles = css`
 | 
				
			||||||
        :host {
 | 
					        :host {
 | 
				
			||||||
            display: flex;
 | 
					            display: flex;
 | 
				
			||||||
            transform: translate3d(0, 0, 0);
 | 
					 | 
				
			||||||
            backface-visibility: hidden;
 | 
					 | 
				
			||||||
            transition: transform 0.2s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.2s ease-out;
 | 
					            transition: transform 0.2s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.2s ease-out;
 | 
				
			||||||
            will-change: transform, opacity;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :host(.hiding) {
 | 
					        :host(.hiding) {
 | 
				
			||||||
@ -33,65 +32,6 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideUp {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.7;
 | 
					 | 
				
			||||||
                transform: translateY(-20%) scale(0.98);
 | 
					 | 
				
			||||||
                filter: blur(0.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            70% {
 | 
					 | 
				
			||||||
                opacity: 0.3;
 | 
					 | 
				
			||||||
                transform: translateY(-80%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @keyframes slideDown {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.5;
 | 
					 | 
				
			||||||
                transform: translateY(-50%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            65% {
 | 
					 | 
				
			||||||
                opacity: 0.9;
 | 
					 | 
				
			||||||
                transform: translateY(-5%) scale(0.99);
 | 
					 | 
				
			||||||
                filter: blur(0.2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            85% {
 | 
					 | 
				
			||||||
                opacity: 0.98;
 | 
					 | 
				
			||||||
                transform: translateY(2%) scale(1.005);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @keyframes fadeIn {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        * {
 | 
					        * {
 | 
				
			||||||
            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
					            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
				
			||||||
@ -100,6 +40,7 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .header {
 | 
					        .header {
 | 
				
			||||||
 | 
					            -webkit-app-region: drag;
 | 
				
			||||||
            width: max-content;
 | 
					            width: max-content;
 | 
				
			||||||
            height: 47px;
 | 
					            height: 47px;
 | 
				
			||||||
            padding: 2px 10px 2px 13px;
 | 
					            padding: 2px 10px 2px 13px;
 | 
				
			||||||
@ -141,6 +82,7 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .listen-button {
 | 
					        .listen-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            height: 26px;
 | 
					            height: 26px;
 | 
				
			||||||
            padding: 0 13px;
 | 
					            padding: 0 13px;
 | 
				
			||||||
            background: transparent;
 | 
					            background: transparent;
 | 
				
			||||||
@ -155,6 +97,11 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
            position: relative;
 | 
					            position: relative;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .listen-button:disabled {
 | 
				
			||||||
 | 
					            cursor: default;
 | 
				
			||||||
 | 
					            opacity: 0.8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .listen-button.active::before {
 | 
					        .listen-button.active::before {
 | 
				
			||||||
            background: rgba(215, 0, 0, 0.5);
 | 
					            background: rgba(215, 0, 0, 0.5);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -163,6 +110,24 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
            background: rgba(255, 20, 20, 0.6);
 | 
					            background: rgba(255, 20, 20, 0.6);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .listen-button.done {
 | 
				
			||||||
 | 
					            background-color: rgba(255, 255, 255, 0.6);
 | 
				
			||||||
 | 
					            transition: background-color 0.15s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .listen-button.done .action-text-content {
 | 
				
			||||||
 | 
					            color: black;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        .listen-button.done .listen-icon svg rect,
 | 
				
			||||||
 | 
					        .listen-button.done .listen-icon svg path {
 | 
				
			||||||
 | 
					            fill: black;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .listen-button.done:hover {
 | 
				
			||||||
 | 
					            background-color: #f0f0f0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .listen-button:hover::before {
 | 
					        .listen-button:hover::before {
 | 
				
			||||||
            background: rgba(255, 255, 255, 0.18);
 | 
					            background: rgba(255, 255, 255, 0.18);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -192,7 +157,40 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .listen-button.done::after {
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .loading-dots {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            gap: 5px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .loading-dots span {
 | 
				
			||||||
 | 
					            width: 6px;
 | 
				
			||||||
 | 
					            height: 6px;
 | 
				
			||||||
 | 
					            background-color: white;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            animation: pulse 1.4s infinite ease-in-out both;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .loading-dots span:nth-of-type(1) {
 | 
				
			||||||
 | 
					            animation-delay: -0.32s;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .loading-dots span:nth-of-type(2) {
 | 
				
			||||||
 | 
					            animation-delay: -0.16s;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        @keyframes pulse {
 | 
				
			||||||
 | 
					            0%, 80%, 100% {
 | 
				
			||||||
 | 
					                opacity: 0.2;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            40% {
 | 
				
			||||||
 | 
					                opacity: 1.0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .header-actions {
 | 
					        .header-actions {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            height: 26px;
 | 
					            height: 26px;
 | 
				
			||||||
            box-sizing: border-box;
 | 
					            box-sizing: border-box;
 | 
				
			||||||
            justify-content: flex-start;
 | 
					            justify-content: flex-start;
 | 
				
			||||||
@ -264,6 +262,7 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .settings-button {
 | 
					        .settings-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            padding: 5px;
 | 
					            padding: 5px;
 | 
				
			||||||
            border-radius: 50%;
 | 
					            border-radius: 50%;
 | 
				
			||||||
            background: transparent;
 | 
					            background: transparent;
 | 
				
			||||||
@ -291,125 +290,22 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
            width: 16px;
 | 
					            width: 16px;
 | 
				
			||||||
            height: 16px;
 | 
					            height: 16px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .listen-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header-actions,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .settings-button {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .icon-box {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header::before,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header::after,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .listen-button::before,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .listen-button::after {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header-actions:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .settings-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .listen-button:hover::before {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) * {
 | 
					 | 
				
			||||||
            animation: none !important;
 | 
					 | 
				
			||||||
            transition: none !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .listen-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .header-actions,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .settings-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .icon-box {
 | 
					 | 
				
			||||||
            border-radius: 0 !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) {
 | 
					 | 
				
			||||||
            animation: none !important;
 | 
					 | 
				
			||||||
            transition: none !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
            will-change: auto !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        `;
 | 
					        `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
        this.shortcuts = {};
 | 
					        this.shortcuts = {};
 | 
				
			||||||
        this.dragState = null;
 | 
					 | 
				
			||||||
        this.wasJustDragged = false;
 | 
					 | 
				
			||||||
        this.isVisible = true;
 | 
					        this.isVisible = true;
 | 
				
			||||||
        this.isAnimating = false;
 | 
					        this.isAnimating = false;
 | 
				
			||||||
        this.hasSlidIn = false;
 | 
					        this.hasSlidIn = false;
 | 
				
			||||||
        this.settingsHideTimer = null;
 | 
					        this.settingsHideTimer = null;
 | 
				
			||||||
        this.isSessionActive = false;
 | 
					        // this.isSessionActive = false;
 | 
				
			||||||
 | 
					        this.isTogglingSession = false;
 | 
				
			||||||
 | 
					        this.actionText = 'Listen';
 | 
				
			||||||
        this.animationEndTimer = null;
 | 
					        this.animationEndTimer = null;
 | 
				
			||||||
        this.handleMouseMove = this.handleMouseMove.bind(this);
 | 
					 | 
				
			||||||
        this.handleMouseUp = this.handleMouseUp.bind(this);
 | 
					 | 
				
			||||||
        this.handleAnimationEnd = this.handleAnimationEnd.bind(this);
 | 
					        this.handleAnimationEnd = this.handleAnimationEnd.bind(this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleMouseDown(e) {
 | 
					 | 
				
			||||||
        e.preventDefault();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					 | 
				
			||||||
        const initialPosition = await ipcRenderer.invoke('get-header-position');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.dragState = {
 | 
					 | 
				
			||||||
            initialMouseX: e.screenX,
 | 
					 | 
				
			||||||
            initialMouseY: e.screenY,
 | 
					 | 
				
			||||||
            initialWindowX: initialPosition.x,
 | 
					 | 
				
			||||||
            initialWindowY: initialPosition.y,
 | 
					 | 
				
			||||||
            moved: false,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener('mousemove', this.handleMouseMove, { capture: true });
 | 
					 | 
				
			||||||
        window.addEventListener('mouseup', this.handleMouseUp, { once: true, capture: true });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleMouseMove(e) {
 | 
					 | 
				
			||||||
        if (!this.dragState) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const deltaX = Math.abs(e.screenX - this.dragState.initialMouseX);
 | 
					 | 
				
			||||||
        const deltaY = Math.abs(e.screenY - this.dragState.initialMouseY);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (deltaX > 3 || deltaY > 3) {
 | 
					 | 
				
			||||||
            this.dragState.moved = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
 | 
					 | 
				
			||||||
        const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					 | 
				
			||||||
        ipcRenderer.invoke('move-header-to', newWindowX, newWindowY);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleMouseUp(e) {
 | 
					 | 
				
			||||||
        if (!this.dragState) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const wasDragged = this.dragState.moved;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.removeEventListener('mousemove', this.handleMouseMove, { capture: true });
 | 
					 | 
				
			||||||
        this.dragState = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (wasDragged) {
 | 
					 | 
				
			||||||
            this.wasJustDragged = true;
 | 
					 | 
				
			||||||
            setTimeout(() => {
 | 
					 | 
				
			||||||
                this.wasJustDragged = false;
 | 
					 | 
				
			||||||
            }, 0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    toggleVisibility() {
 | 
					    toggleVisibility() {
 | 
				
			||||||
        if (this.isAnimating) {
 | 
					        if (this.isAnimating) {
 | 
				
			||||||
            console.log('[MainHeader] Animation already in progress, ignoring toggle');
 | 
					            console.log('[MainHeader] Animation already in progress, ignoring toggle');
 | 
				
			||||||
@ -431,58 +327,29 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hide() {
 | 
					    hide() {
 | 
				
			||||||
        this.classList.remove('showing', 'hidden');
 | 
					        this.classList.remove('showing');
 | 
				
			||||||
        this.classList.add('hiding');
 | 
					        this.classList.add('hiding');
 | 
				
			||||||
        this.isVisible = false;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        this.animationEndTimer = setTimeout(() => {
 | 
					 | 
				
			||||||
            if (this.classList.contains('hiding')) {
 | 
					 | 
				
			||||||
                this.handleAnimationEnd({ target: this });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, 350);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    show() {
 | 
					    show() {
 | 
				
			||||||
        this.classList.remove('hiding', 'hidden');
 | 
					        this.classList.remove('hiding', 'hidden');
 | 
				
			||||||
        this.classList.add('showing');
 | 
					        this.classList.add('showing');
 | 
				
			||||||
        this.isVisible = true;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        this.animationEndTimer = setTimeout(() => {
 | 
					 | 
				
			||||||
            if (this.classList.contains('showing')) {
 | 
					 | 
				
			||||||
                this.handleAnimationEnd({ target: this });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, 400);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    handleAnimationEnd(e) {
 | 
					    handleAnimationEnd(e) {
 | 
				
			||||||
        if (e.target !== this) return;
 | 
					        if (e.target !== this) return;
 | 
				
			||||||
        
 | 
					    
 | 
				
			||||||
        if (this.animationEndTimer) {
 | 
					 | 
				
			||||||
            clearTimeout(this.animationEndTimer);
 | 
					 | 
				
			||||||
            this.animationEndTimer = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        this.isAnimating = false;
 | 
					        this.isAnimating = false;
 | 
				
			||||||
        
 | 
					    
 | 
				
			||||||
        if (this.classList.contains('hiding')) {
 | 
					        if (this.classList.contains('hiding')) {
 | 
				
			||||||
            this.classList.remove('hiding');
 | 
					 | 
				
			||||||
            this.classList.add('hidden');
 | 
					            this.classList.add('hidden');
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (window.require) {
 | 
					            if (window.require) {
 | 
				
			||||||
                const { ipcRenderer } = window.require('electron');
 | 
					                window.require('electron').ipcRenderer.send('header-animation-finished', 'hidden');
 | 
				
			||||||
                ipcRenderer.send('header-animation-complete', 'hidden');
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (this.classList.contains('showing')) {
 | 
					        } else if (this.classList.contains('showing')) {
 | 
				
			||||||
            this.classList.remove('showing');
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (window.require) {
 | 
					            if (window.require) {
 | 
				
			||||||
                const { ipcRenderer } = window.require('electron');
 | 
					                window.require('electron').ipcRenderer.send('header-animation-finished', 'visible');
 | 
				
			||||||
                ipcRenderer.send('header-animation-complete', 'visible');
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (this.classList.contains('sliding-in')) {
 | 
					 | 
				
			||||||
            this.classList.remove('sliding-in');
 | 
					 | 
				
			||||||
            this.hasSlidIn = true;
 | 
					 | 
				
			||||||
            console.log('[MainHeader] Slide-in animation completed');
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -497,10 +364,19 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            this._sessionStateListener = (event, { isActive }) => {
 | 
					
 | 
				
			||||||
                this.isSessionActive = isActive;
 | 
					            this._sessionStateTextListener = (event, text) => {
 | 
				
			||||||
 | 
					                this.actionText = text;
 | 
				
			||||||
 | 
					                this.isTogglingSession = false;
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            ipcRenderer.on('session-state-changed', this._sessionStateListener);
 | 
					            ipcRenderer.on('session-state-text', this._sessionStateTextListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // this._sessionStateListener = (event, { isActive }) => {
 | 
				
			||||||
 | 
					            //     this.isSessionActive = isActive;
 | 
				
			||||||
 | 
					            //     this.isTogglingSession = false;
 | 
				
			||||||
 | 
					            // };
 | 
				
			||||||
 | 
					            // ipcRenderer.on('session-state-changed', this._sessionStateListener);
 | 
				
			||||||
            this._shortcutListener = (event, keybinds) => {
 | 
					            this._shortcutListener = (event, keybinds) => {
 | 
				
			||||||
                console.log('[MainHeader] Received updated shortcuts:', keybinds);
 | 
					                console.log('[MainHeader] Received updated shortcuts:', keybinds);
 | 
				
			||||||
                this.shortcuts = keybinds;
 | 
					                this.shortcuts = keybinds;
 | 
				
			||||||
@ -520,9 +396,12 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            if (this._sessionStateListener) {
 | 
					            if (this._sessionStateTextListener) {
 | 
				
			||||||
                ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
 | 
					                ipcRenderer.removeListener('session-state-text', this._sessionStateTextListener);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            // if (this._sessionStateListener) {
 | 
				
			||||||
 | 
					            //     ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
 | 
				
			||||||
 | 
					            // }
 | 
				
			||||||
            if (this._shortcutListener) {
 | 
					            if (this._shortcutListener) {
 | 
				
			||||||
                ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
 | 
					                ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -530,51 +409,56 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    invoke(channel, ...args) {
 | 
					    invoke(channel, ...args) {
 | 
				
			||||||
        if (this.wasJustDragged) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            window.require('electron').ipcRenderer.invoke(channel, ...args);
 | 
					            window.require('electron').ipcRenderer.invoke(channel, ...args);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // return Promise.resolve();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    showWindow(name, element) {
 | 
					    showSettingsWindow(element) {
 | 
				
			||||||
        if (this.wasJustDragged) return;
 | 
					 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            console.log(`[MainHeader] showWindow('${name}') called at ${Date.now()}`);
 | 
					            console.log(`[MainHeader] showSettingsWindow called at ${Date.now()}`);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            ipcRenderer.send('cancel-hide-window', name);
 | 
					            ipcRenderer.send('cancel-hide-settings-window');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (name === 'settings' && element) {
 | 
					            if (element) {
 | 
				
			||||||
                const rect = element.getBoundingClientRect();
 | 
					                const { left, top, width, height } = element.getBoundingClientRect();
 | 
				
			||||||
                ipcRenderer.send('show-window', {
 | 
					                ipcRenderer.send('show-settings-window', {
 | 
				
			||||||
                    name: 'settings',
 | 
					                    x: left,
 | 
				
			||||||
                    bounds: {
 | 
					                    y: top,
 | 
				
			||||||
                        x: rect.left,
 | 
					                    width,
 | 
				
			||||||
                        y: rect.top,
 | 
					                    height,
 | 
				
			||||||
                        width: rect.width,
 | 
					 | 
				
			||||||
                        height: rect.height
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                ipcRenderer.send('show-window', name);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hideWindow(name) {
 | 
					    hideSettingsWindow() {
 | 
				
			||||||
        if (this.wasJustDragged) return;
 | 
					 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            console.log(`[MainHeader] hideWindow('${name}') called at ${Date.now()}`);
 | 
					            console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
 | 
				
			||||||
            window.require('electron').ipcRenderer.send('hide-window', name);
 | 
					            window.require('electron').ipcRenderer.send('hide-settings-window');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cancelHideWindow(name) {
 | 
					    async _handleListenClick() {
 | 
				
			||||||
 | 
					        if (this.isTogglingSession) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.isTogglingSession = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const channel = 'toggle-feature';
 | 
				
			||||||
 | 
					            const args = ['listen'];
 | 
				
			||||||
 | 
					            await this.invoke(channel, ...args);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error('IPC invoke for session toggle failed:', error);
 | 
				
			||||||
 | 
					            this.isTogglingSession = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderShortcut(accelerator) {
 | 
					    renderShortcut(accelerator) {
 | 
				
			||||||
        if (!accelerator) return html``;
 | 
					        if (!accelerator) return html``;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -599,31 +483,45 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        return html`
 | 
					        const buttonClasses = {
 | 
				
			||||||
            <div class="header" @mousedown=${this.handleMouseDown}>
 | 
					            active: this.actionText === 'Stop',
 | 
				
			||||||
                <button 
 | 
					            done: this.actionText === 'Done',
 | 
				
			||||||
                    class="listen-button ${this.isSessionActive ? 'active' : ''}"
 | 
					        };
 | 
				
			||||||
                    @click=${() => this.invoke(this.isSessionActive ? 'close-session' : 'toggle-feature', 'listen')}
 | 
					        const showStopIcon = this.actionText === 'Stop' || this.actionText === 'Done';
 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <div class="action-text">
 | 
					 | 
				
			||||||
                        <div class="action-text-content">${this.isSessionActive ? 'Stop' : 'Listen'}</div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div class="listen-icon">
 | 
					 | 
				
			||||||
                        ${this.isSessionActive
 | 
					 | 
				
			||||||
                            ? html`
 | 
					 | 
				
			||||||
                                <svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
                                    <rect width="9" height="9" rx="1" fill="white"/>
 | 
					 | 
				
			||||||
                                </svg>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            `
 | 
					        return html`
 | 
				
			||||||
                            : html`
 | 
					            <div class="header">
 | 
				
			||||||
                                <svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
					                <button 
 | 
				
			||||||
                                    <path d="M1.69922 2.7515C1.69922 2.37153 2.00725 2.0635 2.38722 2.0635H2.73122C3.11119 2.0635 3.41922 2.37153 3.41922 2.7515V8.2555C3.41922 8.63547 3.11119 8.9435 2.73122 8.9435H2.38722C2.00725 8.9435 1.69922 8.63547 1.69922 8.2555V2.7515Z" fill="white"/>
 | 
					                    class="listen-button ${Object.keys(buttonClasses).filter(k => buttonClasses[k]).join(' ')}"
 | 
				
			||||||
                                    <path d="M5.13922 1.3755C5.13922 0.995528 5.44725 0.6875 5.82722 0.6875H6.17122C6.55119 0.6875 6.85922 0.995528 6.85922 1.3755V9.6315C6.85922 10.0115 6.55119 10.3195 6.17122 10.3195H5.82722C5.44725 10.3195 5.13922 10.0115 5.13922 9.6315V1.3755Z" fill="white"/>
 | 
					                    @click=${this._handleListenClick}
 | 
				
			||||||
                                    <path d="M8.57922 3.0955C8.57922 2.71553 8.88725 2.4075 9.26722 2.4075H9.61122C9.99119 2.4075 10.2992 2.71553 10.2992 3.0955V7.9115C10.2992 8.29147 9.99119 8.5995 9.61122 8.5995H9.26722C8.88725 8.5995 8.57922 8.29147 8.57922 7.9115V3.0955Z" fill="white"/>
 | 
					                    ?disabled=${this.isTogglingSession}
 | 
				
			||||||
                                </svg>
 | 
					                >
 | 
				
			||||||
                            `}
 | 
					                    ${this.isTogglingSession
 | 
				
			||||||
                    </div>
 | 
					                        ? html`
 | 
				
			||||||
 | 
					                            <div class="loading-dots">
 | 
				
			||||||
 | 
					                                <span></span><span></span><span></span>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        `
 | 
				
			||||||
 | 
					                        : html`
 | 
				
			||||||
 | 
					                            <div class="action-text">
 | 
				
			||||||
 | 
					                                <div class="action-text-content">${this.actionText}</div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="listen-icon">
 | 
				
			||||||
 | 
					                                ${showStopIcon
 | 
				
			||||||
 | 
					                                    ? html`
 | 
				
			||||||
 | 
					                                        <svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					                                            <rect width="9" height="9" rx="1" fill="white"/>
 | 
				
			||||||
 | 
					                                        </svg>
 | 
				
			||||||
 | 
					                                    `
 | 
				
			||||||
 | 
					                                    : html`
 | 
				
			||||||
 | 
					                                        <svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					                                            <path d="M1.69922 2.7515C1.69922 2.37153 2.00725 2.0635 2.38722 2.0635H2.73122C3.11119 2.0635 3.41922 2.37153 3.41922 2.7515V8.2555C3.41922 8.63547 3.11119 8.9435 2.73122 8.9435H2.38722C2.00725 8.9435 1.69922 8.63547 1.69922 8.2555V2.7515Z" fill="white"/>
 | 
				
			||||||
 | 
					                                            <path d="M5.13922 1.3755C5.13922 0.995528 5.44725 0.6875 5.82722 0.6875H6.17122C6.55119 0.6875 6.85922 0.995528 6.85922 1.3755V9.6315C6.85922 10.0115 6.55119 10.3195 6.17122 10.3195H5.82722C5.44725 10.3195 5.13922 10.0115 5.13922 9.6315V1.3755Z" fill="white"/>
 | 
				
			||||||
 | 
					                                            <path d="M8.57922 3.0955C8.57922 2.71553 8.88725 2.4075 9.26722 2.4075H9.61122C9.99119 2.4075 10.2992 2.71553 10.2992 3.0955V7.9115C10.2992 8.29147 9.99119 8.5995 9.61122 8.5995H9.26722C8.88725 8.5995 8.57922 8.29147 8.57922 7.9115V3.0955Z" fill="white"/>
 | 
				
			||||||
 | 
					                                        </svg>
 | 
				
			||||||
 | 
					                                    `}
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        `}
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="header-actions ask-action" @click=${() => this.invoke('toggle-feature', 'ask')}>
 | 
					                <div class="header-actions ask-action" @click=${() => this.invoke('toggle-feature', 'ask')}>
 | 
				
			||||||
@ -646,8 +544,8 @@ export class MainHeader extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                <button 
 | 
					                <button 
 | 
				
			||||||
                    class="settings-button"
 | 
					                    class="settings-button"
 | 
				
			||||||
                    @mouseenter=${(e) => this.showWindow('settings', e.currentTarget)}
 | 
					                    @mouseenter=${(e) => this.showSettingsWindow(e.currentTarget)}
 | 
				
			||||||
                    @mouseleave=${() => this.hideWindow('settings')}
 | 
					                    @mouseleave=${() => this.hideSettingsWindow()}
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <div class="settings-icon">
 | 
					                    <div class="settings-icon">
 | 
				
			||||||
                        <svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
					                        <svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
				
			|||||||
@ -4,14 +4,13 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
    static styles = css`
 | 
					    static styles = css`
 | 
				
			||||||
        :host {
 | 
					        :host {
 | 
				
			||||||
            display: block;
 | 
					            display: block;
 | 
				
			||||||
            transform: translate3d(0, 0, 0);
 | 
					            transition: opacity 0.3s ease-in, transform 0.3s ease-in;
 | 
				
			||||||
            backface-visibility: hidden;
 | 
					            will-change: opacity, transform;
 | 
				
			||||||
            transition: opacity 0.25s ease-out;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :host(.sliding-out) {
 | 
					        :host(.sliding-out) {
 | 
				
			||||||
            animation: slideOutUp 0.3s ease-in forwards;
 | 
					            opacity: 0;
 | 
				
			||||||
            will-change: opacity, transform;
 | 
					            transform: translateY(-20px);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :host(.hidden) {
 | 
					        :host(.hidden) {
 | 
				
			||||||
@ -19,17 +18,6 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideOutUp {
 | 
					 | 
				
			||||||
            from {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            to {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-20px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        * {
 | 
					        * {
 | 
				
			||||||
            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
					            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
				
			||||||
            cursor: default;
 | 
					            cursor: default;
 | 
				
			||||||
@ -38,6 +26,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .container {
 | 
					        .container {
 | 
				
			||||||
 | 
					            -webkit-app-region: drag;
 | 
				
			||||||
            width: 285px;
 | 
					            width: 285px;
 | 
				
			||||||
            height: 220px;
 | 
					            height: 220px;
 | 
				
			||||||
            padding: 18px 20px;
 | 
					            padding: 18px 20px;
 | 
				
			||||||
@ -67,6 +56,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .close-button {
 | 
					        .close-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            position: absolute;
 | 
					            position: absolute;
 | 
				
			||||||
            top: 10px;
 | 
					            top: 10px;
 | 
				
			||||||
            right: 10px;
 | 
					            right: 10px;
 | 
				
			||||||
@ -157,6 +147,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .action-button {
 | 
					        .action-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
            height: 34px;
 | 
					            height: 34px;
 | 
				
			||||||
            background: rgba(255, 255, 255, 0.2);
 | 
					            background: rgba(255, 255, 255, 0.2);
 | 
				
			||||||
@ -198,6 +189,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .continue-button {
 | 
					        .continue-button {
 | 
				
			||||||
 | 
					            -webkit-app-region: no-drag;
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
            height: 34px;
 | 
					            height: 34px;
 | 
				
			||||||
            background: rgba(34, 197, 94, 0.8);
 | 
					            background: rgba(34, 197, 94, 0.8);
 | 
				
			||||||
@ -237,30 +229,6 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
            background: rgba(255, 255, 255, 0.2);
 | 
					            background: rgba(255, 255, 255, 0.2);
 | 
				
			||||||
            cursor: not-allowed;
 | 
					            cursor: not-allowed;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .continue-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .container::after,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button::after,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .continue-button::after {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .action-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .continue-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button:hover {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static properties = {
 | 
					    static properties = {
 | 
				
			||||||
@ -276,9 +244,6 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        this.screenGranted = 'unknown';
 | 
					        this.screenGranted = 'unknown';
 | 
				
			||||||
        this.isChecking = false;
 | 
					        this.isChecking = false;
 | 
				
			||||||
        this.continueCallback = null;
 | 
					        this.continueCallback = null;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.handleMouseMove = this.handleMouseMove.bind(this);
 | 
					 | 
				
			||||||
        this.handleMouseUp = this.handleMouseUp.bind(this);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async connectedCallback() {
 | 
					    async connectedCallback() {
 | 
				
			||||||
@ -298,61 +263,6 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleMouseDown(e) {
 | 
					 | 
				
			||||||
        if (e.target.tagName === 'BUTTON') {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        e.preventDefault();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					 | 
				
			||||||
        const initialPosition = await ipcRenderer.invoke('get-header-position');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.dragState = {
 | 
					 | 
				
			||||||
            initialMouseX: e.screenX,
 | 
					 | 
				
			||||||
            initialMouseY: e.screenY,
 | 
					 | 
				
			||||||
            initialWindowX: initialPosition.x,
 | 
					 | 
				
			||||||
            initialWindowY: initialPosition.y,
 | 
					 | 
				
			||||||
            moved: false,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.addEventListener('mousemove', this.handleMouseMove);
 | 
					 | 
				
			||||||
        window.addEventListener('mouseup', this.handleMouseUp, { once: true });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleMouseMove(e) {
 | 
					 | 
				
			||||||
        if (!this.dragState) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const deltaX = Math.abs(e.screenX - this.dragState.initialMouseX);
 | 
					 | 
				
			||||||
        const deltaY = Math.abs(e.screenY - this.dragState.initialMouseY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (deltaX > 3 || deltaY > 3) {
 | 
					 | 
				
			||||||
            this.dragState.moved = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const newWindowX = this.dragState.initialWindowX + (e.screenX - this.dragState.initialMouseX);
 | 
					 | 
				
			||||||
        const newWindowY = this.dragState.initialWindowY + (e.screenY - this.dragState.initialMouseY);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					 | 
				
			||||||
        ipcRenderer.invoke('move-header-to', newWindowX, newWindowY);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleMouseUp(e) {
 | 
					 | 
				
			||||||
        if (!this.dragState) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const wasDragged = this.dragState.moved;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        window.removeEventListener('mousemove', this.handleMouseMove);
 | 
					 | 
				
			||||||
        this.dragState = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (wasDragged) {
 | 
					 | 
				
			||||||
            this.wasJustDragged = true;
 | 
					 | 
				
			||||||
            setTimeout(() => {
 | 
					 | 
				
			||||||
                this.wasJustDragged = false;
 | 
					 | 
				
			||||||
            }, 200);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async checkPermissions() {
 | 
					    async checkPermissions() {
 | 
				
			||||||
        if (!window.require || this.isChecking) return;
 | 
					        if (!window.require || this.isChecking) return;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@ -390,7 +300,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleMicrophoneClick() {
 | 
					    async handleMicrophoneClick() {
 | 
				
			||||||
        if (!window.require || this.microphoneGranted === 'granted' || this.wasJustDragged) return;
 | 
					        if (!window.require || this.microphoneGranted === 'granted') return;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        console.log('[PermissionHeader] Requesting microphone permission...');
 | 
					        console.log('[PermissionHeader] Requesting microphone permission...');
 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					        const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
@ -423,7 +333,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleScreenClick() {
 | 
					    async handleScreenClick() {
 | 
				
			||||||
        if (!window.require || this.screenGranted === 'granted' || this.wasJustDragged) return;
 | 
					        if (!window.require || this.screenGranted === 'granted') return;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        console.log('[PermissionHeader] Checking screen recording permission...');
 | 
					        console.log('[PermissionHeader] Checking screen recording permission...');
 | 
				
			||||||
        const { ipcRenderer } = window.require('electron');
 | 
					        const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
@ -453,8 +363,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
    async handleContinue() {
 | 
					    async handleContinue() {
 | 
				
			||||||
        if (this.continueCallback && 
 | 
					        if (this.continueCallback && 
 | 
				
			||||||
            this.microphoneGranted === 'granted' && 
 | 
					            this.microphoneGranted === 'granted' && 
 | 
				
			||||||
            this.screenGranted === 'granted' && 
 | 
					            this.screenGranted === 'granted') {
 | 
				
			||||||
            !this.wasJustDragged) {
 | 
					 | 
				
			||||||
            // Mark permissions as completed
 | 
					            // Mark permissions as completed
 | 
				
			||||||
            if (window.require) {
 | 
					            if (window.require) {
 | 
				
			||||||
                const { ipcRenderer } = window.require('electron');
 | 
					                const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
@ -481,7 +390,7 @@ export class PermissionHeader extends LitElement {
 | 
				
			|||||||
        const allGranted = this.microphoneGranted === 'granted' && this.screenGranted === 'granted';
 | 
					        const allGranted = this.microphoneGranted === 'granted' && this.screenGranted === 'granted';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return html`
 | 
					        return html`
 | 
				
			||||||
            <div class="container" @mousedown=${this.handleMouseDown}>
 | 
					            <div class="container">
 | 
				
			||||||
                <button class="close-button" @click=${this.handleClose} title="Close application">
 | 
					                <button class="close-button" @click=${this.handleClose} title="Close application">
 | 
				
			||||||
                    <svg width="8" height="8" viewBox="0 0 10 10" fill="currentColor">
 | 
					                    <svg width="8" height="8" viewBox="0 0 10 10" fill="currentColor">
 | 
				
			||||||
                        <path d="M1 1L9 9M9 1L1 9" stroke="currentColor" stroke-width="1.2" />
 | 
					                        <path d="M1 1L9 9M9 1L1 9" stroke="currentColor" stroke-width="1.2" />
 | 
				
			||||||
 | 
				
			|||||||
@ -68,12 +68,7 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
        this.selectedScreenshotInterval = localStorage.getItem('selectedScreenshotInterval') || '5';
 | 
					        this.selectedScreenshotInterval = localStorage.getItem('selectedScreenshotInterval') || '5';
 | 
				
			||||||
        this.selectedImageQuality = localStorage.getItem('selectedImageQuality') || 'medium';
 | 
					        this.selectedImageQuality = localStorage.getItem('selectedImageQuality') || 'medium';
 | 
				
			||||||
        this._isClickThrough = false;
 | 
					        this._isClickThrough = false;
 | 
				
			||||||
        this.outlines = [];
 | 
					 | 
				
			||||||
        this.analysisRequests = [];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        window.pickleGlass.setStructuredData = data => {
 | 
					 | 
				
			||||||
            this.updateStructuredData(data);
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connectedCallback() {
 | 
					    connectedCallback() {
 | 
				
			||||||
@ -82,14 +77,13 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            ipcRenderer.on('update-status', (_, status) => this.setStatus(status));
 | 
					 | 
				
			||||||
            ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
 | 
					            ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
 | 
				
			||||||
                this._isClickThrough = isEnabled;
 | 
					                this._isClickThrough = isEnabled;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            ipcRenderer.on('start-listening-session', () => {
 | 
					            // ipcRenderer.on('start-listening-session', () => {
 | 
				
			||||||
                console.log('Received start-listening-session command, calling handleListenClick.');
 | 
					            //     console.log('Received start-listening-session command, calling handleListenClick.');
 | 
				
			||||||
                this.handleListenClick();
 | 
					            //     this.handleListenClick();
 | 
				
			||||||
            });
 | 
					            // });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,16 +91,15 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
        super.disconnectedCallback();
 | 
					        super.disconnectedCallback();
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            ipcRenderer.removeAllListeners('update-status');
 | 
					 | 
				
			||||||
            ipcRenderer.removeAllListeners('click-through-toggled');
 | 
					            ipcRenderer.removeAllListeners('click-through-toggled');
 | 
				
			||||||
            ipcRenderer.removeAllListeners('start-listening-session');
 | 
					            // ipcRenderer.removeAllListeners('start-listening-session');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updated(changedProperties) {
 | 
					    updated(changedProperties) {
 | 
				
			||||||
        if (changedProperties.has('isMainViewVisible') || changedProperties.has('currentView')) {
 | 
					        // if (changedProperties.has('isMainViewVisible') || changedProperties.has('currentView')) {
 | 
				
			||||||
            this.requestWindowResize();
 | 
					        //     this.requestWindowResize();
 | 
				
			||||||
        }
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (changedProperties.has('currentView')) {
 | 
					        if (changedProperties.has('currentView')) {
 | 
				
			||||||
            const viewContainer = this.shadowRoot?.querySelector('.view-container');
 | 
					            const viewContainer = this.shadowRoot?.querySelector('.view-container');
 | 
				
			||||||
@ -136,67 +129,35 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    requestWindowResize() {
 | 
					 | 
				
			||||||
        if (window.require) {
 | 
					 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					 | 
				
			||||||
            ipcRenderer.invoke('resize-window', {
 | 
					 | 
				
			||||||
                isMainViewVisible: this.isMainViewVisible,
 | 
					 | 
				
			||||||
                view: this.currentView,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setStatus(text) {
 | 
					    // async handleListenClick() {
 | 
				
			||||||
        this.statusText = text;
 | 
					    //     if (window.require) {
 | 
				
			||||||
    }
 | 
					    //         const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
 | 
					    //         const isActive = await ipcRenderer.invoke('is-session-active');
 | 
				
			||||||
 | 
					    //         // if (isActive) {
 | 
				
			||||||
 | 
					    //         //     console.log('Session is already active. No action needed.');
 | 
				
			||||||
 | 
					    //         //     return;
 | 
				
			||||||
 | 
					    //         // }
 | 
				
			||||||
 | 
					    //     }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleListenClick() {
 | 
					    //     if (window.pickleGlass) {
 | 
				
			||||||
        if (window.require) {
 | 
					    //         // await window.pickleGlass.initializeopenai(this.selectedProfile, this.selectedLanguage);
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					    //         window.pickleGlass.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality);
 | 
				
			||||||
            const isActive = await ipcRenderer.invoke('is-session-active');
 | 
					    //     }
 | 
				
			||||||
            if (isActive) {
 | 
					 | 
				
			||||||
                console.log('Session is already active. No action needed.');
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (window.pickleGlass) {
 | 
					    //     // 🔄 Clear previous summary/analysis when a new listening session begins
 | 
				
			||||||
            await window.pickleGlass.initializeopenai(this.selectedProfile, this.selectedLanguage);
 | 
					    //     this.structuredData = {
 | 
				
			||||||
            window.pickleGlass.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality);
 | 
					    //         summary: [],
 | 
				
			||||||
        }
 | 
					    //         topic: { header: '', bullets: [] },
 | 
				
			||||||
 | 
					    //         actions: [],
 | 
				
			||||||
 | 
					    //         followUps: [],
 | 
				
			||||||
 | 
					    //     };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 🔄 Clear previous summary/analysis when a new listening session begins
 | 
					    //     this.currentResponseIndex = -1;
 | 
				
			||||||
        this.structuredData = {
 | 
					    //     this.startTime = Date.now();
 | 
				
			||||||
            summary: [],
 | 
					    //     this.currentView = 'listen';
 | 
				
			||||||
            topic: { header: '', bullets: [] },
 | 
					    //     this.isMainViewVisible = true;
 | 
				
			||||||
            actions: [],
 | 
					    // }
 | 
				
			||||||
            followUps: [],
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.currentResponseIndex = -1;
 | 
					 | 
				
			||||||
        this.startTime = Date.now();
 | 
					 | 
				
			||||||
        this.currentView = 'listen';
 | 
					 | 
				
			||||||
        this.isMainViewVisible = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleShowHideClick() {
 | 
					 | 
				
			||||||
        this.isMainViewVisible = !this.isMainViewVisible;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleSettingsClick() {
 | 
					 | 
				
			||||||
        this.currentView = 'settings';
 | 
					 | 
				
			||||||
        this.isMainViewVisible = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleHelpClick() {
 | 
					 | 
				
			||||||
        this.currentView = 'help';
 | 
					 | 
				
			||||||
        this.isMainViewVisible = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleHistoryClick() {
 | 
					 | 
				
			||||||
        this.currentView = 'history';
 | 
					 | 
				
			||||||
        this.isMainViewVisible = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleClose() {
 | 
					    async handleClose() {
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
@ -205,50 +166,8 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handleBackClick() {
 | 
					 | 
				
			||||||
        this.currentView = 'listen';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async handleSendText(message) {
 | 
					 | 
				
			||||||
        if (window.pickleGlass) {
 | 
					 | 
				
			||||||
            const result = await window.pickleGlass.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...');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // updateOutline(outline) {
 | 
					 | 
				
			||||||
    //     console.log('📝 PickleGlassApp updateOutline:', outline);
 | 
					 | 
				
			||||||
    //     this.outlines = [...outline];
 | 
					 | 
				
			||||||
    //     this.requestUpdate();
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // updateAnalysisRequests(requests) {
 | 
					 | 
				
			||||||
    //     console.log('📝 PickleGlassApp updateAnalysisRequests:', requests);
 | 
					 | 
				
			||||||
    //     this.analysisRequests = [...requests];
 | 
					 | 
				
			||||||
    //     this.requestUpdate();
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateStructuredData(data) {
 | 
					 | 
				
			||||||
        console.log('📝 PickleGlassApp updateStructuredData:', data);
 | 
					 | 
				
			||||||
        this.structuredData = data;
 | 
					 | 
				
			||||||
        this.requestUpdate();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        const assistantView = this.shadowRoot?.querySelector('assistant-view');
 | 
					 | 
				
			||||||
        if (assistantView) {
 | 
					 | 
				
			||||||
            assistantView.structuredData = data;
 | 
					 | 
				
			||||||
            console.log('✅ Structured data passed to AssistantView');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    handleResponseIndexChanged(e) {
 | 
					 | 
				
			||||||
        this.currentResponseIndex = e.detail.index;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render() {
 | 
					    render() {
 | 
				
			||||||
        switch (this.currentView) {
 | 
					        switch (this.currentView) {
 | 
				
			||||||
@ -257,7 +176,6 @@ export class PickleGlassApp extends LitElement {
 | 
				
			|||||||
                    .currentResponseIndex=${this.currentResponseIndex}
 | 
					                    .currentResponseIndex=${this.currentResponseIndex}
 | 
				
			||||||
                    .selectedProfile=${this.selectedProfile}
 | 
					                    .selectedProfile=${this.selectedProfile}
 | 
				
			||||||
                    .structuredData=${this.structuredData}
 | 
					                    .structuredData=${this.structuredData}
 | 
				
			||||||
                    .onSendText=${message => this.handleSendText(message)}
 | 
					 | 
				
			||||||
                    @response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
 | 
					                    @response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
 | 
				
			||||||
                ></assistant-view>`;
 | 
					                ></assistant-view>`;
 | 
				
			||||||
            case 'ask':
 | 
					            case 'ask':
 | 
				
			||||||
 | 
				
			|||||||
@ -237,63 +237,62 @@
 | 
				
			|||||||
        <script>
 | 
					        <script>
 | 
				
			||||||
            window.addEventListener('DOMContentLoaded', () => {
 | 
					            window.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
                const app = document.getElementById('pickle-glass');
 | 
					                const app = document.getElementById('pickle-glass');
 | 
				
			||||||
                let animationTimeout = null;
 | 
					        
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (window.require) {
 | 
					                if (window.require) {
 | 
				
			||||||
                    const { ipcRenderer } = window.require('electron');
 | 
					                    const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
                    
 | 
					        
 | 
				
			||||||
 | 
					                    // --- REFACTORED: Event-driven animation handling ---
 | 
				
			||||||
 | 
					                    app.addEventListener('animationend', (event) => {
 | 
				
			||||||
 | 
					                        // 숨김 애니메이션이 끝나면 main 프로세스에 알려 창을 실제로 숨깁니다.
 | 
				
			||||||
 | 
					                        if (event.animationName === 'slideUpToHeader' || event.animationName === 'settingsCollapseToButton') {
 | 
				
			||||||
 | 
					                            console.log(`Animation finished: ${event.animationName}. Notifying main process.`);
 | 
				
			||||||
 | 
					                            ipcRenderer.send('animation-finished');
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					                            // 완료 후 애니메이션 클래스 정리
 | 
				
			||||||
 | 
					                            app.classList.remove('window-sliding-up', 'settings-window-hide');
 | 
				
			||||||
 | 
					                            app.classList.add('window-hidden');
 | 
				
			||||||
 | 
					                        } else if (event.animationName === 'slideDownFromHeader' || event.animationName === 'settingsPopFromButton') {
 | 
				
			||||||
 | 
					                             // 보이기 애니메이션 완료 후 클래스 정리
 | 
				
			||||||
 | 
					                            app.classList.remove('window-sliding-down', 'settings-window-show');
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
                    ipcRenderer.on('window-show-animation', () => {
 | 
					                    ipcRenderer.on('window-show-animation', () => {
 | 
				
			||||||
                        console.log('Starting window show animation');
 | 
					                        console.log('Starting window show animation');
 | 
				
			||||||
                        app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
 | 
					                        app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
 | 
				
			||||||
                        app.classList.add('window-sliding-down');
 | 
					                        app.classList.add('window-sliding-down');
 | 
				
			||||||
                        
 | 
					 | 
				
			||||||
                        if (animationTimeout) clearTimeout(animationTimeout);
 | 
					 | 
				
			||||||
                        animationTimeout = setTimeout(() => {
 | 
					 | 
				
			||||||
                            app.classList.remove('window-sliding-down');
 | 
					 | 
				
			||||||
                        }, 120);
 | 
					 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                    
 | 
					        
 | 
				
			||||||
                    ipcRenderer.on('window-hide-animation', () => {
 | 
					                    ipcRenderer.on('window-hide-animation', () => {
 | 
				
			||||||
                        console.log('Starting window hide animation');
 | 
					                        console.log('Starting window hide animation');
 | 
				
			||||||
                        app.classList.remove('window-sliding-down', 'settings-window-show');
 | 
					                        app.classList.remove('window-sliding-down', 'settings-window-show');
 | 
				
			||||||
                        app.classList.add('window-sliding-up');
 | 
					                        app.classList.add('window-sliding-up');
 | 
				
			||||||
                        
 | 
					 | 
				
			||||||
                        if (animationTimeout) clearTimeout(animationTimeout);
 | 
					 | 
				
			||||||
                        animationTimeout = setTimeout(() => {
 | 
					 | 
				
			||||||
                            app.classList.remove('window-sliding-up');
 | 
					 | 
				
			||||||
                            app.classList.add('window-hidden');
 | 
					 | 
				
			||||||
                        }, 100);
 | 
					 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                    
 | 
					        
 | 
				
			||||||
                    ipcRenderer.on('settings-window-hide-animation', () => {
 | 
					                    ipcRenderer.on('settings-window-hide-animation', () => {
 | 
				
			||||||
                        console.log('Starting settings window hide animation');
 | 
					                        console.log('Starting settings window hide animation');
 | 
				
			||||||
                        app.classList.remove('window-sliding-down', 'settings-window-show');
 | 
					                        app.classList.remove('window-sliding-down', 'settings-window-show');
 | 
				
			||||||
                        app.classList.add('settings-window-hide');
 | 
					                        app.classList.add('settings-window-hide');
 | 
				
			||||||
                        
 | 
					 | 
				
			||||||
                        if (animationTimeout) clearTimeout(animationTimeout);
 | 
					 | 
				
			||||||
                        animationTimeout = setTimeout(() => {
 | 
					 | 
				
			||||||
                            app.classList.remove('settings-window-hide');
 | 
					 | 
				
			||||||
                            app.classList.add('window-hidden');
 | 
					 | 
				
			||||||
                        }, 100);
 | 
					 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					                    // --- UNCHANGED: Existing logic for listen window movement ---
 | 
				
			||||||
                    ipcRenderer.on('listen-window-move-to-center', () => {
 | 
					                    ipcRenderer.on('listen-window-move-to-center', () => {
 | 
				
			||||||
                        console.log('Moving listen window to center');
 | 
					                        console.log('Moving listen window to center');
 | 
				
			||||||
                        app.classList.add('listen-window-moving');
 | 
					                        app.classList.add('listen-window-moving');
 | 
				
			||||||
                        app.classList.remove('listen-window-left');
 | 
					                        app.classList.remove('listen-window-left');
 | 
				
			||||||
                        app.classList.add('listen-window-center');
 | 
					                        app.classList.add('listen-window-center');
 | 
				
			||||||
                        
 | 
					        
 | 
				
			||||||
                        setTimeout(() => {
 | 
					                        setTimeout(() => {
 | 
				
			||||||
                            app.classList.remove('listen-window-moving');
 | 
					                            app.classList.remove('listen-window-moving');
 | 
				
			||||||
                        }, 350);
 | 
					                        }, 350);
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
                    ipcRenderer.on('listen-window-move-to-left', () => {
 | 
					                    ipcRenderer.on('listen-window-move-to-left', () => {
 | 
				
			||||||
                        console.log('Moving listen window to left');
 | 
					                        console.log('Moving listen window to left');
 | 
				
			||||||
                        app.classList.add('listen-window-moving');
 | 
					                        app.classList.add('listen-window-moving');
 | 
				
			||||||
                        app.classList.remove('listen-window-center');
 | 
					                        app.classList.remove('listen-window-center');
 | 
				
			||||||
                        app.classList.add('listen-window-left');
 | 
					                        app.classList.add('listen-window-left');
 | 
				
			||||||
                        
 | 
					        
 | 
				
			||||||
                        setTimeout(() => {
 | 
					                        setTimeout(() => {
 | 
				
			||||||
                            app.classList.remove('listen-window-moving');
 | 
					                            app.classList.remove('listen-window-moving');
 | 
				
			||||||
                        }, 350);
 | 
					                        }, 350);
 | 
				
			||||||
@ -305,6 +304,11 @@
 | 
				
			|||||||
            const params = new URLSearchParams(window.location.search);
 | 
					            const params = new URLSearchParams(window.location.search);
 | 
				
			||||||
            if (params.get('glass') === 'true') {
 | 
					            if (params.get('glass') === 'true') {
 | 
				
			||||||
                document.body.classList.add('has-glass');
 | 
					                document.body.classList.add('has-glass');
 | 
				
			||||||
 | 
					                // --- ADDED: Link to centralized glass-bypass styles ---
 | 
				
			||||||
 | 
					                const link = document.createElement('link');
 | 
				
			||||||
 | 
					                link.rel = 'stylesheet';
 | 
				
			||||||
 | 
					                link.href = '../common/styles/glass-bypass.css';
 | 
				
			||||||
 | 
					                document.head.appendChild(link);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        </script>
 | 
					        </script>
 | 
				
			||||||
    </body>
 | 
					    </body>
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,11 @@
 | 
				
			|||||||
            const params = new URLSearchParams(window.location.search);
 | 
					            const params = new URLSearchParams(window.location.search);
 | 
				
			||||||
            if (params.get('glass') === 'true') {
 | 
					            if (params.get('glass') === 'true') {
 | 
				
			||||||
                document.body.classList.add('has-glass');
 | 
					                document.body.classList.add('has-glass');
 | 
				
			||||||
 | 
					                // --- ADDED: Link to centralized glass-bypass styles ---
 | 
				
			||||||
 | 
					                const link = document.createElement('link');
 | 
				
			||||||
 | 
					                link.rel = 'stylesheet';
 | 
				
			||||||
 | 
					                link.href = '../common/styles/glass-bypass.css';
 | 
				
			||||||
 | 
					                document.head.appendChild(link);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        </script>
 | 
					        </script>
 | 
				
			||||||
    </body>
 | 
					    </body>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,89 +0,0 @@
 | 
				
			|||||||
const crypto = require('crypto');
 | 
					 | 
				
			||||||
const { app } = require('electron');
 | 
					 | 
				
			||||||
const os = require('os');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CryptoService {
 | 
					 | 
				
			||||||
    constructor() {
 | 
					 | 
				
			||||||
        this.algorithm = 'aes-256-gcm';
 | 
					 | 
				
			||||||
        this.saltLength = 32;
 | 
					 | 
				
			||||||
        this.tagLength = 16;
 | 
					 | 
				
			||||||
        this.ivLength = 16;
 | 
					 | 
				
			||||||
        this.iterations = 100000;
 | 
					 | 
				
			||||||
        this.keyLength = 32;
 | 
					 | 
				
			||||||
        this._derivedKey = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _getMachineId() {
 | 
					 | 
				
			||||||
        const machineInfo = `${os.hostname()}-${os.platform()}-${os.arch()}`;
 | 
					 | 
				
			||||||
        const appPath = app.getPath('userData');
 | 
					 | 
				
			||||||
        return crypto.createHash('sha256').update(machineInfo + appPath).digest('hex');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    _deriveKey() {
 | 
					 | 
				
			||||||
        if (this._derivedKey) return this._derivedKey;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        const machineId = this._getMachineId();
 | 
					 | 
				
			||||||
        const salt = crypto.createHash('sha256').update('pickle-glass-salt').digest();
 | 
					 | 
				
			||||||
        this._derivedKey = crypto.pbkdf2Sync(machineId, salt, this.iterations, this.keyLength, 'sha256');
 | 
					 | 
				
			||||||
        return this._derivedKey;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    encrypt(text) {
 | 
					 | 
				
			||||||
        if (!text) return null;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            const iv = crypto.randomBytes(this.ivLength);
 | 
					 | 
				
			||||||
            const salt = crypto.randomBytes(this.saltLength);
 | 
					 | 
				
			||||||
            const key = this._deriveKey();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const cipher = crypto.createCipheriv(this.algorithm, key, iv);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const encrypted = Buffer.concat([
 | 
					 | 
				
			||||||
                cipher.update(text, 'utf8'),
 | 
					 | 
				
			||||||
                cipher.final()
 | 
					 | 
				
			||||||
            ]);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const tag = cipher.getAuthTag();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const combined = Buffer.concat([salt, iv, tag, encrypted]);
 | 
					 | 
				
			||||||
            return combined.toString('base64');
 | 
					 | 
				
			||||||
        } catch (error) {
 | 
					 | 
				
			||||||
            console.error('[CryptoService] Encryption failed:', error.message);
 | 
					 | 
				
			||||||
            throw new Error('Encryption failed');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    decrypt(encryptedData) {
 | 
					 | 
				
			||||||
        if (!encryptedData) return null;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            const combined = Buffer.from(encryptedData, 'base64');
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const salt = combined.slice(0, this.saltLength);
 | 
					 | 
				
			||||||
            const iv = combined.slice(this.saltLength, this.saltLength + this.ivLength);
 | 
					 | 
				
			||||||
            const tag = combined.slice(this.saltLength + this.ivLength, this.saltLength + this.ivLength + this.tagLength);
 | 
					 | 
				
			||||||
            const encrypted = combined.slice(this.saltLength + this.ivLength + this.tagLength);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const key = this._deriveKey();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const decipher = crypto.createDecipheriv(this.algorithm, key, iv);
 | 
					 | 
				
			||||||
            decipher.setAuthTag(tag);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            const decrypted = Buffer.concat([
 | 
					 | 
				
			||||||
                decipher.update(encrypted),
 | 
					 | 
				
			||||||
                decipher.final()
 | 
					 | 
				
			||||||
            ]);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            return decrypted.toString('utf8');
 | 
					 | 
				
			||||||
        } catch (error) {
 | 
					 | 
				
			||||||
            console.error('[CryptoService] Decryption failed:', error.message);
 | 
					 | 
				
			||||||
            throw new Error('Decryption failed');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    clearCache() {
 | 
					 | 
				
			||||||
        this._derivedKey = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = new CryptoService();
 | 
					 | 
				
			||||||
@ -2,7 +2,7 @@ const Store = require('electron-store');
 | 
				
			|||||||
const fetch = require('node-fetch');
 | 
					const fetch = require('node-fetch');
 | 
				
			||||||
const { ipcMain, webContents } = require('electron');
 | 
					const { ipcMain, webContents } = require('electron');
 | 
				
			||||||
const { PROVIDERS } = require('../ai/factory');
 | 
					const { PROVIDERS } = require('../ai/factory');
 | 
				
			||||||
const cryptoService = require('./cryptoService');
 | 
					const encryptionService = require('./encryptionService');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ModelStateService {
 | 
					class ModelStateService {
 | 
				
			||||||
    constructor(authService) {
 | 
					    constructor(authService) {
 | 
				
			||||||
@ -11,8 +11,8 @@ class ModelStateService {
 | 
				
			|||||||
        this.state = {};
 | 
					        this.state = {};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    initialize() {
 | 
					    async initialize() {
 | 
				
			||||||
        this._loadStateForCurrentUser();
 | 
					        await this._loadStateForCurrentUser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setupIpcHandlers();
 | 
					        this.setupIpcHandlers();
 | 
				
			||||||
        console.log('[ModelStateService] Initialized.');
 | 
					        console.log('[ModelStateService] Initialized.');
 | 
				
			||||||
@ -64,8 +64,12 @@ class ModelStateService {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _loadStateForCurrentUser() {
 | 
					    async _loadStateForCurrentUser() {
 | 
				
			||||||
        const userId = this.authService.getCurrentUserId();
 | 
					        const userId = this.authService.getCurrentUserId();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Initialize encryption service for current user
 | 
				
			||||||
 | 
					        await encryptionService.initializeKey(userId);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        const initialApiKeys = Object.keys(PROVIDERS).reduce((acc, key) => {
 | 
					        const initialApiKeys = Object.keys(PROVIDERS).reduce((acc, key) => {
 | 
				
			||||||
            acc[key] = null;
 | 
					            acc[key] = null;
 | 
				
			||||||
            return acc;
 | 
					            return acc;
 | 
				
			||||||
@ -83,7 +87,7 @@ class ModelStateService {
 | 
				
			|||||||
                this.state.apiKeys[p] = null;
 | 
					                this.state.apiKeys[p] = null;
 | 
				
			||||||
            } else if (this.state.apiKeys[p] && p !== 'ollama' && p !== 'whisper') {
 | 
					            } else if (this.state.apiKeys[p] && p !== 'ollama' && p !== 'whisper') {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    this.state.apiKeys[p] = cryptoService.decrypt(this.state.apiKeys[p]);
 | 
					                    this.state.apiKeys[p] = encryptionService.decrypt(this.state.apiKeys[p]);
 | 
				
			||||||
                } catch (error) {
 | 
					                } catch (error) {
 | 
				
			||||||
                    console.error(`[ModelStateService] Failed to decrypt API key for ${p}, resetting`);
 | 
					                    console.error(`[ModelStateService] Failed to decrypt API key for ${p}, resetting`);
 | 
				
			||||||
                    this.state.apiKeys[p] = null;
 | 
					                    this.state.apiKeys[p] = null;
 | 
				
			||||||
@ -107,7 +111,7 @@ class ModelStateService {
 | 
				
			|||||||
        for (const [provider, key] of Object.entries(stateToSave.apiKeys)) {
 | 
					        for (const [provider, key] of Object.entries(stateToSave.apiKeys)) {
 | 
				
			||||||
            if (key && provider !== 'ollama' && provider !== 'whisper') {
 | 
					            if (key && provider !== 'ollama' && provider !== 'whisper') {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    stateToSave.apiKeys[provider] = cryptoService.encrypt(key);
 | 
					                    stateToSave.apiKeys[provider] = encryptionService.encrypt(key);
 | 
				
			||||||
                } catch (error) {
 | 
					                } catch (error) {
 | 
				
			||||||
                    console.error(`[ModelStateService] Failed to encrypt API key for ${provider}`);
 | 
					                    console.error(`[ModelStateService] Failed to encrypt API key for ${provider}`);
 | 
				
			||||||
                    stateToSave.apiKeys[provider] = null;
 | 
					                    stateToSave.apiKeys[provider] = null;
 | 
				
			||||||
@ -213,13 +217,21 @@ class ModelStateService {
 | 
				
			|||||||
        const llmModels = PROVIDERS['openai-glass']?.llmModels;
 | 
					        const llmModels = PROVIDERS['openai-glass']?.llmModels;
 | 
				
			||||||
        const sttModels = PROVIDERS['openai-glass']?.sttModels;
 | 
					        const sttModels = PROVIDERS['openai-glass']?.sttModels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this.state.selectedModels.llm && llmModels?.length > 0) {
 | 
					        // When logging in with Pickle, prioritize Pickle's models over existing selections
 | 
				
			||||||
 | 
					        if (virtualKey && llmModels?.length > 0) {
 | 
				
			||||||
            this.state.selectedModels.llm = llmModels[0].id;
 | 
					            this.state.selectedModels.llm = llmModels[0].id;
 | 
				
			||||||
 | 
					            console.log(`[ModelStateService] Prioritized Pickle LLM model: ${llmModels[0].id}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!this.state.selectedModels.stt && sttModels?.length > 0) {
 | 
					        if (virtualKey && sttModels?.length > 0) {
 | 
				
			||||||
            this.state.selectedModels.stt = sttModels[0].id;
 | 
					            this.state.selectedModels.stt = sttModels[0].id;
 | 
				
			||||||
 | 
					            console.log(`[ModelStateService] Prioritized Pickle STT model: ${sttModels[0].id}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this._autoSelectAvailableModels();
 | 
					        
 | 
				
			||||||
 | 
					        // If logging out (virtualKey is null), run auto-selection to find alternatives
 | 
				
			||||||
 | 
					        if (!virtualKey) {
 | 
				
			||||||
 | 
					            this._autoSelectAvailableModels();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        this._saveState();
 | 
					        this._saveState();
 | 
				
			||||||
        this._logCurrentSelection();
 | 
					        this._logCurrentSelection();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,11 @@ class WhisperService extends LocalAIServiceBase {
 | 
				
			|||||||
            
 | 
					            
 | 
				
			||||||
            this.modelsDir = path.join(whisperDir, 'models');
 | 
					            this.modelsDir = path.join(whisperDir, 'models');
 | 
				
			||||||
            this.tempDir = path.join(whisperDir, 'temp');
 | 
					            this.tempDir = path.join(whisperDir, 'temp');
 | 
				
			||||||
            this.whisperPath = path.join(whisperDir, 'bin', 'whisper');
 | 
					            
 | 
				
			||||||
 | 
					            // Windows에서는 .exe 확장자 필요
 | 
				
			||||||
 | 
					            const platform = this.getPlatform();
 | 
				
			||||||
 | 
					            const whisperExecutable = platform === 'win32' ? 'whisper.exe' : 'whisper';
 | 
				
			||||||
 | 
					            this.whisperPath = path.join(whisperDir, 'bin', whisperExecutable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await this.ensureDirectories();
 | 
					            await this.ensureDirectories();
 | 
				
			||||||
            await this.ensureWhisperBinary();
 | 
					            await this.ensureWhisperBinary();
 | 
				
			||||||
@ -304,17 +308,112 @@ class WhisperService extends LocalAIServiceBase {
 | 
				
			|||||||
        const tempFile = path.join(this.tempDir, 'whisper-binary.zip');
 | 
					        const tempFile = path.join(this.tempDir, 'whisper-binary.zip');
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            console.log('[WhisperService] Step 1: Downloading Whisper binary...');
 | 
				
			||||||
            await this.downloadWithRetry(binaryUrl, tempFile);
 | 
					            await this.downloadWithRetry(binaryUrl, tempFile);
 | 
				
			||||||
            const extractDir = path.dirname(this.whisperPath);
 | 
					            
 | 
				
			||||||
            await spawnAsync('powershell', ['-command', `Expand-Archive -Path '${tempFile}' -DestinationPath '${extractDir}' -Force`]);
 | 
					            console.log('[WhisperService] Step 2: Extracting archive...');
 | 
				
			||||||
            await fsPromises.unlink(tempFile);
 | 
					            const extractDir = path.join(this.tempDir, 'extracted');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 임시 압축 해제 디렉토리 생성
 | 
				
			||||||
 | 
					            await fsPromises.mkdir(extractDir, { recursive: true });
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // PowerShell 명령에서 경로를 올바르게 인용
 | 
				
			||||||
 | 
					            const expandCommand = `Expand-Archive -Path "${tempFile}" -DestinationPath "${extractDir}" -Force`;
 | 
				
			||||||
 | 
					            await spawnAsync('powershell', ['-command', expandCommand]);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            console.log('[WhisperService] Step 3: Finding and moving whisper executable...');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 압축 해제된 디렉토리에서 whisper.exe 파일 찾기
 | 
				
			||||||
 | 
					            const whisperExecutables = await this.findWhisperExecutables(extractDir);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (whisperExecutables.length === 0) {
 | 
				
			||||||
 | 
					                throw new Error('whisper.exe not found in extracted files');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 첫 번째로 찾은 whisper.exe를 목표 위치로 복사
 | 
				
			||||||
 | 
					            const sourceExecutable = whisperExecutables[0];
 | 
				
			||||||
 | 
					            const targetDir = path.dirname(this.whisperPath);
 | 
				
			||||||
 | 
					            await fsPromises.mkdir(targetDir, { recursive: true });
 | 
				
			||||||
 | 
					            await fsPromises.copyFile(sourceExecutable, this.whisperPath);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            console.log('[WhisperService] Step 4: Verifying installation...');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 설치 검증
 | 
				
			||||||
 | 
					            await fsPromises.access(this.whisperPath, fs.constants.F_OK);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // whisper.exe 실행 테스트
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                await spawnAsync(this.whisperPath, ['--help']);
 | 
				
			||||||
 | 
					                console.log('[WhisperService] Whisper executable verified successfully');
 | 
				
			||||||
 | 
					            } catch (testError) {
 | 
				
			||||||
 | 
					                console.warn('[WhisperService] Whisper executable test failed, but file exists:', testError.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            console.log('[WhisperService] Step 5: Cleanup...');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 임시 파일 정리
 | 
				
			||||||
 | 
					            await fsPromises.unlink(tempFile).catch(() => {});
 | 
				
			||||||
 | 
					            await this.removeDirectory(extractDir).catch(() => {});
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            console.log('[WhisperService] Whisper installed successfully on Windows');
 | 
					            console.log('[WhisperService] Whisper installed successfully on Windows');
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            console.error('[WhisperService] Windows installation failed:', error);
 | 
					            console.error('[WhisperService] Windows installation failed:', error);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 실패 시 임시 파일 정리
 | 
				
			||||||
 | 
					            await fsPromises.unlink(tempFile).catch(() => {});
 | 
				
			||||||
 | 
					            await this.removeDirectory(path.join(this.tempDir, 'extracted')).catch(() => {});
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            throw new Error(`Failed to install Whisper on Windows: ${error.message}`);
 | 
					            throw new Error(`Failed to install Whisper on Windows: ${error.message}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 압축 해제된 디렉토리에서 whisper.exe 파일들을 재귀적으로 찾기
 | 
				
			||||||
 | 
					    async findWhisperExecutables(dir) {
 | 
				
			||||||
 | 
					        const executables = [];
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const items = await fsPromises.readdir(dir, { withFileTypes: true });
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            for (const item of items) {
 | 
				
			||||||
 | 
					                const fullPath = path.join(dir, item.name);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (item.isDirectory()) {
 | 
				
			||||||
 | 
					                    const subExecutables = await this.findWhisperExecutables(fullPath);
 | 
				
			||||||
 | 
					                    executables.push(...subExecutables);
 | 
				
			||||||
 | 
					                } else if (item.isFile() && (item.name === 'whisper.exe' || item.name === 'main.exe')) {
 | 
				
			||||||
 | 
					                    // main.exe도 포함 (일부 빌드에서 whisper 실행파일이 main.exe로 명명됨)
 | 
				
			||||||
 | 
					                    executables.push(fullPath);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.warn('[WhisperService] Error reading directory:', dir, error.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return executables;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // 디렉토리 재귀적 삭제
 | 
				
			||||||
 | 
					    async removeDirectory(dir) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const items = await fsPromises.readdir(dir, { withFileTypes: true });
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            for (const item of items) {
 | 
				
			||||||
 | 
					                const fullPath = path.join(dir, item.name);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (item.isDirectory()) {
 | 
				
			||||||
 | 
					                    await this.removeDirectory(fullPath);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    await fsPromises.unlink(fullPath);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            await fsPromises.rmdir(dir);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.warn('[WhisperService] Error removing directory:', dir, error.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async installLinux() {
 | 
					    async installLinux() {
 | 
				
			||||||
        console.log('[WhisperService] Installing Whisper on Linux...');
 | 
					        console.log('[WhisperService] Installing Whisper on Linux...');
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/common/styles/glass-bypass.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/common/styles/glass-bypass.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					  이 파일은 body.has-glass 클래스가 적용되었을 때 모든 애니메이션, 트랜지션,
 | 
				
			||||||
 | 
					  배경, 테두리 등을 비활성화하여 깨끗한 투명 효과(Glass)를 보장합니다.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					body.has-glass * {
 | 
				
			||||||
 | 
					    animation: none !important;
 | 
				
			||||||
 | 
					    transition: none !important;
 | 
				
			||||||
 | 
					    background: transparent !important;
 | 
				
			||||||
 | 
					    border: none !important;
 | 
				
			||||||
 | 
					    box-shadow: none !important;
 | 
				
			||||||
 | 
					    backdrop-filter: none !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -78,13 +78,6 @@ function updateLayout() {
 | 
				
			|||||||
let movementManager = null;
 | 
					let movementManager = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const featureWindows = ['listen','ask','settings'];
 | 
					 | 
				
			||||||
// const featureWindows = ['listen','ask','settings','shortcut-settings'];
 | 
					 | 
				
			||||||
function isAllowed(name) {
 | 
					 | 
				
			||||||
    if (name === 'header') return true;
 | 
					 | 
				
			||||||
    return featureWindows.includes(name) && currentHeaderState === 'main';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function createFeatureWindows(header, namesToCreate) {
 | 
					function createFeatureWindows(header, namesToCreate) {
 | 
				
			||||||
    // if (windowPool.has('listen')) return;
 | 
					    // if (windowPool.has('listen')) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,7 +90,7 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
        hasShadow: false,
 | 
					        hasShadow: false,
 | 
				
			||||||
        skipTaskbar: true,
 | 
					        skipTaskbar: true,
 | 
				
			||||||
        hiddenInMissionControl: true,
 | 
					        hiddenInMissionControl: true,
 | 
				
			||||||
        resizable: false,
 | 
					        resizable: true,
 | 
				
			||||||
        webPreferences: { nodeIntegration: true, contextIsolation: false },
 | 
					        webPreferences: { nodeIntegration: true, contextIsolation: false },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,8 +100,8 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
        switch (name) {
 | 
					        switch (name) {
 | 
				
			||||||
            case 'listen': {
 | 
					            case 'listen': {
 | 
				
			||||||
                const listen = new BrowserWindow({
 | 
					                const listen = new BrowserWindow({
 | 
				
			||||||
                    ...commonChildOptions, width:400,minWidth:400,maxWidth:400,
 | 
					                    ...commonChildOptions, width:400,minWidth:400,maxWidth:900,
 | 
				
			||||||
                    maxHeight:700,
 | 
					                    maxHeight:900,
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
                listen.setContentProtection(isContentProtectionOn);
 | 
					                listen.setContentProtection(isContentProtectionOn);
 | 
				
			||||||
                listen.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
 | 
					                listen.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
 | 
				
			||||||
@ -305,6 +298,7 @@ function createFeatureWindows(header, namesToCreate) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function destroyFeatureWindows() {
 | 
					function destroyFeatureWindows() {
 | 
				
			||||||
 | 
					    const featureWindows = ['listen','ask','settings','shortcut-settings'];
 | 
				
			||||||
    if (settingsHideTimer) {
 | 
					    if (settingsHideTimer) {
 | 
				
			||||||
        clearTimeout(settingsHideTimer);
 | 
					        clearTimeout(settingsHideTimer);
 | 
				
			||||||
        settingsHideTimer = null;
 | 
					        settingsHideTimer = null;
 | 
				
			||||||
@ -337,78 +331,35 @@ function getDisplayById(displayId) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function toggleAllWindowsVisibility(movementManager) {
 | 
					function toggleAllWindowsVisibility() {
 | 
				
			||||||
    const header = windowPool.get('header');
 | 
					    const header = windowPool.get('header');
 | 
				
			||||||
    if (!header) return;
 | 
					    if (!header) return;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
    if (header.isVisible()) {
 | 
					    if (header.isVisible()) {
 | 
				
			||||||
        console.log('[Visibility] Smart hiding - calculating nearest edge');
 | 
					      lastVisibleWindows.clear();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
        const headerBounds = header.getBounds();
 | 
					      windowPool.forEach((win, name) => {
 | 
				
			||||||
        const display = screen.getPrimaryDisplay();
 | 
					        if (win && !win.isDestroyed() && win.isVisible()) {
 | 
				
			||||||
        const { width: screenWidth, height: screenHeight } = display.workAreaSize;
 | 
					          lastVisibleWindows.add(name);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        const centerX = headerBounds.x + headerBounds.width / 2;
 | 
					      });
 | 
				
			||||||
        const centerY = headerBounds.y + headerBounds.height / 2;
 | 
					  
 | 
				
			||||||
 | 
					      lastVisibleWindows.forEach(name => {
 | 
				
			||||||
        const distances = {
 | 
					        if (name === 'header') return;
 | 
				
			||||||
            top: centerY,
 | 
					        const win = windowPool.get(name);
 | 
				
			||||||
            bottom: screenHeight - centerY,
 | 
					        if (win && !win.isDestroyed()) win.hide();
 | 
				
			||||||
            left: centerX,
 | 
					      });
 | 
				
			||||||
            right: screenWidth - centerX,
 | 
					      header.hide();
 | 
				
			||||||
        };
 | 
					  
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
        const nearestEdge = Object.keys(distances).reduce((nearest, edge) => (distances[edge] < distances[nearest] ? edge : nearest));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(`[Visibility] Nearest edge: ${nearestEdge} (distance: ${distances[nearestEdge].toFixed(1)}px)`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        lastVisibleWindows.clear();
 | 
					 | 
				
			||||||
        lastVisibleWindows.add('header');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        windowPool.forEach((win, name) => {
 | 
					 | 
				
			||||||
            if (win.isVisible()) {
 | 
					 | 
				
			||||||
                lastVisibleWindows.add(name);
 | 
					 | 
				
			||||||
                if (name !== 'header') {
 | 
					 | 
				
			||||||
                    // win.webContents.send('window-hide-animation');
 | 
					 | 
				
			||||||
                    // setTimeout(() => {
 | 
					 | 
				
			||||||
                    //     if (!win.isDestroyed()) {
 | 
					 | 
				
			||||||
                    //         win.hide();
 | 
					 | 
				
			||||||
                    //     }
 | 
					 | 
				
			||||||
                    // }, 200);
 | 
					 | 
				
			||||||
                    win.hide();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log('[Visibility] Visible windows before hide:', Array.from(lastVisibleWindows));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        movementManager.hideToEdge(nearestEdge, () => {
 | 
					 | 
				
			||||||
            header.hide();
 | 
					 | 
				
			||||||
            console.log('[Visibility] Smart hide completed');
 | 
					 | 
				
			||||||
        }, { instant: true });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        console.log('[Visibility] Smart showing from hidden position');
 | 
					 | 
				
			||||||
        console.log('[Visibility] Restoring windows:', Array.from(lastVisibleWindows));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        header.show();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        movementManager.showFromEdge(() => {
 | 
					 | 
				
			||||||
            lastVisibleWindows.forEach(name => {
 | 
					 | 
				
			||||||
                if (name === 'header') return;
 | 
					 | 
				
			||||||
                const win = windowPool.get(name);
 | 
					 | 
				
			||||||
                if (win && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
                    win.show();
 | 
					 | 
				
			||||||
                    win.webContents.send('window-show-animation');
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            setImmediate(updateLayout);
 | 
					 | 
				
			||||||
            setTimeout(updateLayout, 120);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.log('[Visibility] Smart show completed');
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					  
 | 
				
			||||||
 | 
					    lastVisibleWindows.forEach(name => {
 | 
				
			||||||
 | 
					      const win = windowPool.get(name);
 | 
				
			||||||
 | 
					      if (win && !win.isDestroyed())
 | 
				
			||||||
 | 
					        win.show();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createWindows() {
 | 
					function createWindows() {
 | 
				
			||||||
@ -464,6 +415,7 @@ function createWindows() {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    windowPool.set('header', header);
 | 
					    windowPool.set('header', header);
 | 
				
			||||||
 | 
					    header.on('moved', updateLayout);
 | 
				
			||||||
    layoutManager = new WindowLayoutManager(windowPool);
 | 
					    layoutManager = new WindowLayoutManager(windowPool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    header.webContents.once('dom-ready', () => {
 | 
					    header.webContents.once('dom-ready', () => {
 | 
				
			||||||
@ -507,25 +459,38 @@ function createWindows() {
 | 
				
			|||||||
        updateLayout();
 | 
					        updateLayout();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility(movementManager));
 | 
					    ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('toggle-feature', async (event, featureName) => {
 | 
					    ipcMain.handle('toggle-feature', async (event, featureName) => {
 | 
				
			||||||
        if (!windowPool.get(featureName) && currentHeaderState === 'main') {
 | 
					        if (!windowPool.get(featureName) && currentHeaderState === 'main') {
 | 
				
			||||||
            createFeatureWindows(windowPool.get('header'));
 | 
					            createFeatureWindows(windowPool.get('header'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const windowToToggle = windowPool.get(featureName);
 | 
					        const header = windowPool.get('header');
 | 
				
			||||||
 | 
					        if (featureName === 'listen') {
 | 
				
			||||||
        if (windowToToggle) {
 | 
					            console.log(`[WindowManager] Toggling feature: ${featureName}`);
 | 
				
			||||||
            if (featureName === 'listen') {
 | 
					            const listenWindow = windowPool.get(featureName);
 | 
				
			||||||
                const listenService = global.listenService;
 | 
					            const listenService = global.listenService;
 | 
				
			||||||
                if (listenService && listenService.isSessionActive()) {
 | 
					            if (listenService && listenService.isSessionActive()) {
 | 
				
			||||||
                    console.log('[WindowManager] Listen session is active, closing it via toggle.');
 | 
					                console.log('[WindowManager] Listen session is active, closing it via toggle.');
 | 
				
			||||||
                    await listenService.closeSession();
 | 
					                await listenService.closeSession();
 | 
				
			||||||
                    return;
 | 
					                listenWindow.webContents.send('session-state-changed', { isActive: false });
 | 
				
			||||||
 | 
					                header.webContents.send('session-state-text', 'Done');
 | 
				
			||||||
 | 
					                // return;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                if (listenWindow.isVisible()) {
 | 
				
			||||||
 | 
					                    listenWindow.webContents.send('window-hide-animation');
 | 
				
			||||||
 | 
					                    listenWindow.webContents.send('session-state-changed', { isActive: false });
 | 
				
			||||||
 | 
					                    header.webContents.send('session-state-text', 'Listen');
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    listenWindow.show();
 | 
				
			||||||
 | 
					                    updateLayout();
 | 
				
			||||||
 | 
					                    listenWindow.webContents.send('window-show-animation');
 | 
				
			||||||
 | 
					                    await listenService.initializeSession();
 | 
				
			||||||
 | 
					                    listenWindow.webContents.send('session-state-changed', { isActive: true });
 | 
				
			||||||
 | 
					                    header.webContents.send('session-state-text', 'Stop');
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            console.log(`[WindowManager] Toggling feature: ${featureName}`);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (featureName === 'ask') {
 | 
					        if (featureName === 'ask') {
 | 
				
			||||||
@ -590,13 +555,6 @@ function createWindows() {
 | 
				
			|||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        console.log('[WindowManager] No response found, closing window');
 | 
					                        console.log('[WindowManager] No response found, closing window');
 | 
				
			||||||
                        askWindow.webContents.send('window-hide-animation');
 | 
					                        askWindow.webContents.send('window-hide-animation');
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        setTimeout(() => {
 | 
					 | 
				
			||||||
                            if (!askWindow.isDestroyed()) {
 | 
					 | 
				
			||||||
                                askWindow.hide();
 | 
					 | 
				
			||||||
                                updateLayout();
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }, 250);
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } catch (error) {
 | 
					                } catch (error) {
 | 
				
			||||||
                    console.error('[WindowManager] Error checking Ask window state:', error);
 | 
					                    console.error('[WindowManager] Error checking Ask window state:', error);
 | 
				
			||||||
@ -610,38 +568,29 @@ function createWindows() {
 | 
				
			|||||||
                askWindow.webContents.send('window-show-animation');
 | 
					                askWindow.webContents.send('window-show-animation');
 | 
				
			||||||
                askWindow.webContents.send('window-did-show');
 | 
					                askWindow.webContents.send('window-did-show');
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        }
 | 
				
			||||||
            const windowToToggle = windowPool.get(featureName);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (windowToToggle) {
 | 
					        if (featureName === 'settings') {
 | 
				
			||||||
                if (windowToToggle.isDestroyed()) {
 | 
					            const settingsWindow = windowPool.get(featureName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (settingsWindow) {
 | 
				
			||||||
 | 
					                if (settingsWindow.isDestroyed()) {
 | 
				
			||||||
                    console.error(`Window ${featureName} is destroyed, cannot toggle`);
 | 
					                    console.error(`Window ${featureName} is destroyed, cannot toggle`);
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (windowToToggle.isVisible()) {
 | 
					                if (settingsWindow.isVisible()) {
 | 
				
			||||||
                    if (featureName === 'settings') {
 | 
					                    if (featureName === 'settings') {
 | 
				
			||||||
                        windowToToggle.webContents.send('settings-window-hide-animation');
 | 
					                        settingsWindow.webContents.send('settings-window-hide-animation');
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        windowToToggle.webContents.send('window-hide-animation');
 | 
					                        settingsWindow.webContents.send('window-hide-animation');
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    setTimeout(() => {
 | 
					 | 
				
			||||||
                        if (!windowToToggle.isDestroyed()) {
 | 
					 | 
				
			||||||
                            windowToToggle.hide();
 | 
					 | 
				
			||||||
                            updateLayout();
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }, 250);
 | 
					 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    try {
 | 
					                    try {
 | 
				
			||||||
                        windowToToggle.show();
 | 
					                        settingsWindow.show();
 | 
				
			||||||
                        updateLayout();
 | 
					                        updateLayout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (featureName === 'listen') {
 | 
					                        settingsWindow.webContents.send('window-show-animation');
 | 
				
			||||||
                            windowToToggle.webContents.send('start-listening-session');
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        windowToToggle.webContents.send('window-show-animation');
 | 
					 | 
				
			||||||
                    } catch (e) {
 | 
					                    } catch (e) {
 | 
				
			||||||
                        console.error('Error showing window:', e);
 | 
					                        console.error('Error showing window:', e);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -767,9 +716,17 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.on('show-window', (event, args) => {
 | 
					    ipcMain.on('animation-finished', (event) => {
 | 
				
			||||||
        const { name, bounds } = typeof args === 'object' && args !== null ? args : { name: args, bounds: null };
 | 
					        const win = BrowserWindow.fromWebContents(event.sender);
 | 
				
			||||||
        const win = windowPool.get(name);
 | 
					        if (win && !win.isDestroyed()) {
 | 
				
			||||||
 | 
					            console.log(`[WindowManager] Hiding window after animation.`);
 | 
				
			||||||
 | 
					            win.hide();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ipcMain.on('show-settings-window', (event, bounds) => {
 | 
				
			||||||
 | 
					        if (!bounds) return;  
 | 
				
			||||||
 | 
					        const win = windowPool.get('settings');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (win && !win.isDestroyed()) {
 | 
					        if (win && !win.isDestroyed()) {
 | 
				
			||||||
            if (settingsHideTimer) {
 | 
					            if (settingsHideTimer) {
 | 
				
			||||||
@ -777,78 +734,60 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
                settingsHideTimer = null;
 | 
					                settingsHideTimer = null;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (name === 'settings') {
 | 
					            // Adjust position based on button bounds
 | 
				
			||||||
                // Adjust position based on button bounds
 | 
					            const header = windowPool.get('header');
 | 
				
			||||||
                const header = windowPool.get('header');
 | 
					            const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
 | 
				
			||||||
                const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
 | 
					            const settingsBounds = win.getBounds();
 | 
				
			||||||
                const settingsBounds = win.getBounds();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const disp = getCurrentDisplay(header);
 | 
					            const disp = getCurrentDisplay(header);
 | 
				
			||||||
                const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
 | 
					            const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2);
 | 
					            let x = Math.round(headerBounds.x + (bounds?.x ?? 0) + (bounds?.width ?? 0) / 2 - settingsBounds.width / 2);
 | 
				
			||||||
                let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
 | 
					            let y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x));
 | 
					            x = Math.max(waX + 10, Math.min(waX + waW - settingsBounds.width - 10, x));
 | 
				
			||||||
                y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y));
 | 
					            y = Math.max(waY + 10, Math.min(waY + waH - settingsBounds.height - 10, y));
 | 
				
			||||||
 | 
					 | 
				
			||||||
                win.setBounds({ x, y });
 | 
					 | 
				
			||||||
                win.__lockedByButton = true;
 | 
					 | 
				
			||||||
                console.log(`[WindowManager] Positioning settings window at (${x}, ${y}) based on button bounds.`);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            win.setBounds({ x, y });
 | 
				
			||||||
 | 
					            win.__lockedByButton = true;
 | 
				
			||||||
 | 
					            console.log(`[WindowManager] Positioning settings window at (${x}, ${y}) based on button bounds.`);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            win.show();
 | 
					            win.show();
 | 
				
			||||||
            win.moveTop();
 | 
					            win.moveTop();
 | 
				
			||||||
 | 
					            win.setAlwaysOnTop(true);
 | 
				
			||||||
            if (name === 'settings') {
 | 
					 | 
				
			||||||
                win.setAlwaysOnTop(true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            // updateLayout();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.on('hide-window', (event, name) => {
 | 
					    ipcMain.on('hide-settings-window', (event) => {
 | 
				
			||||||
        const window = windowPool.get(name);
 | 
					        const window = windowPool.get("settings");
 | 
				
			||||||
        if (window && !window.isDestroyed()) {
 | 
					        if (window && !window.isDestroyed()) {
 | 
				
			||||||
            if (name === 'settings') {
 | 
					            if (settingsHideTimer) {
 | 
				
			||||||
                if (settingsHideTimer) {
 | 
					                clearTimeout(settingsHideTimer);
 | 
				
			||||||
                    clearTimeout(settingsHideTimer);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                settingsHideTimer = setTimeout(() => {
 | 
					 | 
				
			||||||
                    // window.setAlwaysOnTop(false);
 | 
					 | 
				
			||||||
                    // window.hide();
 | 
					 | 
				
			||||||
                    if (window && !window.isDestroyed()) {
 | 
					 | 
				
			||||||
                        window.setAlwaysOnTop(false);
 | 
					 | 
				
			||||||
                        window.hide();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    settingsHideTimer = null;
 | 
					 | 
				
			||||||
                }, 200);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                window.hide();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            settingsHideTimer = setTimeout(() => {
 | 
				
			||||||
 | 
					                if (window && !window.isDestroyed()) {
 | 
				
			||||||
 | 
					                    window.setAlwaysOnTop(false);
 | 
				
			||||||
 | 
					                    window.hide();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                settingsHideTimer = null;
 | 
				
			||||||
 | 
					            }, 200);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            window.__lockedByButton = false;
 | 
					            window.__lockedByButton = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.on('cancel-hide-window', (event, name) => {
 | 
					    ipcMain.on('cancel-hide-settings-window', (event) => {
 | 
				
			||||||
        if (name === 'settings' && settingsHideTimer) {
 | 
					        if (settingsHideTimer) {
 | 
				
			||||||
            clearTimeout(settingsHideTimer);
 | 
					            clearTimeout(settingsHideTimer);
 | 
				
			||||||
            settingsHideTimer = null;
 | 
					            settingsHideTimer = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('hide-all', () => {
 | 
					 | 
				
			||||||
        windowPool.forEach(win => {
 | 
					 | 
				
			||||||
            if (win.isFocused()) return;
 | 
					 | 
				
			||||||
            win.hide();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ipcMain.handle('quit-application', () => {
 | 
					    ipcMain.handle('quit-application', () => {
 | 
				
			||||||
        app.quit();
 | 
					        app.quit();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('is-window-visible', (event, windowName) => {
 | 
					    ipcMain.handle('is-ask-window-visible', (event, windowName) => {
 | 
				
			||||||
        const window = windowPool.get(windowName);
 | 
					        const window = windowPool.get(windowName);
 | 
				
			||||||
        if (window && !window.isDestroyed()) {
 | 
					        if (window && !window.isDestroyed()) {
 | 
				
			||||||
            return window.isVisible();
 | 
					            return window.isVisible();
 | 
				
			||||||
@ -882,15 +821,6 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
            destroyFeatureWindows();
 | 
					            destroyFeatureWindows();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        loadAndRegisterShortcuts(movementManager);
 | 
					        loadAndRegisterShortcuts(movementManager);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const [name, win] of windowPool) {
 | 
					 | 
				
			||||||
            if (!isAllowed(name) && !win.isDestroyed()) {
 | 
					 | 
				
			||||||
                win.hide();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (isAllowed(name) && win.isVisible()) {
 | 
					 | 
				
			||||||
                win.show();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.on('update-keybinds', (event, newKeybinds) => {
 | 
					    ipcMain.on('update-keybinds', (event, newKeybinds) => {
 | 
				
			||||||
@ -963,9 +893,6 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    setupApiKeyIPC();
 | 
					    setupApiKeyIPC();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('resize-window', () => {});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ipcMain.handle('resize-for-view', () => {});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('resize-header-window', (event, { width, height }) => {
 | 
					    ipcMain.handle('resize-header-window', (event, { width, height }) => {
 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					        const header = windowPool.get('header');
 | 
				
			||||||
@ -1018,74 +945,19 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
        return { success: false, error: 'Header window not found' };
 | 
					        return { success: false, error: 'Header window not found' };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.on('header-animation-complete', (event, state) => {
 | 
					    ipcMain.on('header-animation-finished', (event, state) => {
 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					        const header = windowPool.get('header');
 | 
				
			||||||
        if (!header) return;
 | 
					        if (!header || header.isDestroyed()) return;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
        if (state === 'hidden') {
 | 
					        if (state === 'hidden') {
 | 
				
			||||||
            header.hide();
 | 
					            header.hide();
 | 
				
			||||||
 | 
					            console.log('[WindowManager] Header hidden after animation.');
 | 
				
			||||||
        } else if (state === 'visible') {
 | 
					        } else if (state === 'visible') {
 | 
				
			||||||
            lastVisibleWindows.forEach(name => {
 | 
					            console.log('[WindowManager] Header shown after animation.');
 | 
				
			||||||
                if (name === 'header') return;
 | 
					 | 
				
			||||||
                const win = windowPool.get(name);
 | 
					 | 
				
			||||||
                if (win) win.show();
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            setImmediate(updateLayout);
 | 
					 | 
				
			||||||
            setTimeout(updateLayout, 120);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ipcMain.handle('get-header-position', () => {
 | 
					 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					 | 
				
			||||||
        if (header) {
 | 
					 | 
				
			||||||
            const [x, y] = header.getPosition();
 | 
					 | 
				
			||||||
            return { x, y };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return { x: 0, y: 0 };
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ipcMain.handle('move-header', (event, newX, newY) => {
 | 
					 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					 | 
				
			||||||
        if (header) {
 | 
					 | 
				
			||||||
            const currentY = newY !== undefined ? newY : header.getBounds().y;
 | 
					 | 
				
			||||||
            header.setPosition(newX, currentY, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            updateLayout();
 | 
					            updateLayout();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('move-header-to', (event, newX, newY) => {
 | 
					 | 
				
			||||||
        const header = windowPool.get('header');
 | 
					 | 
				
			||||||
        if (header) {
 | 
					 | 
				
			||||||
            const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
 | 
					 | 
				
			||||||
            const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
 | 
					 | 
				
			||||||
            const headerBounds = header.getBounds();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Only clamp if the new position would actually go out of bounds
 | 
					 | 
				
			||||||
            // This prevents progressive restriction of movement
 | 
					 | 
				
			||||||
            let clampedX = newX;
 | 
					 | 
				
			||||||
            let clampedY = newY;
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Check if we need to clamp X position
 | 
					 | 
				
			||||||
            if (newX < workAreaX) {
 | 
					 | 
				
			||||||
                clampedX = workAreaX;
 | 
					 | 
				
			||||||
            } else if (newX + headerBounds.width > workAreaX + width) {
 | 
					 | 
				
			||||||
                clampedX = workAreaX + width - headerBounds.width;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Check if we need to clamp Y position  
 | 
					 | 
				
			||||||
            if (newY < workAreaY) {
 | 
					 | 
				
			||||||
                clampedY = workAreaY;
 | 
					 | 
				
			||||||
            } else if (newY + headerBounds.height > workAreaY + height) {
 | 
					 | 
				
			||||||
                clampedY = workAreaY + height - headerBounds.height;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            header.setPosition(clampedX, clampedY, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            updateLayout();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('move-window-step', (event, direction) => {
 | 
					    ipcMain.handle('move-window-step', (event, direction) => {
 | 
				
			||||||
        if (movementManager) {
 | 
					        if (movementManager) {
 | 
				
			||||||
@ -1099,13 +971,6 @@ function setupIpcHandlers(movementManager) {
 | 
				
			|||||||
            console.log(`[WindowManager] Force closing window: ${windowName}`);
 | 
					            console.log(`[WindowManager] Force closing window: ${windowName}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            window.webContents.send('window-hide-animation');
 | 
					            window.webContents.send('window-hide-animation');
 | 
				
			||||||
 | 
					 | 
				
			||||||
            setTimeout(() => {
 | 
					 | 
				
			||||||
                if (!window.isDestroyed()) {
 | 
					 | 
				
			||||||
                    window.hide();
 | 
					 | 
				
			||||||
                    updateLayout();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }, 250);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1399,7 +1264,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
 | 
				
			|||||||
    if (state === 'apikey') {
 | 
					    if (state === 'apikey') {
 | 
				
			||||||
        if (keybinds.toggleVisibility) {
 | 
					        if (keybinds.toggleVisibility) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                globalShortcut.register(keybinds.toggleVisibility, () => toggleAllWindowsVisibility(movementManager));
 | 
					                globalShortcut.register(keybinds.toggleVisibility, () => toggleAllWindowsVisibility());
 | 
				
			||||||
            } catch (error) {
 | 
					            } catch (error) {
 | 
				
			||||||
                console.error(`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`, error);
 | 
					                console.error(`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`, error);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -1435,7 +1300,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
 | 
				
			|||||||
            let callback;
 | 
					            let callback;
 | 
				
			||||||
            switch(action) {
 | 
					            switch(action) {
 | 
				
			||||||
                case 'toggleVisibility':
 | 
					                case 'toggleVisibility':
 | 
				
			||||||
                    callback = () => toggleAllWindowsVisibility(movementManager);
 | 
					                    callback = () => toggleAllWindowsVisibility();
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case 'nextStep':
 | 
					                case 'nextStep':
 | 
				
			||||||
                    callback = () => {
 | 
					                    callback = () => {
 | 
				
			||||||
@ -1605,9 +1470,6 @@ module.exports = {
 | 
				
			|||||||
    createWindows,
 | 
					    createWindows,
 | 
				
			||||||
    windowPool,
 | 
					    windowPool,
 | 
				
			||||||
    fixedYPosition,
 | 
					    fixedYPosition,
 | 
				
			||||||
    //////// before_modelStateService ////////
 | 
					 | 
				
			||||||
    // setApiKey,
 | 
					 | 
				
			||||||
    //////// before_modelStateService ////////
 | 
					 | 
				
			||||||
    getStoredApiKey,
 | 
					    getStoredApiKey,
 | 
				
			||||||
    getStoredProvider,
 | 
					    getStoredProvider,
 | 
				
			||||||
    getCurrentModelInfo,
 | 
					    getCurrentModelInfo,
 | 
				
			||||||
 | 
				
			|||||||
@ -41,56 +41,6 @@ export class AskView extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideUp {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.7;
 | 
					 | 
				
			||||||
                transform: translateY(-20%) scale(0.98);
 | 
					 | 
				
			||||||
                filter: blur(0.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            70% {
 | 
					 | 
				
			||||||
                opacity: 0.3;
 | 
					 | 
				
			||||||
                transform: translateY(-80%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @keyframes slideDown {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.5;
 | 
					 | 
				
			||||||
                transform: translateY(-50%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            65% {
 | 
					 | 
				
			||||||
                opacity: 0.9;
 | 
					 | 
				
			||||||
                transform: translateY(-5%) scale(0.99);
 | 
					 | 
				
			||||||
                filter: blur(0.2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            85% {
 | 
					 | 
				
			||||||
                opacity: 0.98;
 | 
					 | 
				
			||||||
                transform: translateY(2%) scale(1.005);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        * {
 | 
					        * {
 | 
				
			||||||
            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
					            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
				
			||||||
@ -252,20 +202,6 @@ export class AskView extends LitElement {
 | 
				
			|||||||
            animation: fadeInOut 0.3s ease-in-out;
 | 
					            animation: fadeInOut 0.3s ease-in-out;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes fadeInOut {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            50% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-10px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .header-right {
 | 
					        .header-right {
 | 
				
			||||||
            display: flex;
 | 
					            display: flex;
 | 
				
			||||||
@ -422,19 +358,6 @@ export class AskView extends LitElement {
 | 
				
			|||||||
            animation-delay: 0.4s;
 | 
					            animation-delay: 0.4s;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes pulse {
 | 
					 | 
				
			||||||
            0%,
 | 
					 | 
				
			||||||
            80%,
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 0.3;
 | 
					 | 
				
			||||||
                transform: scale(0.8);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            40% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: scale(1.2);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .response-line {
 | 
					        .response-line {
 | 
				
			||||||
            position: relative;
 | 
					            position: relative;
 | 
				
			||||||
            padding: 2px 0;
 | 
					            padding: 2px 0;
 | 
				
			||||||
@ -492,7 +415,7 @@ export class AskView extends LitElement {
 | 
				
			|||||||
            background: rgba(0, 0, 0, 0.1);
 | 
					            background: rgba(0, 0, 0, 0.1);
 | 
				
			||||||
            border-top: 1px solid rgba(255, 255, 255, 0.1);
 | 
					            border-top: 1px solid rgba(255, 255, 255, 0.1);
 | 
				
			||||||
            flex-shrink: 0;
 | 
					            flex-shrink: 0;
 | 
				
			||||||
            transition: all 0.3s ease-in-out;
 | 
					            transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
 | 
				
			||||||
            transform-origin: bottom;
 | 
					            transform-origin: bottom;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -596,42 +519,6 @@ export class AskView extends LitElement {
 | 
				
			|||||||
            color: rgba(255, 255, 255, 0.5);
 | 
					            color: rgba(255, 255, 255, 0.5);
 | 
				
			||||||
            font-size: 14px;
 | 
					            font-size: 14px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .ask-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-header,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-icon,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .line-copy-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .text-input-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-container pre,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-container p code,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-container pre code {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
            outline: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .ask-container::before {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .close-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .line-copy-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .line-copy-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-line:hover {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-container::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .response-container::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
@ -654,6 +541,7 @@ export class AskView extends LitElement {
 | 
				
			|||||||
        this.handleStreamChunk = this.handleStreamChunk.bind(this);
 | 
					        this.handleStreamChunk = this.handleStreamChunk.bind(this);
 | 
				
			||||||
        this.handleStreamEnd = this.handleStreamEnd.bind(this);
 | 
					        this.handleStreamEnd = this.handleStreamEnd.bind(this);
 | 
				
			||||||
        this.handleSendText = this.handleSendText.bind(this);
 | 
					        this.handleSendText = this.handleSendText.bind(this);
 | 
				
			||||||
 | 
					        this.handleGlobalSendRequest = this.handleGlobalSendRequest.bind(this);
 | 
				
			||||||
        this.handleTextKeydown = this.handleTextKeydown.bind(this);
 | 
					        this.handleTextKeydown = this.handleTextKeydown.bind(this);
 | 
				
			||||||
        this.closeResponsePanel = this.closeResponsePanel.bind(this);
 | 
					        this.closeResponsePanel = this.closeResponsePanel.bind(this);
 | 
				
			||||||
        this.handleCopy = this.handleCopy.bind(this);
 | 
					        this.handleCopy = this.handleCopy.bind(this);
 | 
				
			||||||
@ -669,7 +557,6 @@ export class AskView extends LitElement {
 | 
				
			|||||||
        this.loadLibraries();
 | 
					        this.loadLibraries();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // --- Resize helpers ---
 | 
					        // --- Resize helpers ---
 | 
				
			||||||
        this.adjustHeightThrottle = null;
 | 
					 | 
				
			||||||
        this.isThrottled = false;
 | 
					        this.isThrottled = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1333,6 +1220,14 @@ export class AskView extends LitElement {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    handleGlobalSendRequest() {
 | 
					    handleGlobalSendRequest() {
 | 
				
			||||||
        const textInput = this.shadowRoot?.getElementById('textInput');
 | 
					        const textInput = this.shadowRoot?.getElementById('textInput');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.showTextInput) {
 | 
				
			||||||
 | 
					            this.showTextInput = true;
 | 
				
			||||||
 | 
					            this.requestUpdate();
 | 
				
			||||||
 | 
					            this.focusTextInput();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!textInput) return;
 | 
					        if (!textInput) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        textInput.focus();
 | 
					        textInput.focus();
 | 
				
			||||||
@ -1462,12 +1357,11 @@ export class AskView extends LitElement {
 | 
				
			|||||||
    adjustWindowHeightThrottled() {
 | 
					    adjustWindowHeightThrottled() {
 | 
				
			||||||
        if (this.isThrottled) return;
 | 
					        if (this.isThrottled) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.adjustWindowHeight();
 | 
					 | 
				
			||||||
        this.isThrottled = true;
 | 
					        this.isThrottled = true;
 | 
				
			||||||
 | 
					        requestAnimationFrame(() => {
 | 
				
			||||||
        this.adjustHeightThrottle = setTimeout(() => {
 | 
					            this.adjustWindowHeight();
 | 
				
			||||||
            this.isThrottled = false;
 | 
					            this.isThrottled = false;
 | 
				
			||||||
        }, 16);
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,56 +27,6 @@ export class AssistantView extends LitElement {
 | 
				
			|||||||
            pointer-events: none;
 | 
					            pointer-events: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideUp {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.7;
 | 
					 | 
				
			||||||
                transform: translateY(-20%) scale(0.98);
 | 
					 | 
				
			||||||
                filter: blur(0.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            70% {
 | 
					 | 
				
			||||||
                opacity: 0.3;
 | 
					 | 
				
			||||||
                transform: translateY(-80%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1.5px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        @keyframes slideDown {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
                transform: translateY(-150%) scale(0.85);
 | 
					 | 
				
			||||||
                filter: blur(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            30% {
 | 
					 | 
				
			||||||
                opacity: 0.5;
 | 
					 | 
				
			||||||
                transform: translateY(-50%) scale(0.92);
 | 
					 | 
				
			||||||
                filter: blur(1px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            65% {
 | 
					 | 
				
			||||||
                opacity: 0.9;
 | 
					 | 
				
			||||||
                transform: translateY(-5%) scale(0.99);
 | 
					 | 
				
			||||||
                filter: blur(0.2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            85% {
 | 
					 | 
				
			||||||
                opacity: 0.98;
 | 
					 | 
				
			||||||
                transform: translateY(2%) scale(1.005);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
                transform: translateY(0) scale(1);
 | 
					 | 
				
			||||||
                filter: blur(0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        * {
 | 
					        * {
 | 
				
			||||||
            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
					            font-family: 'Helvetica Neue', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 | 
				
			||||||
@ -245,17 +195,6 @@ export class AssistantView extends LitElement {
 | 
				
			|||||||
            animation: slideIn 0.3s ease forwards;
 | 
					            animation: slideIn 0.3s ease forwards;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes slideIn {
 | 
					 | 
				
			||||||
            from {
 | 
					 | 
				
			||||||
                transform: translateX(10%);
 | 
					 | 
				
			||||||
                opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            to {
 | 
					 | 
				
			||||||
                transform: translateX(0);
 | 
					 | 
				
			||||||
                opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .bar-controls {
 | 
					        .bar-controls {
 | 
				
			||||||
            display: flex;
 | 
					            display: flex;
 | 
				
			||||||
            gap: 4px;
 | 
					            gap: 4px;
 | 
				
			||||||
@ -349,134 +288,6 @@ export class AssistantView extends LitElement {
 | 
				
			|||||||
            font-size: 10px;
 | 
					            font-size: 10px;
 | 
				
			||||||
            color: rgba(255, 255, 255, 0.7);
 | 
					            color: rgba(255, 255, 255, 0.7);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        /* ────────────────[ GLASS BYPASS ]─────────────── */
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .top-bar,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .stt-message,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .outline-item,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .request-item,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .markdown-content,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container pre,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container p code,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container pre code {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
            outline: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container::before,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container::after {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .outline-item:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .request-item.clickable:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .markdown-content:hover {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container::-webkit-scrollbar-thumb,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) * {
 | 
					 | 
				
			||||||
            animation: none !important;
 | 
					 | 
				
			||||||
            transition: none !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .stt-message,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button {
 | 
					 | 
				
			||||||
            border-radius: 0 !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            width: 0 !important;      /* 스크롤바 자체 숨기기 */
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .top-bar,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .stt-message,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .outline-item,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .request-item,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .markdown-content,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container pre,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container p code,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container pre code {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            border: none !important;
 | 
					 | 
				
			||||||
            outline: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container::before,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container::after {
 | 
					 | 
				
			||||||
            display: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .outline-item:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .request-item.clickable:hover,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .markdown-content:hover {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .transcription-container::-webkit-scrollbar-thumb,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .insights-container::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) * {
 | 
					 | 
				
			||||||
            animation: none !important;
 | 
					 | 
				
			||||||
            transition: none !important;
 | 
					 | 
				
			||||||
            transform: none !important;
 | 
					 | 
				
			||||||
            filter: none !important;
 | 
					 | 
				
			||||||
            backdrop-filter: none !important;
 | 
					 | 
				
			||||||
            box-shadow: none !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .assistant-container,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .stt-message,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .toggle-button,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) .copy-button {
 | 
					 | 
				
			||||||
            border-radius: 0 !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar-track,
 | 
					 | 
				
			||||||
        :host-context(body.has-glass) ::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
            background: transparent !important;
 | 
					 | 
				
			||||||
            width: 0 !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static properties = {
 | 
					    static properties = {
 | 
				
			||||||
 | 
				
			|||||||
@ -144,7 +144,6 @@ class ListenService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            console.log('✅ Listen service initialized successfully.');
 | 
					            console.log('✅ Listen service initialized successfully.');
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            this.sendToRenderer('session-state-changed', { isActive: true });
 | 
					 | 
				
			||||||
            this.sendToRenderer('update-status', 'Connected. Ready to listen.');
 | 
					            this.sendToRenderer('update-status', 'Connected. Ready to listen.');
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
@ -155,6 +154,7 @@ class ListenService {
 | 
				
			|||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
            this.isInitializingSession = false;
 | 
					            this.isInitializingSession = false;
 | 
				
			||||||
            this.sendToRenderer('session-initializing', false);
 | 
					            this.sendToRenderer('session-initializing', false);
 | 
				
			||||||
 | 
					            this.sendToRenderer('change-listen-capture-state', { status: "start" });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -179,6 +179,7 @@ class ListenService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    async closeSession() {
 | 
					    async closeSession() {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            this.sendToRenderer('change-listen-capture-state', { status: "stop" });
 | 
				
			||||||
            // Close STT sessions
 | 
					            // Close STT sessions
 | 
				
			||||||
            await this.sttService.closeSessions();
 | 
					            await this.sttService.closeSessions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -192,7 +193,6 @@ class ListenService {
 | 
				
			|||||||
            this.currentSessionId = null;
 | 
					            this.currentSessionId = null;
 | 
				
			||||||
            this.summaryService.resetConversationHistory();
 | 
					            this.summaryService.resetConversationHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.sendToRenderer('session-state-changed', { isActive: false });
 | 
					 | 
				
			||||||
            this.sendToRenderer('session-did-close');
 | 
					            this.sendToRenderer('session-did-close');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            console.log('Listen service session closed.');
 | 
					            console.log('Listen service session closed.');
 | 
				
			||||||
@ -282,9 +282,9 @@ class ListenService {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ipcMain.handle('close-session', async () => {
 | 
					        // ipcMain.handle('close-session', async () => {
 | 
				
			||||||
            return await this.closeSession();
 | 
					        //     return await this.closeSession();
 | 
				
			||||||
        });
 | 
					        // });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ipcMain.handle('update-google-search-setting', async (event, enabled) => {
 | 
					        ipcMain.handle('update-google-search-setting', async (event, enabled) => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,138 +1,30 @@
 | 
				
			|||||||
// renderer.js
 | 
					// renderer.js
 | 
				
			||||||
const { ipcRenderer } = require('electron');
 | 
					const { ipcRenderer } = require('electron');
 | 
				
			||||||
const listenCapture = require('./listenCapture.js');
 | 
					const listenCapture = require('./listenCapture.js');
 | 
				
			||||||
 | 
					const params        = new URLSearchParams(window.location.search);
 | 
				
			||||||
 | 
					const isListenView  = params.get('view') === 'listen';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let realtimeConversationHistory = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function queryLoginState() {
 | 
					 | 
				
			||||||
    const userState = await ipcRenderer.invoke('get-current-user');
 | 
					 | 
				
			||||||
    return userState;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function pickleGlassElement() {
 | 
					 | 
				
			||||||
    return document.getElementById('pickle-glass');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function initializeopenai(profile = 'interview', language = 'en') {
 | 
					 | 
				
			||||||
    // The API key is now handled in the main process from .env file.
 | 
					 | 
				
			||||||
    // We just need to trigger the initialization.
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
        console.log(`Requesting OpenAI initialization with profile: ${profile}, language: ${language}`);
 | 
					 | 
				
			||||||
        const success = await ipcRenderer.invoke('initialize-openai', profile, language);
 | 
					 | 
				
			||||||
        if (success) {
 | 
					 | 
				
			||||||
            // The status will be updated via 'update-status' event from the main process.
 | 
					 | 
				
			||||||
            console.log('OpenAI initialization successful.');
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            console.error('OpenAI initialization failed.');
 | 
					 | 
				
			||||||
            const appElement = pickleGlassElement();
 | 
					 | 
				
			||||||
            if (appElement && typeof appElement.setStatus === 'function') {
 | 
					 | 
				
			||||||
                appElement.setStatus('Initialization Failed');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
        console.error('Error during OpenAI initialization IPC call:', error);
 | 
					 | 
				
			||||||
        const appElement = pickleGlassElement();
 | 
					 | 
				
			||||||
        if (appElement && typeof appElement.setStatus === 'function') {
 | 
					 | 
				
			||||||
            appElement.setStatus('Error');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Listen for status updates
 | 
					 | 
				
			||||||
ipcRenderer.on('update-status', (event, status) => {
 | 
					 | 
				
			||||||
    console.log('Status update:', status);
 | 
					 | 
				
			||||||
    pickleGlass.e().setStatus(status);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Listen for real-time STT updates
 | 
					 | 
				
			||||||
ipcRenderer.on('stt-update', (event, data) => {
 | 
					 | 
				
			||||||
    console.log('Renderer.js stt-update', data);
 | 
					 | 
				
			||||||
    const { speaker, text, isFinal, isPartial, timestamp } = data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (isPartial) {
 | 
					 | 
				
			||||||
        console.log(`🔄 [${speaker} - partial]: ${text}`);
 | 
					 | 
				
			||||||
    } else if (isFinal) {
 | 
					 | 
				
			||||||
        console.log(`✅ [${speaker} - final]: ${text}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const speakerText = speaker.toLowerCase();
 | 
					 | 
				
			||||||
        const conversationText = `${speakerText}: ${text.trim()}`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        realtimeConversationHistory.push(conversationText);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (realtimeConversationHistory.length > 30) {
 | 
					 | 
				
			||||||
            realtimeConversationHistory = realtimeConversationHistory.slice(-30);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(`📝 Updated realtime conversation history: ${realtimeConversationHistory.length} texts`);
 | 
					 | 
				
			||||||
        console.log(`📋 Latest text: ${conversationText}`);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (pickleGlass.e() && typeof pickleGlass.e().updateRealtimeTranscription === 'function') {
 | 
					 | 
				
			||||||
        pickleGlass.e().updateRealtimeTranscription({
 | 
					 | 
				
			||||||
            speaker,
 | 
					 | 
				
			||||||
            text,
 | 
					 | 
				
			||||||
            isFinal,
 | 
					 | 
				
			||||||
            isPartial,
 | 
					 | 
				
			||||||
            timestamp,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ipcRenderer.on('update-structured-data', (_, structuredData) => {
 | 
					 | 
				
			||||||
    console.log('📥 Received structured data update:', structuredData);
 | 
					 | 
				
			||||||
    window.pickleGlass.structuredData = structuredData;
 | 
					 | 
				
			||||||
    window.pickleGlass.setStructuredData(structuredData);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.pickleGlass.structuredData = {
 | 
					 | 
				
			||||||
    summary: [],
 | 
					 | 
				
			||||||
    topic: { header: '', bullets: [] },
 | 
					 | 
				
			||||||
    actions: [],
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
window.pickleGlass.setStructuredData = data => {
 | 
					 | 
				
			||||||
    window.pickleGlass.structuredData = data;
 | 
					 | 
				
			||||||
    pickleGlass.e()?.updateStructuredData?.(data);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function formatRealtimeConversationHistory() {
 | 
					 | 
				
			||||||
    if (realtimeConversationHistory.length === 0) return 'No conversation history available.';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return realtimeConversationHistory.slice(-30).join('\n');
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.pickleGlass = {
 | 
					window.pickleGlass = {
 | 
				
			||||||
    initializeopenai,
 | 
					 | 
				
			||||||
    startCapture: listenCapture.startCapture,
 | 
					    startCapture: listenCapture.startCapture,
 | 
				
			||||||
    stopCapture: listenCapture.stopCapture,
 | 
					    stopCapture: listenCapture.stopCapture,
 | 
				
			||||||
    isLinux: listenCapture.isLinux,
 | 
					    isLinux: listenCapture.isLinux,
 | 
				
			||||||
    isMacOS: listenCapture.isMacOS,
 | 
					    isMacOS: listenCapture.isMacOS,
 | 
				
			||||||
    captureManualScreenshot: listenCapture.captureManualScreenshot,
 | 
					    captureManualScreenshot: listenCapture.captureManualScreenshot,
 | 
				
			||||||
    getCurrentScreenshot: listenCapture.getCurrentScreenshot,
 | 
					    getCurrentScreenshot: listenCapture.getCurrentScreenshot,
 | 
				
			||||||
    e: pickleGlassElement,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// -------------------------------------------------------
 | 
					
 | 
				
			||||||
// 🔔 React to session state changes from the main process
 | 
					ipcRenderer.on('change-listen-capture-state', (_event, { status }) => {
 | 
				
			||||||
// When the session ends (isActive === false), ensure we stop
 | 
					    if (!isListenView) {
 | 
				
			||||||
// all local capture pipelines (mic, screen, etc.).
 | 
					        console.log('[Renderer] Non-listen view: ignoring capture-state change');
 | 
				
			||||||
// -------------------------------------------------------
 | 
					        return;
 | 
				
			||||||
ipcRenderer.on('session-state-changed', (_event, { isActive }) => {
 | 
					    }
 | 
				
			||||||
    if (!isActive) {
 | 
					    if (status === "stop") {
 | 
				
			||||||
        console.log('[Renderer] Session ended – stopping local capture');
 | 
					        console.log('[Renderer] Session ended – stopping local capture');
 | 
				
			||||||
        listenCapture.stopCapture();
 | 
					        listenCapture.stopCapture();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        console.log('[Renderer] New session started – clearing in-memory history and summaries');
 | 
					        console.log('[Renderer] Session initialized – starting local capture');
 | 
				
			||||||
 | 
					        listenCapture.startCapture();
 | 
				
			||||||
        // Reset live conversation & analysis caches
 | 
					 | 
				
			||||||
        realtimeConversationHistory = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const blankData = {
 | 
					 | 
				
			||||||
            summary: [],
 | 
					 | 
				
			||||||
            topic: { header: '', bullets: [] },
 | 
					 | 
				
			||||||
            actions: [],
 | 
					 | 
				
			||||||
            followUps: [],
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        window.pickleGlass.setStructuredData(blankData);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -264,7 +264,7 @@ export class SummaryView extends LitElement {
 | 
				
			|||||||
        super.connectedCallback();
 | 
					        super.connectedCallback();
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            ipcRenderer.on('update-structured-data', (event, data) => {
 | 
					            ipcRenderer.on('summary-update', (event, data) => {
 | 
				
			||||||
                this.structuredData = data;
 | 
					                this.structuredData = data;
 | 
				
			||||||
                this.requestUpdate();
 | 
					                this.requestUpdate();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -275,7 +275,7 @@ export class SummaryView extends LitElement {
 | 
				
			|||||||
        super.disconnectedCallback();
 | 
					        super.disconnectedCallback();
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            ipcRenderer.removeAllListeners('update-structured-data');
 | 
					            ipcRenderer.removeAllListeners('summary-update');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -412,7 +412,7 @@ export class SummaryView extends LitElement {
 | 
				
			|||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                const isAskViewVisible = await ipcRenderer.invoke('is-window-visible', 'ask');
 | 
					                const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!isAskViewVisible) {
 | 
					                if (!isAskViewVisible) {
 | 
				
			||||||
                    await ipcRenderer.invoke('toggle-feature', 'ask');
 | 
					                    await ipcRenderer.invoke('toggle-feature', 'ask');
 | 
				
			||||||
 | 
				
			|||||||
@ -27,23 +27,6 @@ class SummaryService {
 | 
				
			|||||||
        this.currentSessionId = sessionId;
 | 
					        this.currentSessionId = sessionId;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // async getApiKey() {
 | 
					 | 
				
			||||||
    //     const storedKey = await getStoredApiKey();
 | 
					 | 
				
			||||||
    //     if (storedKey) {
 | 
					 | 
				
			||||||
    //         console.log('[SummaryService] Using stored API key');
 | 
					 | 
				
			||||||
    //         return storedKey;
 | 
					 | 
				
			||||||
    //     }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //     const envKey = process.env.OPENAI_API_KEY;
 | 
					 | 
				
			||||||
    //     if (envKey) {
 | 
					 | 
				
			||||||
    //         console.log('[SummaryService] Using environment API key');
 | 
					 | 
				
			||||||
    //         return envKey;
 | 
					 | 
				
			||||||
    //     }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //     console.error('[SummaryService] No API key found in storage or environment');
 | 
					 | 
				
			||||||
    //     return null;
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sendToRenderer(channel, data) {
 | 
					    sendToRenderer(channel, data) {
 | 
				
			||||||
        BrowserWindow.getAllWindows().forEach(win => {
 | 
					        BrowserWindow.getAllWindows().forEach(win => {
 | 
				
			||||||
            if (!win.isDestroyed()) {
 | 
					            if (!win.isDestroyed()) {
 | 
				
			||||||
@ -327,7 +310,7 @@ Keep all points concise and build upon previous analysis if provided.`,
 | 
				
			|||||||
                .then(data => {
 | 
					                .then(data => {
 | 
				
			||||||
                    if (data) {
 | 
					                    if (data) {
 | 
				
			||||||
                        console.log('📤 Sending structured data to renderer');
 | 
					                        console.log('📤 Sending structured data to renderer');
 | 
				
			||||||
                        this.sendToRenderer('update-structured-data', data);
 | 
					                        this.sendToRenderer('summary-update', data);
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                        // Notify callback
 | 
					                        // Notify callback
 | 
				
			||||||
                        if (this.onAnalysisComplete) {
 | 
					                        if (this.onAnalysisComplete) {
 | 
				
			||||||
 | 
				
			|||||||
@ -367,11 +367,6 @@ export class SettingsView extends LitElement {
 | 
				
			|||||||
            margin-right: 6px;
 | 
					            margin-right: 6px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @keyframes spin {
 | 
					 | 
				
			||||||
            0% { transform: rotate(0deg); }
 | 
					 | 
				
			||||||
            100% { transform: rotate(360deg); }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .hidden {
 | 
					        .hidden {
 | 
				
			||||||
            display: none;
 | 
					            display: none;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -1030,14 +1025,14 @@ export class SettingsView extends LitElement {
 | 
				
			|||||||
    handleMouseEnter = () => {
 | 
					    handleMouseEnter = () => {
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            ipcRenderer.send('cancel-hide-window', 'settings');
 | 
					            ipcRenderer.send('cancel-hide-settings-window');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handleMouseLeave = () => {
 | 
					    handleMouseLeave = () => {
 | 
				
			||||||
        if (window.require) {
 | 
					        if (window.require) {
 | 
				
			||||||
            const { ipcRenderer } = window.require('electron');
 | 
					            const { ipcRenderer } = window.require('electron');
 | 
				
			||||||
            ipcRenderer.send('hide-window', 'settings');
 | 
					            ipcRenderer.send('hide-settings-window');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/index.js
									
									
									
									
									
								
							@ -199,7 +199,7 @@ app.whenReady().then(async () => {
 | 
				
			|||||||
        await authService.initialize();
 | 
					        await authService.initialize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //////// after_modelStateService ////////
 | 
					        //////// after_modelStateService ////////
 | 
				
			||||||
        modelStateService.initialize();
 | 
					        await modelStateService.initialize();
 | 
				
			||||||
        //////// after_modelStateService ////////
 | 
					        //////// after_modelStateService ////////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        listenService.setupIpcHandlers();
 | 
					        listenService.setupIpcHandlers();
 | 
				
			||||||
@ -396,21 +396,6 @@ function setupGeneralIpcHandlers() {
 | 
				
			|||||||
    const userRepository = require('./common/repositories/user');
 | 
					    const userRepository = require('./common/repositories/user');
 | 
				
			||||||
    const presetRepository = require('./common/repositories/preset');
 | 
					    const presetRepository = require('./common/repositories/preset');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ipcMain.handle('save-api-key', (event, apiKey) => {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            // The adapter injects the UID and handles local/firebase logic.
 | 
					 | 
				
			||||||
            // Assuming a default provider if not specified.
 | 
					 | 
				
			||||||
            userRepository.saveApiKey(apiKey, 'openai');
 | 
					 | 
				
			||||||
            BrowserWindow.getAllWindows().forEach(win => {
 | 
					 | 
				
			||||||
                win.webContents.send('api-key-updated');
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return { success: true };
 | 
					 | 
				
			||||||
        } catch (error) {
 | 
					 | 
				
			||||||
            console.error('IPC: Failed to save API key:', error);
 | 
					 | 
				
			||||||
            return { success: false, error: error.message };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ipcMain.handle('get-user-presets', () => {
 | 
					    ipcMain.handle('get-user-presets', () => {
 | 
				
			||||||
        // The adapter injects the UID.
 | 
					        // The adapter injects the UID.
 | 
				
			||||||
        return presetRepository.getPresets();
 | 
					        return presetRepository.getPresets();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user