centralized ask logic

This commit is contained in:
sanio 2025-07-12 23:23:02 +09:00
parent 8402e7d296
commit c948d4ed08
5 changed files with 268 additions and 410 deletions

View File

@ -22,11 +22,6 @@ async function sendMessage(userPrompt) {
console.warn('[AskService] Cannot process empty message');
return { success: false, error: 'Empty message' };
}
const askWindow = windowPool.get('ask');
if (askWindow && !askWindow.isDestroyed()) {
askWindow.webContents.send('hide-text-input');
}
let sessionId;

View File

@ -193,8 +193,6 @@ class ListenService {
this.currentSessionId = null;
this.summaryService.resetConversationHistory();
this.sendToRenderer('session-did-close');
console.log('Listen service session closed.');
return { success: true };
} catch (error) {

View File

@ -729,17 +729,13 @@ 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);
this.clearResponseContent = this.clearResponseContent.bind(this);
this.processAssistantQuestion = this.processAssistantQuestion.bind(this);
this.handleEscKey = this.handleEscKey.bind(this);
this.handleDocumentClick = this.handleDocumentClick.bind(this);
this.handleWindowBlur = this.handleWindowBlur.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.handleCloseAskWindow = this.handleCloseAskWindow.bind(this);
this.handleCloseIfNoContent = this.handleCloseIfNoContent.bind(this);
this.loadLibraries();
@ -747,6 +743,94 @@ export class AskView extends LitElement {
this.isThrottled = false;
}
connectedCallback() {
super.connectedCallback();
console.log('📱 AskView connectedCallback - IPC 이벤트 리스너 설정');
document.addEventListener('keydown', this.handleEscKey);
this.resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const needed = entry.contentRect.height;
const current = window.innerHeight;
if (needed > current - 4) {
this.requestWindowResize(Math.ceil(needed));
}
}
});
const container = this.shadowRoot?.querySelector('.ask-container');
if (container) this.resizeObserver.observe(container);
this.handleQuestionFromAssistant = (event, question) => {
console.log('📨 AskView: Received question from ListenView:', question);
this.handleSendText(null, question);
};
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('ask:sendQuestionToRenderer', this.handleQuestionFromAssistant);
ipcRenderer.on('hide-text-input', () => {
console.log('📤 Hide text input signal received');
this.showTextInput = false;
this.requestUpdate();
});
ipcRenderer.on('ask:showTextInput', () => {
console.log('📤 Show text input signal received');
if (!this.showTextInput) {
this.showTextInput = true;
this.requestUpdate();
}
});
ipcRenderer.on('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.on('ask-response-stream-end', this.handleStreamEnd);
ipcRenderer.on('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.on('scroll-response-down', () => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver?.disconnect();
console.log('📱 AskView disconnectedCallback - IPC 이벤트 리스너 제거');
document.removeEventListener('keydown', this.handleEscKey);
if (this.copyTimeout) {
clearTimeout(this.copyTimeout);
}
if (this.headerAnimationTimeout) {
clearTimeout(this.headerAnimationTimeout);
}
if (this.streamingTimeout) {
clearTimeout(this.streamingTimeout);
}
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeListener('hide-text-input', () => { });
ipcRenderer.removeListener('ask:showTextInput', () => { });
ipcRenderer.removeListener('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.removeListener('ask-response-stream-end', this.handleStreamEnd);
ipcRenderer.removeListener('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.removeListener('scroll-response-down', () => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 제거 완료');
}
}
async loadLibraries() {
try {
if (!window.marked) {
@ -803,38 +887,49 @@ export class AskView extends LitElement {
}
}
handleDocumentClick(e) {
handleCloseAskWindow() {
this.clearResponseContent();
ipcRenderer.invoke('ask:closeAskWindow');
}
handleCloseIfNoContent() {
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
const askContainer = this.shadowRoot?.querySelector('.ask-container');
if (askContainer && !e.composedPath().includes(askContainer)) {
this.closeIfNoContent();
}
this.handleCloseAskWindow();
}
}
handleEscKey(e) {
if (e.key === 'Escape') {
e.preventDefault();
this.closeResponsePanel();
this.handleCloseIfNoContent();
}
}
handleWindowBlur() {
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
// If there's no active content, ask the main process to close this window.
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('close-ask-window-if-empty');
clearResponseContent() {
this.currentResponse = '';
this.currentQuestion = '';
this.isLoading = false;
this.isStreaming = false;
this.headerText = 'AI Response';
this.showTextInput = true;
this.accumulatedResponse = '';
this.requestUpdate();
this.renderContent();
}
handleInputFocus() {
this.isInputFocused = true;
}
focusTextInput() {
requestAnimationFrame(() => {
const textInput = this.shadowRoot?.getElementById('textInput');
if (textInput) {
textInput.focus();
}
}
});
}
closeIfNoContent() {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('force-close-window', 'ask');
}
}
loadScript(src) {
return new Promise((resolve, reject) => {
@ -874,114 +969,6 @@ export class AskView extends LitElement {
return text;
}
connectedCallback() {
super.connectedCallback();
console.log('📱 AskView connectedCallback - IPC 이벤트 리스너 설정');
document.addEventListener('click', this.handleDocumentClick, true);
document.addEventListener('keydown', this.handleEscKey);
this.resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const needed = entry.contentRect.height;
const current = window.innerHeight;
if (needed > current - 4) {
this.requestWindowResize(Math.ceil(needed));
}
}
});
const container = this.shadowRoot?.querySelector('.ask-container');
if (container) this.resizeObserver.observe(container);
this.handleQuestionFromAssistant = (event, question) => {
console.log('📨 AskView: Received question from ListenView:', question);
this.currentResponse = '';
this.isStreaming = false;
this.requestUpdate();
this.currentQuestion = question;
this.isLoading = true;
this.showTextInput = false;
this.headerText = 'analyzing screen...';
this.startHeaderAnimation();
this.requestUpdate();
this.processAssistantQuestion(question);
};
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.on('ask-global-send', this.handleGlobalSendRequest);
ipcRenderer.on('receive-question-from-assistant', this.handleQuestionFromAssistant);
ipcRenderer.on('hide-text-input', () => {
console.log('📤 Hide text input signal received');
this.showTextInput = false;
this.requestUpdate();
});
ipcRenderer.on('window-hide-animation', () => {
console.log('📤 Ask window hiding - clearing response content');
setTimeout(() => {
this.clearResponseContent();
}, 250);
});
ipcRenderer.on('window-blur', this.handleWindowBlur);
ipcRenderer.on('window-did-show', () => {
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
this.focusTextInput();
}
});
ipcRenderer.on('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.on('ask-response-stream-end', this.handleStreamEnd);
ipcRenderer.on('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.on('scroll-response-down', () => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver?.disconnect();
console.log('📱 AskView disconnectedCallback - IPC 이벤트 리스너 제거');
document.removeEventListener('click', this.handleDocumentClick, true);
document.removeEventListener('keydown', this.handleEscKey);
if (this.copyTimeout) {
clearTimeout(this.copyTimeout);
}
if (this.headerAnimationTimeout) {
clearTimeout(this.headerAnimationTimeout);
}
if (this.streamingTimeout) {
clearTimeout(this.streamingTimeout);
}
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.removeListener('ask-global-send', this.handleGlobalSendRequest);
ipcRenderer.removeListener('hide-text-input', () => { });
ipcRenderer.removeListener('window-hide-animation', () => { });
ipcRenderer.removeListener('window-blur', this.handleWindowBlur);
ipcRenderer.removeListener('ask-response-chunk', this.handleStreamChunk);
ipcRenderer.removeListener('ask-response-stream-end', this.handleStreamEnd);
ipcRenderer.removeListener('scroll-response-up', () => this.handleScroll('up'));
ipcRenderer.removeListener('scroll-response-down', () => this.handleScroll('down'));
console.log('✅ AskView: IPC 이벤트 리스너 제거 완료');
}
}
handleScroll(direction) {
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
if (scrollableElement) {
@ -1124,18 +1111,6 @@ export class AskView extends LitElement {
this.adjustWindowHeightThrottled();
}
clearResponseContent() {
this.currentResponse = '';
this.currentQuestion = '';
this.isLoading = false;
this.isStreaming = false;
this.headerText = 'AI Response';
this.showTextInput = true;
this.accumulatedResponse = '';
this.requestUpdate();
this.renderContent(); // 👈 updateResponseContent() 대신 renderContent() 호출
}
requestWindowResize(targetHeight) {
if (window.require) {
@ -1180,13 +1155,6 @@ export class AskView extends LitElement {
.replace(/`(.*?)`/g, '<code>$1</code>');
}
closeResponsePanel() {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('force-close-window', 'ask');
}
}
fixIncompleteMarkdown(text) {
if (!text) return text;
@ -1224,29 +1192,6 @@ export class AskView extends LitElement {
return text;
}
// ✨ processAssistantQuestion 수정
async processAssistantQuestion(question) {
this.currentQuestion = question;
this.showTextInput = false;
this.isLoading = true;
this.isStreaming = false;
this.currentResponse = '';
this.accumulatedResponse = '';
this.startHeaderAnimation();
this.requestUpdate();
this.renderContent();
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.invoke('ask:sendMessage', question).catch(error => {
console.error('Error processing assistant question:', error);
this.isLoading = false;
this.isStreaming = false;
this.currentResponse = `Error: ${error.message}`;
this.renderContent();
});
}
}
async handleCopy() {
if (this.copyState === 'copied') return;
@ -1316,10 +1261,9 @@ export class AskView extends LitElement {
}
}
async handleSendText() {
async handleSendText(e, overridingText = '') {
const textInput = this.shadowRoot?.getElementById('textInput');
if (!textInput) return;
const text = textInput.value.trim();
const text = (overridingText || textInput?.value || '').trim();
if (!text) return;
textInput.value = '';
@ -1377,37 +1321,10 @@ export class AskView extends LitElement {
}
}
focusTextInput() {
requestAnimationFrame(() => {
const textInput = this.shadowRoot?.getElementById('textInput');
if (textInput) {
textInput.focus();
}
});
}
firstUpdated() {
setTimeout(() => this.adjustWindowHeight(), 200);
}
handleGlobalSendRequest() {
const textInput = this.shadowRoot?.getElementById('textInput');
if (!this.showTextInput) {
this.showTextInput = true;
this.requestUpdate();
this.focusTextInput();
return;
}
if (!textInput) return;
textInput.focus();
if (!textInput.value.trim()) return;
this.handleSendText();
}
getTruncatedQuestion(question, maxLength = 30) {
if (!question) return '';
@ -1415,24 +1332,7 @@ export class AskView extends LitElement {
return question.substring(0, maxLength) + '...';
}
handleInputFocus() {
this.isInputFocused = true;
}
handleInputBlur(e) {
this.isInputFocused = false;
// 잠시 후 포커스가 다른 곳으로 갔는지 확인
setTimeout(() => {
const activeElement = this.shadowRoot?.activeElement || document.activeElement;
const textInput = this.shadowRoot?.getElementById('textInput');
// 포커스가 AskView 내부가 아니고, 응답이 없는 경우
if (!this.currentResponse && !this.isLoading && !this.isStreaming && activeElement !== textInput && !this.isInputFocused) {
this.closeIfNoContent();
}
}, 200);
}
render() {
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
@ -1470,7 +1370,7 @@ export class AskView extends LitElement {
<path d="M20 6L9 17l-5-5" />
</svg>
</button>
<button class="close-button" @click=${this.closeResponsePanel}>
<button class="close-button" @click=${this.handleCloseAskWindow}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
@ -1493,7 +1393,6 @@ export class AskView extends LitElement {
placeholder="Ask about your screen or audio"
@keydown=${this.handleTextKeydown}
@focus=${this.handleInputFocus}
@blur=${this.handleInputBlur}
/>
<button
class="submit-btn"

View File

@ -412,14 +412,7 @@ export class SummaryView extends LitElement {
const { ipcRenderer } = window.require('electron');
try {
const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
if (!isAskViewVisible) {
await ipcRenderer.invoke('toggle-feature', 'ask');
await new Promise(resolve => setTimeout(resolve, 100));
}
const result = await ipcRenderer.invoke('send-question-to-ask', requestText);
const result = await ipcRenderer.invoke('ask:sendQuestionToMain', requestText);
if (result.success) {
console.log('✅ Question sent to AskView successfully');

View File

@ -76,8 +76,16 @@ function updateLayout() {
let movementManager = null;
const windowBridge = require('../bridge/windowBridge');
async function toggleFeature(featureName) {
/**
*
* @param {'listen'|'ask'|'settings'} featureName
* @param {{
* listen?: { targetVisibility?: 'show'|'hide' },
* ask?: { targetVisibility?: 'show'|'hide', questionText?: string },
* settings?: { targetVisibility?: 'show'|'hide' }
* }} [options={}]
*/
async function toggleFeature(featureName, options = {}) {
if (!windowPool.get(featureName) && currentHeaderState === 'main') {
createFeatureWindows(windowPool.get('header'));
}
@ -117,14 +125,27 @@ async function toggleFeature(featureName) {
return;
}
const questionText = options?.ask?.questionText ?? null;
const targetVisibility = options?.ask?.targetVisibility ?? null;
if (askWindow.isVisible()) {
askWindow.webContents.send('ask-global-send');
if (questionText) {
askWindow.webContents.send('ask:sendQuestionToRenderer', questionText);
} else {
updateLayout();
if (targetVisibility === 'show') {
askWindow.webContents.send('ask:showTextInput');
} else {
askWindow.webContents.send('window-hide-animation');
}
}
} else {
console.log('[WindowManager] Showing hidden Ask window');
askWindow.show();
updateLayout();
if (questionText) {
askWindow.webContents.send('ask:sendQuestionToRenderer', questionText);
}
askWindow.webContents.send('window-show-animation');
askWindow.webContents.send('window-did-show');
}
}
@ -238,8 +259,6 @@ function createFeatureWindows(header, namesToCreate) {
}
});
}
ask.on('blur',()=>ask.webContents.send('window-blur'));
// Open DevTools in development
if (!app.isPackaged) {
@ -532,61 +551,6 @@ function createWindows() {
updateLayout();
});
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
ipcMain.handle('toggle-feature', async (event, featureName) => {
return toggleFeature(featureName);
});
ipcMain.handle('send-question-to-ask', (event, question) => {
const askWindow = windowPool.get('ask');
if (askWindow && !askWindow.isDestroyed()) {
console.log('📨 Main process: Sending question to AskView', question);
askWindow.webContents.send('receive-question-from-assistant', question);
return { success: true };
} else {
console.error('❌ Cannot find AskView window');
return { success: false, error: 'AskView window not found' };
}
});
ipcMain.handle('adjust-window-height', (event, targetHeight) => {
const senderWindow = BrowserWindow.fromWebContents(event.sender);
if (senderWindow) {
const wasResizable = senderWindow.isResizable();
if (!wasResizable) {
senderWindow.setResizable(true);
}
const currentBounds = senderWindow.getBounds();
const minHeight = senderWindow.getMinimumSize()[1];
const maxHeight = senderWindow.getMaximumSize()[1];
let adjustedHeight;
if (maxHeight === 0) {
adjustedHeight = Math.max(minHeight, targetHeight);
} else {
adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
}
senderWindow.setSize(currentBounds.width, adjustedHeight, false);
if (!wasResizable) {
senderWindow.setResizable(false);
}
updateLayout();
}
});
ipcMain.on('session-did-close', () => {
const listenWindow = windowPool.get('listen');
if (listenWindow && listenWindow.isVisible()) {
console.log('[WindowManager] Session closed, hiding listen window.');
listenWindow.hide();
}
});
return windowPool;
}
@ -617,6 +581,12 @@ function loadAndRegisterShortcuts(movementManager) {
function setupIpcHandlers(movementManager) {
setupApiKeyIPC();
ipcMain.handle('quit-application', () => {
app.quit();
});
screen.on('display-added', (event, newDisplay) => {
console.log('[Display] New display added:', newDisplay.id);
});
@ -631,107 +601,10 @@ function setupIpcHandlers(movementManager) {
});
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
console.log('[Display] Display metrics changed:', display.id, changedMetrics);
// console.log('[Display] Display metrics changed:', display.id, changedMetrics);
updateLayout();
});
// 1. 스트리밍 데이터 조각(chunk)을 받아서 ask 창으로 전달
ipcMain.on('ask-response-chunk', (event, { token }) => {
const askWindow = windowPool.get('ask');
if (askWindow && !askWindow.isDestroyed()) {
// renderer.js가 보낸 토큰을 AskView.js로 그대로 전달합니다.
askWindow.webContents.send('ask-response-chunk', { token });
}
});
// 2. 스트리밍 종료 신호를 받아서 ask 창으로 전달
ipcMain.on('ask-response-stream-end', () => {
const askWindow = windowPool.get('ask');
if (askWindow && !askWindow.isDestroyed()) {
askWindow.webContents.send('ask-response-stream-end');
}
});
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) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
// 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;
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.`);
win.show();
win.moveTop();
win.setAlwaysOnTop(true);
}
});
ipcMain.on('hide-settings-window', (event) => {
const window = windowPool.get("settings");
if (window && !window.isDestroyed()) {
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-settings-window', (event) => {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
});
ipcMain.handle('quit-application', () => {
app.quit();
});
ipcMain.handle('is-ask-window-visible', (event, windowName) => {
const window = windowPool.get(windowName);
if (window && !window.isDestroyed()) {
return window.isVisible();
}
return false;
});
ipcMain.handle('toggle-content-protection', () => {
isContentProtectionOn = !isContentProtectionOn;
console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
@ -827,8 +700,6 @@ function setupIpcHandlers(movementManager) {
console.log('Opening personalization page:', personalizeUrl);
});
setupApiKeyIPC();
ipcMain.handle('resize-header-window', (event, { width, height }) => {
const header = windowPool.get('header');
@ -952,12 +823,32 @@ function setupIpcHandlers(movementManager) {
}
});
ipcMain.handle('force-close-window', (event, windowName) => {
const window = windowPool.get(windowName);
if (window && !window.isDestroyed()) {
console.log(`[WindowManager] Force closing window: ${windowName}`);
ipcMain.handle('adjust-window-height', (event, targetHeight) => {
const senderWindow = BrowserWindow.fromWebContents(event.sender);
if (senderWindow) {
const wasResizable = senderWindow.isResizable();
if (!wasResizable) {
senderWindow.setResizable(true);
}
window.webContents.send('window-hide-animation');
const currentBounds = senderWindow.getBounds();
const minHeight = senderWindow.getMinimumSize()[1];
const maxHeight = senderWindow.getMaximumSize()[1];
let adjustedHeight;
if (maxHeight === 0) {
adjustedHeight = Math.max(minHeight, targetHeight);
} else {
adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
}
senderWindow.setSize(currentBounds.width, adjustedHeight, false);
if (!wasResizable) {
senderWindow.setResizable(false);
}
updateLayout();
}
});
@ -1140,13 +1031,95 @@ function setupIpcHandlers(movementManager) {
return false;
}
});
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
ipcMain.handle('close-ask-window-if-empty', async () => {
const askWindow = windowPool.get('ask');
if (askWindow && !askWindow.isFocused()) {
askWindow.hide();
ipcMain.handle('toggle-feature', async (event, featureName) => {
return toggleFeature(featureName);
});
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) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
// 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;
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.`);
win.show();
win.moveTop();
win.setAlwaysOnTop(true);
}
});
ipcMain.on('hide-settings-window', (event) => {
const window = windowPool.get("settings");
if (window && !window.isDestroyed()) {
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-settings-window', (event) => {
if (settingsHideTimer) {
clearTimeout(settingsHideTimer);
settingsHideTimer = null;
}
});
ipcMain.handle('ask:closeAskWindow', async () => {
const askWindow = windowPool.get('ask');
if (askWindow) {
askWindow.webContents.send('window-hide-animation');
}
});
ipcMain.handle('ask:sendQuestionToMain', (event, question) => {
console.log('📨 Main process: Sending question to AskView', question);
toggleFeature('ask', {ask: { questionText: question }});
return { success: true };
});
}
@ -1290,7 +1263,7 @@ function updateGlobalShortcuts(keybinds, mainWindow, sendToRenderer, movementMan
callback = () => toggleAllWindowsVisibility();
break;
case 'nextStep':
callback = () => toggleFeature('ask');
callback = () => toggleFeature('ask', {ask: { targetVisibility: 'show' }});
break;
case 'scrollUp':
callback = () => {