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
|
||||
self-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
|
||||
permissions:
|
||||
issues: write
|
||||
|
@ -46,8 +46,11 @@ win:
|
||||
- target: portable
|
||||
arch: x64
|
||||
requestedExecutionLevel: asInvoker
|
||||
# Disable code signing to avoid symbolic link issues on Windows
|
||||
signAndEditExecutable: false
|
||||
signAndEditExecutable: true
|
||||
cscLink: build\certs\glass-dev.pfx
|
||||
cscKeyPassword: "${env.CSC_KEY_PASSWORD}"
|
||||
signtoolOptions:
|
||||
certificateSubjectName: "Glass Dev Code Signing"
|
||||
|
||||
# NSIS installer configuration for Windows
|
||||
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",
|
||||
"productName": "Glass",
|
||||
|
||||
"version": "0.2.4",
|
||||
|
||||
"description": "Cl*ely for Free",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"setup": "npm install && cd pickleglass_web && npm install && npm run build && cd .. && npm start",
|
||||
"start": "npm run build:renderer && electron-forge start",
|
||||
"package": "npm run build:renderer && electron-forge package",
|
||||
"start": "npm run build:renderer && electron .",
|
||||
"package": "npm run build:all && electron-builder --dir",
|
||||
"make": "npm run build:renderer && electron-forge make",
|
||||
"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",
|
||||
@ -58,14 +56,6 @@
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"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/notarize": "^2.5.0",
|
||||
"electron": "^30.5.1",
|
||||
@ -77,4 +67,4 @@
|
||||
"optionalDependencies": {
|
||||
"electron-liquid-glass": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,10 @@ export default function LoginPage() {
|
||||
|
||||
window.location.href = deepLinkUrl
|
||||
|
||||
setTimeout(() => {
|
||||
alert('Login completed. Please return to Pickle Glass app.')
|
||||
}, 1000)
|
||||
// Maybe we don't need this
|
||||
// setTimeout(() => {
|
||||
// alert('Login completed. Please return to Pickle Glass app.')
|
||||
// }, 1000)
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Deep link processing failed:', error)
|
||||
|
@ -25,15 +25,14 @@ export class ApiKeyHeader extends LitElement {
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
transition: opacity 0.25s ease-out;
|
||||
display: block;
|
||||
transition: opacity 0.3s ease-in, transform 0.3s ease-in;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
:host(.sliding-out) {
|
||||
animation: slideOutUp 0.3s ease-in forwards;
|
||||
will-change: opacity, transform;
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
:host(.hidden) {
|
||||
@ -41,17 +40,6 @@ export class ApiKeyHeader extends LitElement {
|
||||
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;
|
||||
cursor: default;
|
||||
@ -60,6 +48,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
.container {
|
||||
-webkit-app-region: drag;
|
||||
width: 350px;
|
||||
min-height: 260px;
|
||||
padding: 18px 20px;
|
||||
@ -89,6 +78,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
.close-button {
|
||||
-webkit-app-region: no-drag;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@ -168,6 +158,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
|
||||
.api-input {
|
||||
-webkit-app-region: no-drag;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
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-label { color: rgba(255, 255, 255, 0.7); font-size: 11px; font-weight: 500; margin-bottom: 6px; }
|
||||
.api-input, .provider-select {
|
||||
-webkit-app-region: no-drag;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
text-align: center;
|
||||
@ -221,6 +213,7 @@ export class ApiKeyHeader extends LitElement {
|
||||
|
||||
|
||||
.action-button {
|
||||
-webkit-app-region: no-drag;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
@ -266,37 +259,10 @@ export class ApiKeyHeader extends LitElement {
|
||||
font-weight: 500; /* Medium */
|
||||
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() {
|
||||
super()
|
||||
this.dragState = null
|
||||
this.wasJustDragged = false
|
||||
this.isLoading = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
@ -358,8 +324,6 @@ export class ApiKeyHeader extends LitElement {
|
||||
this.loadProviderConfig();
|
||||
//////// after_modelStateService ////////
|
||||
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this)
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this)
|
||||
this.handleKeyPress = this.handleKeyPress.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleInput = this.handleInput.bind(this)
|
||||
@ -1533,7 +1497,6 @@ export class ApiKeyHeader extends LitElement {
|
||||
|
||||
handleUsePicklesKey(e) {
|
||||
e.preventDefault()
|
||||
if (this.wasJustDragged) return
|
||||
|
||||
console.log("Requesting Firebase authentication from main process...")
|
||||
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 {
|
||||
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 },
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
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;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
:host(.hiding) {
|
||||
@ -33,65 +32,6 @@ export class MainHeader extends LitElement {
|
||||
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;
|
||||
@ -100,6 +40,7 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
.header {
|
||||
-webkit-app-region: drag;
|
||||
width: max-content;
|
||||
height: 47px;
|
||||
padding: 2px 10px 2px 13px;
|
||||
@ -141,6 +82,7 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
.listen-button {
|
||||
-webkit-app-region: no-drag;
|
||||
height: 26px;
|
||||
padding: 0 13px;
|
||||
background: transparent;
|
||||
@ -155,6 +97,11 @@ export class MainHeader extends LitElement {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.listen-button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.listen-button.active::before {
|
||||
background: rgba(215, 0, 0, 0.5);
|
||||
}
|
||||
@ -163,6 +110,24 @@ export class MainHeader extends LitElement {
|
||||
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 {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
@ -192,7 +157,40 @@ export class MainHeader extends LitElement {
|
||||
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 {
|
||||
-webkit-app-region: no-drag;
|
||||
height: 26px;
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-start;
|
||||
@ -264,6 +262,7 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
-webkit-app-region: no-drag;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
@ -291,125 +290,22 @@ export class MainHeader extends LitElement {
|
||||
width: 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() {
|
||||
super();
|
||||
this.shortcuts = {};
|
||||
this.dragState = null;
|
||||
this.wasJustDragged = false;
|
||||
this.isVisible = true;
|
||||
this.isAnimating = false;
|
||||
this.hasSlidIn = false;
|
||||
this.settingsHideTimer = null;
|
||||
this.isSessionActive = false;
|
||||
// this.isSessionActive = false;
|
||||
this.isTogglingSession = false;
|
||||
this.actionText = 'Listen';
|
||||
this.animationEndTimer = null;
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleMouseUp = this.handleMouseUp.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() {
|
||||
if (this.isAnimating) {
|
||||
console.log('[MainHeader] Animation already in progress, ignoring toggle');
|
||||
@ -431,58 +327,29 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.classList.remove('showing', 'hidden');
|
||||
this.classList.remove('showing');
|
||||
this.classList.add('hiding');
|
||||
this.isVisible = false;
|
||||
|
||||
this.animationEndTimer = setTimeout(() => {
|
||||
if (this.classList.contains('hiding')) {
|
||||
this.handleAnimationEnd({ target: this });
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
|
||||
|
||||
show() {
|
||||
this.classList.remove('hiding', 'hidden');
|
||||
this.classList.add('showing');
|
||||
this.isVisible = true;
|
||||
|
||||
this.animationEndTimer = setTimeout(() => {
|
||||
if (this.classList.contains('showing')) {
|
||||
this.handleAnimationEnd({ target: this });
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
|
||||
handleAnimationEnd(e) {
|
||||
if (e.target !== this) return;
|
||||
|
||||
if (this.animationEndTimer) {
|
||||
clearTimeout(this.animationEndTimer);
|
||||
this.animationEndTimer = null;
|
||||
}
|
||||
|
||||
|
||||
this.isAnimating = false;
|
||||
|
||||
|
||||
if (this.classList.contains('hiding')) {
|
||||
this.classList.remove('hiding');
|
||||
this.classList.add('hidden');
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('header-animation-complete', 'hidden');
|
||||
window.require('electron').ipcRenderer.send('header-animation-finished', 'hidden');
|
||||
}
|
||||
} else if (this.classList.contains('showing')) {
|
||||
this.classList.remove('showing');
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('header-animation-complete', 'visible');
|
||||
window.require('electron').ipcRenderer.send('header-animation-finished', '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) {
|
||||
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) => {
|
||||
console.log('[MainHeader] Received updated shortcuts:', keybinds);
|
||||
this.shortcuts = keybinds;
|
||||
@ -520,9 +396,12 @@ export class MainHeader extends LitElement {
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
if (this._sessionStateListener) {
|
||||
ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
|
||||
if (this._sessionStateTextListener) {
|
||||
ipcRenderer.removeListener('session-state-text', this._sessionStateTextListener);
|
||||
}
|
||||
// if (this._sessionStateListener) {
|
||||
// ipcRenderer.removeListener('session-state-changed', this._sessionStateListener);
|
||||
// }
|
||||
if (this._shortcutListener) {
|
||||
ipcRenderer.removeListener('shortcuts-updated', this._shortcutListener);
|
||||
}
|
||||
@ -530,51 +409,56 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
invoke(channel, ...args) {
|
||||
if (this.wasJustDragged) {
|
||||
return;
|
||||
}
|
||||
if (window.require) {
|
||||
window.require('electron').ipcRenderer.invoke(channel, ...args);
|
||||
}
|
||||
// return Promise.resolve();
|
||||
}
|
||||
|
||||
showWindow(name, element) {
|
||||
if (this.wasJustDragged) return;
|
||||
showSettingsWindow(element) {
|
||||
if (window.require) {
|
||||
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) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
ipcRenderer.send('show-window', {
|
||||
name: 'settings',
|
||||
bounds: {
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
if (element) {
|
||||
const { left, top, width, height } = element.getBoundingClientRect();
|
||||
ipcRenderer.send('show-settings-window', {
|
||||
x: left,
|
||||
y: top,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
} else {
|
||||
ipcRenderer.send('show-window', name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideWindow(name) {
|
||||
if (this.wasJustDragged) return;
|
||||
hideSettingsWindow() {
|
||||
if (window.require) {
|
||||
console.log(`[MainHeader] hideWindow('${name}') called at ${Date.now()}`);
|
||||
window.require('electron').ipcRenderer.send('hide-window', name);
|
||||
console.log(`[MainHeader] hideSettingsWindow called at ${Date.now()}`);
|
||||
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) {
|
||||
if (!accelerator) return html``;
|
||||
|
||||
@ -599,31 +483,45 @@ export class MainHeader extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="header" @mousedown=${this.handleMouseDown}>
|
||||
<button
|
||||
class="listen-button ${this.isSessionActive ? 'active' : ''}"
|
||||
@click=${() => this.invoke(this.isSessionActive ? 'close-session' : 'toggle-feature', 'listen')}
|
||||
>
|
||||
<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>
|
||||
const buttonClasses = {
|
||||
active: this.actionText === 'Stop',
|
||||
done: this.actionText === 'Done',
|
||||
};
|
||||
const showStopIcon = this.actionText === 'Stop' || this.actionText === 'Done';
|
||||
|
||||
`
|
||||
: 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>
|
||||
return html`
|
||||
<div class="header">
|
||||
<button
|
||||
class="listen-button ${Object.keys(buttonClasses).filter(k => buttonClasses[k]).join(' ')}"
|
||||
@click=${this._handleListenClick}
|
||||
?disabled=${this.isTogglingSession}
|
||||
>
|
||||
${this.isTogglingSession
|
||||
? 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>
|
||||
|
||||
<div class="header-actions ask-action" @click=${() => this.invoke('toggle-feature', 'ask')}>
|
||||
@ -646,8 +544,8 @@ export class MainHeader extends LitElement {
|
||||
|
||||
<button
|
||||
class="settings-button"
|
||||
@mouseenter=${(e) => this.showWindow('settings', e.currentTarget)}
|
||||
@mouseleave=${() => this.hideWindow('settings')}
|
||||
@mouseenter=${(e) => this.showSettingsWindow(e.currentTarget)}
|
||||
@mouseleave=${() => this.hideSettingsWindow()}
|
||||
>
|
||||
<div class="settings-icon">
|
||||
<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`
|
||||
:host {
|
||||
display: block;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
transition: opacity 0.25s ease-out;
|
||||
transition: opacity 0.3s ease-in, transform 0.3s ease-in;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
:host(.sliding-out) {
|
||||
animation: slideOutUp 0.3s ease-in forwards;
|
||||
will-change: opacity, transform;
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
:host(.hidden) {
|
||||
@ -19,17 +18,6 @@ export class PermissionHeader extends LitElement {
|
||||
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;
|
||||
cursor: default;
|
||||
@ -38,6 +26,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
.container {
|
||||
-webkit-app-region: drag;
|
||||
width: 285px;
|
||||
height: 220px;
|
||||
padding: 18px 20px;
|
||||
@ -67,6 +56,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
.close-button {
|
||||
-webkit-app-region: no-drag;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
@ -157,6 +147,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
.action-button {
|
||||
-webkit-app-region: no-drag;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
@ -198,6 +189,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
.continue-button {
|
||||
-webkit-app-region: no-drag;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
background: rgba(34, 197, 94, 0.8);
|
||||
@ -237,30 +229,6 @@ export class PermissionHeader extends LitElement {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
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 = {
|
||||
@ -276,9 +244,6 @@ export class PermissionHeader extends LitElement {
|
||||
this.screenGranted = 'unknown';
|
||||
this.isChecking = false;
|
||||
this.continueCallback = null;
|
||||
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!window.require || this.isChecking) return;
|
||||
|
||||
@ -390,7 +300,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
async handleMicrophoneClick() {
|
||||
if (!window.require || this.microphoneGranted === 'granted' || this.wasJustDragged) return;
|
||||
if (!window.require || this.microphoneGranted === 'granted') return;
|
||||
|
||||
console.log('[PermissionHeader] Requesting microphone permission...');
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
@ -423,7 +333,7 @@ export class PermissionHeader extends LitElement {
|
||||
}
|
||||
|
||||
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...');
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
@ -453,8 +363,7 @@ export class PermissionHeader extends LitElement {
|
||||
async handleContinue() {
|
||||
if (this.continueCallback &&
|
||||
this.microphoneGranted === 'granted' &&
|
||||
this.screenGranted === 'granted' &&
|
||||
!this.wasJustDragged) {
|
||||
this.screenGranted === 'granted') {
|
||||
// Mark permissions as completed
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
@ -481,7 +390,7 @@ export class PermissionHeader extends LitElement {
|
||||
const allGranted = this.microphoneGranted === 'granted' && this.screenGranted === 'granted';
|
||||
|
||||
return html`
|
||||
<div class="container" @mousedown=${this.handleMouseDown}>
|
||||
<div class="container">
|
||||
<button class="close-button" @click=${this.handleClose} title="Close application">
|
||||
<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" />
|
||||
|
@ -68,12 +68,7 @@ export class PickleGlassApp extends LitElement {
|
||||
this.selectedScreenshotInterval = localStorage.getItem('selectedScreenshotInterval') || '5';
|
||||
this.selectedImageQuality = localStorage.getItem('selectedImageQuality') || 'medium';
|
||||
this._isClickThrough = false;
|
||||
this.outlines = [];
|
||||
this.analysisRequests = [];
|
||||
|
||||
window.pickleGlass.setStructuredData = data => {
|
||||
this.updateStructuredData(data);
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@ -82,14 +77,13 @@ export class PickleGlassApp extends LitElement {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
ipcRenderer.on('update-status', (_, status) => this.setStatus(status));
|
||||
ipcRenderer.on('click-through-toggled', (_, isEnabled) => {
|
||||
this._isClickThrough = isEnabled;
|
||||
});
|
||||
ipcRenderer.on('start-listening-session', () => {
|
||||
console.log('Received start-listening-session command, calling handleListenClick.');
|
||||
this.handleListenClick();
|
||||
});
|
||||
// ipcRenderer.on('start-listening-session', () => {
|
||||
// console.log('Received start-listening-session command, calling handleListenClick.');
|
||||
// this.handleListenClick();
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,16 +91,15 @@ export class PickleGlassApp extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('update-status');
|
||||
ipcRenderer.removeAllListeners('click-through-toggled');
|
||||
ipcRenderer.removeAllListeners('start-listening-session');
|
||||
// ipcRenderer.removeAllListeners('start-listening-session');
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties.has('isMainViewVisible') || changedProperties.has('currentView')) {
|
||||
this.requestWindowResize();
|
||||
}
|
||||
// if (changedProperties.has('isMainViewVisible') || changedProperties.has('currentView')) {
|
||||
// this.requestWindowResize();
|
||||
// }
|
||||
|
||||
if (changedProperties.has('currentView')) {
|
||||
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) {
|
||||
this.statusText = text;
|
||||
}
|
||||
// async handleListenClick() {
|
||||
// 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.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;
|
||||
}
|
||||
}
|
||||
// if (window.pickleGlass) {
|
||||
// // await window.pickleGlass.initializeopenai(this.selectedProfile, this.selectedLanguage);
|
||||
// window.pickleGlass.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality);
|
||||
// }
|
||||
|
||||
if (window.pickleGlass) {
|
||||
await window.pickleGlass.initializeopenai(this.selectedProfile, this.selectedLanguage);
|
||||
window.pickleGlass.startCapture(this.selectedScreenshotInterval, this.selectedImageQuality);
|
||||
}
|
||||
// // 🔄 Clear previous summary/analysis when a new listening session begins
|
||||
// this.structuredData = {
|
||||
// summary: [],
|
||||
// topic: { header: '', bullets: [] },
|
||||
// actions: [],
|
||||
// followUps: [],
|
||||
// };
|
||||
|
||||
// 🔄 Clear previous summary/analysis when a new listening session begins
|
||||
this.structuredData = {
|
||||
summary: [],
|
||||
topic: { header: '', bullets: [] },
|
||||
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;
|
||||
}
|
||||
// this.currentResponseIndex = -1;
|
||||
// this.startTime = Date.now();
|
||||
// this.currentView = 'listen';
|
||||
// this.isMainViewVisible = true;
|
||||
// }
|
||||
|
||||
async handleClose() {
|
||||
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() {
|
||||
switch (this.currentView) {
|
||||
@ -257,7 +176,6 @@ export class PickleGlassApp extends LitElement {
|
||||
.currentResponseIndex=${this.currentResponseIndex}
|
||||
.selectedProfile=${this.selectedProfile}
|
||||
.structuredData=${this.structuredData}
|
||||
.onSendText=${message => this.handleSendText(message)}
|
||||
@response-index-changed=${e => (this.currentResponseIndex = e.detail.index)}
|
||||
></assistant-view>`;
|
||||
case 'ask':
|
||||
|
@ -237,63 +237,62 @@
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const app = document.getElementById('pickle-glass');
|
||||
let animationTimeout = null;
|
||||
|
||||
|
||||
if (window.require) {
|
||||
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', () => {
|
||||
console.log('Starting window show animation');
|
||||
app.classList.remove('window-hidden', 'window-sliding-up', 'settings-window-hide');
|
||||
app.classList.add('window-sliding-down');
|
||||
|
||||
if (animationTimeout) clearTimeout(animationTimeout);
|
||||
animationTimeout = setTimeout(() => {
|
||||
app.classList.remove('window-sliding-down');
|
||||
}, 120);
|
||||
});
|
||||
|
||||
|
||||
ipcRenderer.on('window-hide-animation', () => {
|
||||
console.log('Starting window hide animation');
|
||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||
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', () => {
|
||||
console.log('Starting settings window hide animation');
|
||||
app.classList.remove('window-sliding-down', 'settings-window-show');
|
||||
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', () => {
|
||||
console.log('Moving listen window to center');
|
||||
app.classList.add('listen-window-moving');
|
||||
app.classList.remove('listen-window-left');
|
||||
app.classList.add('listen-window-center');
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
app.classList.remove('listen-window-moving');
|
||||
}, 350);
|
||||
});
|
||||
|
||||
|
||||
ipcRenderer.on('listen-window-move-to-left', () => {
|
||||
console.log('Moving listen window to left');
|
||||
app.classList.add('listen-window-moving');
|
||||
app.classList.remove('listen-window-center');
|
||||
app.classList.add('listen-window-left');
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
app.classList.remove('listen-window-moving');
|
||||
}, 350);
|
||||
@ -305,6 +304,11 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('glass') === 'true') {
|
||||
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>
|
||||
</body>
|
||||
|
@ -22,6 +22,11 @@
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('glass') === 'true') {
|
||||
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>
|
||||
</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 { ipcMain, webContents } = require('electron');
|
||||
const { PROVIDERS } = require('../ai/factory');
|
||||
const cryptoService = require('./cryptoService');
|
||||
const encryptionService = require('./encryptionService');
|
||||
|
||||
class ModelStateService {
|
||||
constructor(authService) {
|
||||
@ -11,8 +11,8 @@ class ModelStateService {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this._loadStateForCurrentUser();
|
||||
async initialize() {
|
||||
await this._loadStateForCurrentUser();
|
||||
|
||||
this.setupIpcHandlers();
|
||||
console.log('[ModelStateService] Initialized.');
|
||||
@ -64,8 +64,12 @@ class ModelStateService {
|
||||
});
|
||||
}
|
||||
|
||||
_loadStateForCurrentUser() {
|
||||
async _loadStateForCurrentUser() {
|
||||
const userId = this.authService.getCurrentUserId();
|
||||
|
||||
// Initialize encryption service for current user
|
||||
await encryptionService.initializeKey(userId);
|
||||
|
||||
const initialApiKeys = Object.keys(PROVIDERS).reduce((acc, key) => {
|
||||
acc[key] = null;
|
||||
return acc;
|
||||
@ -83,7 +87,7 @@ class ModelStateService {
|
||||
this.state.apiKeys[p] = null;
|
||||
} else if (this.state.apiKeys[p] && p !== 'ollama' && p !== 'whisper') {
|
||||
try {
|
||||
this.state.apiKeys[p] = cryptoService.decrypt(this.state.apiKeys[p]);
|
||||
this.state.apiKeys[p] = encryptionService.decrypt(this.state.apiKeys[p]);
|
||||
} catch (error) {
|
||||
console.error(`[ModelStateService] Failed to decrypt API key for ${p}, resetting`);
|
||||
this.state.apiKeys[p] = null;
|
||||
@ -107,7 +111,7 @@ class ModelStateService {
|
||||
for (const [provider, key] of Object.entries(stateToSave.apiKeys)) {
|
||||
if (key && provider !== 'ollama' && provider !== 'whisper') {
|
||||
try {
|
||||
stateToSave.apiKeys[provider] = cryptoService.encrypt(key);
|
||||
stateToSave.apiKeys[provider] = encryptionService.encrypt(key);
|
||||
} catch (error) {
|
||||
console.error(`[ModelStateService] Failed to encrypt API key for ${provider}`);
|
||||
stateToSave.apiKeys[provider] = null;
|
||||
@ -213,13 +217,21 @@ class ModelStateService {
|
||||
const llmModels = PROVIDERS['openai-glass']?.llmModels;
|
||||
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;
|
||||
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;
|
||||
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._logCurrentSelection();
|
||||
}
|
||||
|
@ -48,7 +48,11 @@ class WhisperService extends LocalAIServiceBase {
|
||||
|
||||
this.modelsDir = path.join(whisperDir, 'models');
|
||||
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.ensureWhisperBinary();
|
||||
@ -304,17 +308,112 @@ class WhisperService extends LocalAIServiceBase {
|
||||
const tempFile = path.join(this.tempDir, 'whisper-binary.zip');
|
||||
|
||||
try {
|
||||
console.log('[WhisperService] Step 1: Downloading Whisper binary...');
|
||||
await this.downloadWithRetry(binaryUrl, tempFile);
|
||||
const extractDir = path.dirname(this.whisperPath);
|
||||
await spawnAsync('powershell', ['-command', `Expand-Archive -Path '${tempFile}' -DestinationPath '${extractDir}' -Force`]);
|
||||
await fsPromises.unlink(tempFile);
|
||||
|
||||
console.log('[WhisperService] Step 2: Extracting archive...');
|
||||
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');
|
||||
return true;
|
||||
|
||||
} catch (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}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 압축 해제된 디렉토리에서 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() {
|
||||
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;
|
||||
|
||||
|
||||
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) {
|
||||
// if (windowPool.has('listen')) return;
|
||||
|
||||
@ -97,7 +90,7 @@ function createFeatureWindows(header, namesToCreate) {
|
||||
hasShadow: false,
|
||||
skipTaskbar: true,
|
||||
hiddenInMissionControl: true,
|
||||
resizable: false,
|
||||
resizable: true,
|
||||
webPreferences: { nodeIntegration: true, contextIsolation: false },
|
||||
};
|
||||
|
||||
@ -107,8 +100,8 @@ function createFeatureWindows(header, namesToCreate) {
|
||||
switch (name) {
|
||||
case 'listen': {
|
||||
const listen = new BrowserWindow({
|
||||
...commonChildOptions, width:400,minWidth:400,maxWidth:400,
|
||||
maxHeight:700,
|
||||
...commonChildOptions, width:400,minWidth:400,maxWidth:900,
|
||||
maxHeight:900,
|
||||
});
|
||||
listen.setContentProtection(isContentProtectionOn);
|
||||
listen.setVisibleOnAllWorkspaces(true,{visibleOnFullScreen:true});
|
||||
@ -305,6 +298,7 @@ function createFeatureWindows(header, namesToCreate) {
|
||||
}
|
||||
|
||||
function destroyFeatureWindows() {
|
||||
const featureWindows = ['listen','ask','settings','shortcut-settings'];
|
||||
if (settingsHideTimer) {
|
||||
clearTimeout(settingsHideTimer);
|
||||
settingsHideTimer = null;
|
||||
@ -337,78 +331,35 @@ function getDisplayById(displayId) {
|
||||
|
||||
|
||||
|
||||
function toggleAllWindowsVisibility(movementManager) {
|
||||
function toggleAllWindowsVisibility() {
|
||||
const header = windowPool.get('header');
|
||||
if (!header) return;
|
||||
|
||||
|
||||
if (header.isVisible()) {
|
||||
console.log('[Visibility] Smart hiding - calculating nearest edge');
|
||||
|
||||
const headerBounds = header.getBounds();
|
||||
const display = screen.getPrimaryDisplay();
|
||||
const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
||||
|
||||
const centerX = headerBounds.x + headerBounds.width / 2;
|
||||
const centerY = headerBounds.y + headerBounds.height / 2;
|
||||
|
||||
const distances = {
|
||||
top: centerY,
|
||||
bottom: screenHeight - centerY,
|
||||
left: centerX,
|
||||
right: screenWidth - centerX,
|
||||
};
|
||||
|
||||
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.clear();
|
||||
|
||||
windowPool.forEach((win, name) => {
|
||||
if (win && !win.isDestroyed() && win.isVisible()) {
|
||||
lastVisibleWindows.add(name);
|
||||
}
|
||||
});
|
||||
|
||||
lastVisibleWindows.forEach(name => {
|
||||
if (name === 'header') return;
|
||||
const win = windowPool.get(name);
|
||||
if (win && !win.isDestroyed()) win.hide();
|
||||
});
|
||||
header.hide();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lastVisibleWindows.forEach(name => {
|
||||
const win = windowPool.get(name);
|
||||
if (win && !win.isDestroyed())
|
||||
win.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function createWindows() {
|
||||
@ -464,6 +415,7 @@ function createWindows() {
|
||||
});
|
||||
}
|
||||
windowPool.set('header', header);
|
||||
header.on('moved', updateLayout);
|
||||
layoutManager = new WindowLayoutManager(windowPool);
|
||||
|
||||
header.webContents.once('dom-ready', () => {
|
||||
@ -507,25 +459,38 @@ function createWindows() {
|
||||
updateLayout();
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility(movementManager));
|
||||
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
|
||||
|
||||
ipcMain.handle('toggle-feature', async (event, featureName) => {
|
||||
if (!windowPool.get(featureName) && currentHeaderState === 'main') {
|
||||
createFeatureWindows(windowPool.get('header'));
|
||||
}
|
||||
|
||||
const windowToToggle = windowPool.get(featureName);
|
||||
|
||||
if (windowToToggle) {
|
||||
if (featureName === 'listen') {
|
||||
const listenService = global.listenService;
|
||||
if (listenService && listenService.isSessionActive()) {
|
||||
console.log('[WindowManager] Listen session is active, closing it via toggle.');
|
||||
await listenService.closeSession();
|
||||
return;
|
||||
const header = windowPool.get('header');
|
||||
if (featureName === 'listen') {
|
||||
console.log(`[WindowManager] Toggling feature: ${featureName}`);
|
||||
const listenWindow = windowPool.get(featureName);
|
||||
const listenService = global.listenService;
|
||||
if (listenService && listenService.isSessionActive()) {
|
||||
console.log('[WindowManager] Listen session is active, closing it via toggle.');
|
||||
await listenService.closeSession();
|
||||
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') {
|
||||
@ -590,13 +555,6 @@ function createWindows() {
|
||||
} else {
|
||||
console.log('[WindowManager] No response found, closing window');
|
||||
askWindow.webContents.send('window-hide-animation');
|
||||
|
||||
setTimeout(() => {
|
||||
if (!askWindow.isDestroyed()) {
|
||||
askWindow.hide();
|
||||
updateLayout();
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
} catch (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-did-show');
|
||||
}
|
||||
} else {
|
||||
const windowToToggle = windowPool.get(featureName);
|
||||
}
|
||||
|
||||
if (windowToToggle) {
|
||||
if (windowToToggle.isDestroyed()) {
|
||||
if (featureName === 'settings') {
|
||||
const settingsWindow = windowPool.get(featureName);
|
||||
|
||||
if (settingsWindow) {
|
||||
if (settingsWindow.isDestroyed()) {
|
||||
console.error(`Window ${featureName} is destroyed, cannot toggle`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (windowToToggle.isVisible()) {
|
||||
if (settingsWindow.isVisible()) {
|
||||
if (featureName === 'settings') {
|
||||
windowToToggle.webContents.send('settings-window-hide-animation');
|
||||
settingsWindow.webContents.send('settings-window-hide-animation');
|
||||
} else {
|
||||
windowToToggle.webContents.send('window-hide-animation');
|
||||
settingsWindow.webContents.send('window-hide-animation');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!windowToToggle.isDestroyed()) {
|
||||
windowToToggle.hide();
|
||||
updateLayout();
|
||||
}
|
||||
}, 250);
|
||||
} else {
|
||||
try {
|
||||
windowToToggle.show();
|
||||
settingsWindow.show();
|
||||
updateLayout();
|
||||
|
||||
if (featureName === 'listen') {
|
||||
windowToToggle.webContents.send('start-listening-session');
|
||||
}
|
||||
|
||||
windowToToggle.webContents.send('window-show-animation');
|
||||
settingsWindow.webContents.send('window-show-animation');
|
||||
} catch (e) {
|
||||
console.error('Error showing window:', e);
|
||||
}
|
||||
@ -767,9 +716,17 @@ function setupIpcHandlers(movementManager) {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('show-window', (event, args) => {
|
||||
const { name, bounds } = typeof args === 'object' && args !== null ? args : { name: args, bounds: null };
|
||||
const win = windowPool.get(name);
|
||||
ipcMain.on('animation-finished', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
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 (settingsHideTimer) {
|
||||
@ -777,78 +734,60 @@ function setupIpcHandlers(movementManager) {
|
||||
settingsHideTimer = null;
|
||||
}
|
||||
|
||||
if (name === 'settings') {
|
||||
// Adjust position based on button bounds
|
||||
const header = windowPool.get('header');
|
||||
const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
|
||||
const settingsBounds = win.getBounds();
|
||||
// Adjust position based on button bounds
|
||||
const header = windowPool.get('header');
|
||||
const headerBounds = header?.getBounds() ?? { x: 0, y: 0 };
|
||||
const settingsBounds = win.getBounds();
|
||||
|
||||
const disp = getCurrentDisplay(header);
|
||||
const { x: waX, y: waY, width: waW, height: waH } = disp.workArea;
|
||||
const disp = getCurrentDisplay(header);
|
||||
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 y = Math.round(headerBounds.y + (bounds?.y ?? 0) + (bounds?.height ?? 0) + 31);
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
win.setBounds({ x, y });
|
||||
win.__lockedByButton = true;
|
||||
console.log(`[WindowManager] Positioning settings window at (${x}, ${y}) based on button bounds.`);
|
||||
}
|
||||
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));
|
||||
|
||||
win.setBounds({ x, y });
|
||||
win.__lockedByButton = true;
|
||||
console.log(`[WindowManager] Positioning settings window at (${x}, ${y}) based on button bounds.`);
|
||||
|
||||
win.show();
|
||||
win.moveTop();
|
||||
|
||||
if (name === 'settings') {
|
||||
win.setAlwaysOnTop(true);
|
||||
}
|
||||
// updateLayout();
|
||||
win.setAlwaysOnTop(true);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('hide-window', (event, name) => {
|
||||
const window = windowPool.get(name);
|
||||
ipcMain.on('hide-settings-window', (event) => {
|
||||
const window = windowPool.get("settings");
|
||||
if (window && !window.isDestroyed()) {
|
||||
if (name === 'settings') {
|
||||
if (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();
|
||||
if (settingsHideTimer) {
|
||||
clearTimeout(settingsHideTimer);
|
||||
}
|
||||
settingsHideTimer = setTimeout(() => {
|
||||
if (window && !window.isDestroyed()) {
|
||||
window.setAlwaysOnTop(false);
|
||||
window.hide();
|
||||
}
|
||||
settingsHideTimer = null;
|
||||
}, 200);
|
||||
|
||||
window.__lockedByButton = false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('cancel-hide-window', (event, name) => {
|
||||
if (name === 'settings' && settingsHideTimer) {
|
||||
ipcMain.on('cancel-hide-settings-window', (event) => {
|
||||
if (settingsHideTimer) {
|
||||
clearTimeout(settingsHideTimer);
|
||||
settingsHideTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('hide-all', () => {
|
||||
windowPool.forEach(win => {
|
||||
if (win.isFocused()) return;
|
||||
win.hide();
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('quit-application', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipcMain.handle('is-window-visible', (event, windowName) => {
|
||||
ipcMain.handle('is-ask-window-visible', (event, windowName) => {
|
||||
const window = windowPool.get(windowName);
|
||||
if (window && !window.isDestroyed()) {
|
||||
return window.isVisible();
|
||||
@ -882,15 +821,6 @@ function setupIpcHandlers(movementManager) {
|
||||
destroyFeatureWindows();
|
||||
}
|
||||
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) => {
|
||||
@ -963,9 +893,6 @@ function setupIpcHandlers(movementManager) {
|
||||
|
||||
setupApiKeyIPC();
|
||||
|
||||
ipcMain.handle('resize-window', () => {});
|
||||
|
||||
ipcMain.handle('resize-for-view', () => {});
|
||||
|
||||
ipcMain.handle('resize-header-window', (event, { width, height }) => {
|
||||
const header = windowPool.get('header');
|
||||
@ -1018,74 +945,19 @@ function setupIpcHandlers(movementManager) {
|
||||
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');
|
||||
if (!header) return;
|
||||
|
||||
if (!header || header.isDestroyed()) return;
|
||||
|
||||
if (state === 'hidden') {
|
||||
header.hide();
|
||||
console.log('[WindowManager] Header hidden after animation.');
|
||||
} else if (state === 'visible') {
|
||||
lastVisibleWindows.forEach(name => {
|
||||
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);
|
||||
|
||||
console.log('[WindowManager] Header shown after animation.');
|
||||
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) => {
|
||||
if (movementManager) {
|
||||
@ -1099,13 +971,6 @@ function setupIpcHandlers(movementManager) {
|
||||
console.log(`[WindowManager] Force closing window: ${windowName}`);
|
||||
|
||||
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 (keybinds.toggleVisibility) {
|
||||
try {
|
||||
globalShortcut.register(keybinds.toggleVisibility, () => toggleAllWindowsVisibility(movementManager));
|
||||
globalShortcut.register(keybinds.toggleVisibility, () => toggleAllWindowsVisibility());
|
||||
} catch (error) {
|
||||
console.error(`Failed to register toggleVisibility (${keybinds.toggleVisibility}):`, error);
|
||||
}
|
||||
@ -1435,7 +1300,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
|
||||
let callback;
|
||||
switch(action) {
|
||||
case 'toggleVisibility':
|
||||
callback = () => toggleAllWindowsVisibility(movementManager);
|
||||
callback = () => toggleAllWindowsVisibility();
|
||||
break;
|
||||
case 'nextStep':
|
||||
callback = () => {
|
||||
@ -1605,9 +1470,6 @@ module.exports = {
|
||||
createWindows,
|
||||
windowPool,
|
||||
fixedYPosition,
|
||||
//////// before_modelStateService ////////
|
||||
// setApiKey,
|
||||
//////// before_modelStateService ////////
|
||||
getStoredApiKey,
|
||||
getStoredProvider,
|
||||
getCurrentModelInfo,
|
||||
|
@ -41,56 +41,6 @@ export class AskView extends LitElement {
|
||||
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;
|
||||
@ -252,20 +202,6 @@ export class AskView extends LitElement {
|
||||
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 {
|
||||
display: flex;
|
||||
@ -422,19 +358,6 @@ export class AskView extends LitElement {
|
||||
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 {
|
||||
position: relative;
|
||||
padding: 2px 0;
|
||||
@ -492,7 +415,7 @@ export class AskView extends LitElement {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -596,42 +519,6 @@ export class AskView extends LitElement {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
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() {
|
||||
@ -654,6 +541,7 @@ export class AskView extends LitElement {
|
||||
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
||||
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
||||
this.handleSendText = this.handleSendText.bind(this);
|
||||
this.handleGlobalSendRequest = this.handleGlobalSendRequest.bind(this);
|
||||
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
||||
this.closeResponsePanel = this.closeResponsePanel.bind(this);
|
||||
this.handleCopy = this.handleCopy.bind(this);
|
||||
@ -669,7 +557,6 @@ export class AskView extends LitElement {
|
||||
this.loadLibraries();
|
||||
|
||||
// --- Resize helpers ---
|
||||
this.adjustHeightThrottle = null;
|
||||
this.isThrottled = false;
|
||||
}
|
||||
|
||||
@ -1333,6 +1220,14 @@ export class AskView extends LitElement {
|
||||
|
||||
handleGlobalSendRequest() {
|
||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||
|
||||
if (!this.showTextInput) {
|
||||
this.showTextInput = true;
|
||||
this.requestUpdate();
|
||||
this.focusTextInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!textInput) return;
|
||||
|
||||
textInput.focus();
|
||||
@ -1462,12 +1357,11 @@ export class AskView extends LitElement {
|
||||
adjustWindowHeightThrottled() {
|
||||
if (this.isThrottled) return;
|
||||
|
||||
this.adjustWindowHeight();
|
||||
this.isThrottled = true;
|
||||
|
||||
this.adjustHeightThrottle = setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
this.adjustWindowHeight();
|
||||
this.isThrottled = false;
|
||||
}, 16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,56 +27,6 @@ export class AssistantView extends LitElement {
|
||||
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;
|
||||
@ -245,17 +195,6 @@ export class AssistantView extends LitElement {
|
||||
animation: slideIn 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(10%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.bar-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
@ -349,134 +288,6 @@ export class AssistantView extends LitElement {
|
||||
font-size: 10px;
|
||||
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 = {
|
||||
|
@ -144,7 +144,6 @@ class ListenService {
|
||||
|
||||
console.log('✅ Listen service initialized successfully.');
|
||||
|
||||
this.sendToRenderer('session-state-changed', { isActive: true });
|
||||
this.sendToRenderer('update-status', 'Connected. Ready to listen.');
|
||||
|
||||
return true;
|
||||
@ -155,6 +154,7 @@ class ListenService {
|
||||
} finally {
|
||||
this.isInitializingSession = false;
|
||||
this.sendToRenderer('session-initializing', false);
|
||||
this.sendToRenderer('change-listen-capture-state', { status: "start" });
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +179,7 @@ class ListenService {
|
||||
|
||||
async closeSession() {
|
||||
try {
|
||||
this.sendToRenderer('change-listen-capture-state', { status: "stop" });
|
||||
// Close STT sessions
|
||||
await this.sttService.closeSessions();
|
||||
|
||||
@ -192,7 +193,6 @@ class ListenService {
|
||||
this.currentSessionId = null;
|
||||
this.summaryService.resetConversationHistory();
|
||||
|
||||
this.sendToRenderer('session-state-changed', { isActive: false });
|
||||
this.sendToRenderer('session-did-close');
|
||||
|
||||
console.log('Listen service session closed.');
|
||||
@ -282,9 +282,9 @@ class ListenService {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('close-session', async () => {
|
||||
return await this.closeSession();
|
||||
});
|
||||
// ipcMain.handle('close-session', async () => {
|
||||
// return await this.closeSession();
|
||||
// });
|
||||
|
||||
ipcMain.handle('update-google-search-setting', async (event, enabled) => {
|
||||
try {
|
||||
|
@ -1,138 +1,30 @@
|
||||
// renderer.js
|
||||
const { ipcRenderer } = require('electron');
|
||||
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 = {
|
||||
initializeopenai,
|
||||
startCapture: listenCapture.startCapture,
|
||||
stopCapture: listenCapture.stopCapture,
|
||||
isLinux: listenCapture.isLinux,
|
||||
isMacOS: listenCapture.isMacOS,
|
||||
captureManualScreenshot: listenCapture.captureManualScreenshot,
|
||||
getCurrentScreenshot: listenCapture.getCurrentScreenshot,
|
||||
e: pickleGlassElement,
|
||||
};
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔔 React to session state changes from the main process
|
||||
// When the session ends (isActive === false), ensure we stop
|
||||
// all local capture pipelines (mic, screen, etc.).
|
||||
// -------------------------------------------------------
|
||||
ipcRenderer.on('session-state-changed', (_event, { isActive }) => {
|
||||
if (!isActive) {
|
||||
|
||||
ipcRenderer.on('change-listen-capture-state', (_event, { status }) => {
|
||||
if (!isListenView) {
|
||||
console.log('[Renderer] Non-listen view: ignoring capture-state change');
|
||||
return;
|
||||
}
|
||||
if (status === "stop") {
|
||||
console.log('[Renderer] Session ended – stopping local capture');
|
||||
listenCapture.stopCapture();
|
||||
} else {
|
||||
console.log('[Renderer] New session started – clearing in-memory history and summaries');
|
||||
|
||||
// Reset live conversation & analysis caches
|
||||
realtimeConversationHistory = [];
|
||||
|
||||
const blankData = {
|
||||
summary: [],
|
||||
topic: { header: '', bullets: [] },
|
||||
actions: [],
|
||||
followUps: [],
|
||||
};
|
||||
window.pickleGlass.setStructuredData(blankData);
|
||||
console.log('[Renderer] Session initialized – starting local capture');
|
||||
listenCapture.startCapture();
|
||||
}
|
||||
});
|
||||
|
@ -264,7 +264,7 @@ export class SummaryView extends LitElement {
|
||||
super.connectedCallback();
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.on('update-structured-data', (event, data) => {
|
||||
ipcRenderer.on('summary-update', (event, data) => {
|
||||
this.structuredData = data;
|
||||
this.requestUpdate();
|
||||
});
|
||||
@ -275,7 +275,7 @@ export class SummaryView extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
if (window.require) {
|
||||
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');
|
||||
|
||||
try {
|
||||
const isAskViewVisible = await ipcRenderer.invoke('is-window-visible', 'ask');
|
||||
const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
|
||||
|
||||
if (!isAskViewVisible) {
|
||||
await ipcRenderer.invoke('toggle-feature', 'ask');
|
||||
|
@ -27,23 +27,6 @@ class SummaryService {
|
||||
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) {
|
||||
BrowserWindow.getAllWindows().forEach(win => {
|
||||
if (!win.isDestroyed()) {
|
||||
@ -327,7 +310,7 @@ Keep all points concise and build upon previous analysis if provided.`,
|
||||
.then(data => {
|
||||
if (data) {
|
||||
console.log('📤 Sending structured data to renderer');
|
||||
this.sendToRenderer('update-structured-data', data);
|
||||
this.sendToRenderer('summary-update', data);
|
||||
|
||||
// Notify callback
|
||||
if (this.onAnalysisComplete) {
|
||||
|
@ -367,11 +367,6 @@ export class SettingsView extends LitElement {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@ -1030,14 +1025,14 @@ export class SettingsView extends LitElement {
|
||||
handleMouseEnter = () => {
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.send('cancel-hide-window', 'settings');
|
||||
ipcRenderer.send('cancel-hide-settings-window');
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (window.require) {
|
||||
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();
|
||||
|
||||
//////// after_modelStateService ////////
|
||||
modelStateService.initialize();
|
||||
await modelStateService.initialize();
|
||||
//////// after_modelStateService ////////
|
||||
|
||||
listenService.setupIpcHandlers();
|
||||
@ -396,21 +396,6 @@ function setupGeneralIpcHandlers() {
|
||||
const userRepository = require('./common/repositories/user');
|
||||
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', () => {
|
||||
// The adapter injects the UID.
|
||||
return presetRepository.getPresets();
|
||||
|
Loading…
x
Reference in New Issue
Block a user