diff --git a/src/common/config/schema.js b/src/common/config/schema.js index 19e0d8c..00b58f7 100644 --- a/src/common/config/schema.js +++ b/src/common/config/schema.js @@ -6,7 +6,8 @@ const LATEST_SCHEMA = { { name: 'email', type: 'TEXT NOT NULL' }, { name: 'created_at', type: 'INTEGER' }, { name: 'api_key', type: 'TEXT' }, - { name: 'provider', type: 'TEXT DEFAULT \'openai\'' } + { name: 'provider', type: 'TEXT DEFAULT \'openai\'' }, + { name: 'auto_update_enabled', type: 'INTEGER DEFAULT 1' } ] }, sessions: { diff --git a/src/common/services/authService.js b/src/common/services/authService.js index a664ba1..fd4b9de 100644 --- a/src/common/services/authService.js +++ b/src/common/services/authService.js @@ -37,56 +37,64 @@ class AuthService { this.currentUserMode = 'local'; // 'local' or 'firebase' this.currentUser = null; this.isInitialized = false; + this.initializationPromise = null; } initialize() { - if (this.isInitialized) return; + if (this.isInitialized) return this.initializationPromise; - const auth = getFirebaseAuth(); - onAuthStateChanged(auth, async (user) => { - const previousUser = this.currentUser; + this.initializationPromise = new Promise((resolve) => { + const auth = getFirebaseAuth(); + onAuthStateChanged(auth, async (user) => { + const previousUser = this.currentUser; - if (user) { - // User signed IN - console.log(`[AuthService] Firebase user signed in:`, user.uid); - this.currentUser = user; - this.currentUserId = user.uid; - this.currentUserMode = 'firebase'; + if (user) { + // User signed IN + console.log(`[AuthService] Firebase user signed in:`, user.uid); + this.currentUser = user; + this.currentUserId = user.uid; + this.currentUserMode = 'firebase'; - // Start background task to fetch and save virtual key - (async () => { - try { - const idToken = await user.getIdToken(true); - const virtualKey = await getVirtualKeyByEmail(user.email, idToken); + // Start background task to fetch and save virtual key + (async () => { + try { + const idToken = await user.getIdToken(true); + const virtualKey = await getVirtualKeyByEmail(user.email, idToken); - if (global.modelStateService) { - global.modelStateService.setFirebaseVirtualKey(virtualKey); + if (global.modelStateService) { + global.modelStateService.setFirebaseVirtualKey(virtualKey); + } + console.log(`[AuthService] BG: Virtual key for ${user.email} has been processed.`); + + } catch (error) { + console.error('[AuthService] BG: Failed to fetch or save virtual key:', error); } - console.log(`[AuthService] BG: Virtual key for ${user.email} has been processed.`); + })(); - } catch (error) { - console.error('[AuthService] BG: Failed to fetch or save virtual key:', error); - } - })(); - - } else { - // User signed OUT - console.log(`[AuthService] No Firebase user.`); - if (previousUser) { - console.log(`[AuthService] Clearing API key for logged-out user: ${previousUser.uid}`); - if (global.modelStateService) { - global.modelStateService.setFirebaseVirtualKey(null); + } else { + // User signed OUT + console.log(`[AuthService] No Firebase user.`); + if (previousUser) { + console.log(`[AuthService] Clearing API key for logged-out user: ${previousUser.uid}`); + if (global.modelStateService) { + global.modelStateService.setFirebaseVirtualKey(null); + } } + this.currentUser = null; + this.currentUserId = 'default_user'; + this.currentUserMode = 'local'; } - this.currentUser = null; - this.currentUserId = 'default_user'; - this.currentUserMode = 'local'; - } - this.broadcastUserState(); + this.broadcastUserState(); + + if (!this.isInitialized) { + this.isInitialized = true; + console.log('[AuthService] Initialized and resolved initialization promise.'); + resolve(); + } + }); }); - this.isInitialized = true; - console.log('[AuthService] Initialized and attached to Firebase Auth state.'); + return this.initializationPromise; } async signInWithCustomToken(token) { diff --git a/src/electron/smoothMovementManager.js b/src/electron/smoothMovementManager.js index 67f383d..75feae6 100644 --- a/src/electron/smoothMovementManager.js +++ b/src/electron/smoothMovementManager.js @@ -122,6 +122,11 @@ class SmoothMovementManager { console.log(`[MovementManager] Moving ${direction} from (${targetX}, ${targetY})`); + const windowSize = { + width: currentBounds.width, + height: currentBounds.height + }; + switch (direction) { case 'left': targetX -= this.stepSize; break; case 'right': targetX += this.stepSize; break; @@ -165,7 +170,7 @@ class SmoothMovementManager { this.animateToPosition(header, clampedX, clampedY); } - animateToPosition(header, targetX, targetY) { + animateToPosition(header, targetX, targetY, windowSize) { if (!this._isWindowValid(header)) return; this.isAnimating = true; @@ -193,7 +198,13 @@ class SmoothMovementManager { } if (!this._isWindowValid(header)) return; - header.setPosition(Math.round(currentX), Math.round(currentY)); + const { width, height } = windowSize || header.getBounds(); + header.setBounds({ + x: Math.round(currentX), + y: Math.round(currentY), + width, + height + }); if (progress < 1) { this.animationFrameId = setTimeout(animate, 8); @@ -219,20 +230,40 @@ class SmoothMovementManager { const display = this.getCurrentDisplay(header); const { width, height } = display.workAreaSize; const { x: workAreaX, y: workAreaY } = display.workArea; - const headerBounds = header.getBounds(); const currentBounds = header.getBounds(); + + const windowSize = { + width: currentBounds.width, + height: currentBounds.height + }; + let targetX = currentBounds.x; let targetY = currentBounds.y; switch (direction) { - case 'left': targetX = workAreaX; break; - case 'right': targetX = workAreaX + width - headerBounds.width; break; - case 'up': targetY = workAreaY; break; - case 'down': targetY = workAreaY + height - headerBounds.height; break; + case 'left': + targetX = workAreaX; + break; + case 'right': + targetX = workAreaX + width - windowSize.width; + break; + case 'up': + targetY = workAreaY; + break; + case 'down': + targetY = workAreaY + height - windowSize.height; + break; } - this.headerPosition = { x: currentBounds.x, y: currentBounds.y }; - this.animateToPosition(header, targetX, targetY); + header.setBounds({ + x: Math.round(targetX), + y: Math.round(targetY), + width: windowSize.width, + height: windowSize.height + }); + + this.headerPosition = { x: targetX, y: targetY }; + this.updateLayout(); } destroy() { @@ -245,4 +276,4 @@ class SmoothMovementManager { } } -module.exports = SmoothMovementManager; \ No newline at end of file +module.exports = SmoothMovementManager; diff --git a/src/features/ask/AskView.js b/src/features/ask/AskView.js index ee0c153..ed4794f 100644 --- a/src/features/ask/AskView.js +++ b/src/features/ask/AskView.js @@ -98,6 +98,12 @@ export class AskView extends LitElement { user-select: none; } + /* Allow text selection in assistant responses */ + .response-container, .response-container * { + user-select: text !important; + cursor: text !important; + } + .response-container pre { background: rgba(0, 0, 0, 0.4) !important; border-radius: 8px !important; diff --git a/src/features/listen/AssistantView.js b/src/features/listen/AssistantView.js index 311a11f..9c6a7e9 100644 --- a/src/features/listen/AssistantView.js +++ b/src/features/listen/AssistantView.js @@ -84,6 +84,87 @@ export class AssistantView extends LitElement { user-select: none; } +/* Allow text selection in insights responses */ +.insights-container, .insights-container *, .markdown-content { + user-select: text !important; + cursor: text !important; +} + +/* highlight.js 스타일 추가 */ +.insights-container pre { + background: rgba(0, 0, 0, 0.4) !important; + border-radius: 8px !important; + padding: 12px !important; + margin: 8px 0 !important; + overflow-x: auto !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; +} + +.insights-container code { + font-family: 'Monaco', 'Menlo', 'Consolas', monospace !important; + font-size: 11px !important; + background: transparent !important; + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; +} + +.insights-container pre code { + white-space: pre !important; + word-wrap: normal !important; + word-break: normal !important; + display: block !important; +} + +.insights-container p code { + background: rgba(255, 255, 255, 0.1) !important; + padding: 2px 4px !important; + border-radius: 3px !important; + color: #ffd700 !important; +} + +.hljs-keyword { + color: #ff79c6 !important; +} + +.hljs-string { + color: #f1fa8c !important; +} + +.hljs-comment { + color: #6272a4 !important; +} + +.hljs-number { + color: #bd93f9 !important; +} + +.hljs-function { + color: #50fa7b !important; +} + +.hljs-title { + color: #50fa7b !important; +} + +.hljs-variable { + color: #8be9fd !important; +} + +.hljs-built_in { + color: #ffb86c !important; +} + +.hljs-attr { + color: #50fa7b !important; +} + +.hljs-tag { + color: #ff79c6 !important; +} .assistant-container { display: flex; flex-direction: column; diff --git a/src/features/settings/SettingsView.js b/src/features/settings/SettingsView.js index 7dbd9f1..bd99937 100644 --- a/src/features/settings/SettingsView.js +++ b/src/features/settings/SettingsView.js @@ -456,6 +456,8 @@ export class SettingsView extends LitElement { presets: { type: Array, state: true }, selectedPreset: { type: Object, state: true }, showPresets: { type: Boolean, state: true }, + autoUpdateEnabled: { type: Boolean, state: true }, + autoUpdateLoading: { type: Boolean, state: true }, }; //////// after_modelStateService //////// @@ -479,10 +481,48 @@ export class SettingsView extends LitElement { this.selectedPreset = null; this.showPresets = false; this.handleUsePicklesKey = this.handleUsePicklesKey.bind(this) + this.autoUpdateEnabled = true; + this.autoUpdateLoading = true; this.loadInitialData(); //////// after_modelStateService //////// } + async loadAutoUpdateSetting() { + if (!window.require) return; + const { ipcRenderer } = window.require('electron'); + this.autoUpdateLoading = true; + try { + const enabled = await ipcRenderer.invoke('settings:get-auto-update'); + this.autoUpdateEnabled = enabled; + console.log('Auto-update setting loaded:', enabled); + } catch (e) { + console.error('Error loading auto-update setting:', e); + this.autoUpdateEnabled = true; // fallback + } + this.autoUpdateLoading = false; + this.requestUpdate(); + } + + async handleToggleAutoUpdate() { + if (!window.require || this.autoUpdateLoading) return; + const { ipcRenderer } = window.require('electron'); + this.autoUpdateLoading = true; + this.requestUpdate(); + try { + const newValue = !this.autoUpdateEnabled; + const result = await ipcRenderer.invoke('settings:set-auto-update', newValue); + if (result && result.success) { + this.autoUpdateEnabled = newValue; + } else { + console.error('Failed to update auto-update setting'); + } + } catch (e) { + console.error('Error toggling auto-update:', e); + } + this.autoUpdateLoading = false; + this.requestUpdate(); + } + //////// after_modelStateService //////// async loadInitialData() { if (!window.require) return; @@ -617,6 +657,7 @@ export class SettingsView extends LitElement { this.setupEventListeners(); this.setupIpcListeners(); this.setupWindowResize(); + this.loadAutoUpdateSetting(); } disconnectedCallback() { @@ -648,6 +689,7 @@ export class SettingsView extends LitElement { } else { this.firebaseUser = null; } + this.loadAutoUpdateSetting(); this.requestUpdate(); }; @@ -1161,6 +1203,9 @@ export class SettingsView extends LitElement { +