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