Merge branch 'refactor/0712_ask' of https://github.com/pickle-com/glass into refactor/0712_ask

This commit is contained in:
samtiz 2025-07-13 10:49:22 +09:00
commit 9ec8df0548
4 changed files with 73 additions and 171 deletions

View File

@ -4,6 +4,7 @@ const { getCurrentModelInfo, windowPool, captureScreenshot } = require('../../wi
const sessionRepository = require('../common/repositories/session');
const askRepository = require('./repositories');
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
const listenService = require('../listen/listenService');
/**
* @class
@ -34,15 +35,12 @@ class AskService {
const { windowPool, updateLayout } = require('../../window/windowManager');
const askWindow = windowPool.get('ask');
// 답변이 있거나 스트리밍 중일 때
const hasContent = this.state.isStreaming || (this.state.currentResponse && this.state.currentResponse.length > 0);
if (askWindow.isVisible() && hasContent) {
// 창을 닫는 대신, 텍스트 입력창만 토글합니다.
this.state.showTextInput = !this.state.showTextInput;
this._broadcastState(); // 변경된 상태 전파
this._broadcastState();
} else {
// 기존의 창 보이기/숨기기 로직
if (askWindow.isVisible()) {
askWindow.webContents.send('window-hide-animation');
this.state.isVisible = false;
@ -53,7 +51,6 @@ class AskService {
updateLayout();
askWindow.webContents.send('window-show-animation');
}
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
if (this.state.isVisible) {
this.state.showTextInput = true;
this._broadcastState();
@ -80,7 +77,7 @@ class AskService {
* @param {string} userPrompt
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
*/
async sendMessage(userPrompt, conversationHistoryRaw=[]) {
async sendMessage(userPrompt) {
if (this.abortController) {
this.abortController.abort('New request received.');
}
@ -120,7 +117,20 @@ class AskService {
const screenshotResult = await captureScreenshot({ quality: 'medium' });
const screenshotBase64 = screenshotResult.success ? screenshotResult.base64 : null;
let conversationHistoryRaw = [];
try {
const history = listenService.getConversationHistory();
if (history && Array.isArray(history) && history.length > 0) {
console.log(`[AskService] Using conversation history from ListenService ${history.length} items).`);
conversationHistoryRaw = history;
} else {
console.log('[AskService] No active conversation history found in ListenService.');
}
} catch (error) {
console.error('[AskService] Failed to get conversation history from ListenService:', error);
}
const conversationHistory = this._formatConversationForPrompt(conversationHistoryRaw);
console.log(`[AskService] Using conversation history (${conversationHistory}`);
const systemPrompt = getSystemPrompt('pickle_glass_analysis', conversationHistory, false);
@ -231,9 +241,6 @@ class AskService {
console.log(`[AskService] Stream reading was intentionally cancelled. Reason: ${signal.reason}`);
} else {
console.error('[AskService] Error while processing stream:', streamError);
if (askWin && !askWin.isDestroyed()) {
askWin.webContents.send('ask-response-stream-error', { error: streamError.message });
}
}
} finally {
this.state.isStreaming = false;

View File

@ -138,20 +138,13 @@ contextBridge.exposeInMainWorld('api', {
removeOnAskStateUpdate: (callback) => ipcRenderer.removeListener('ask:stateUpdate', callback),
// Listeners
onSendQuestionToRenderer: (callback) => ipcRenderer.on('ask:sendQuestionToRenderer', callback),
removeOnSendQuestionToRenderer: (callback) => ipcRenderer.removeListener('ask:sendQuestionToRenderer', callback),
onHideTextInput: (callback) => ipcRenderer.on('hide-text-input', callback),
removeOnHideTextInput: (callback) => ipcRenderer.removeListener('hide-text-input', callback),
onShowTextInput: (callback) => ipcRenderer.on('ask:showTextInput', callback),
removeOnShowTextInput: (callback) => ipcRenderer.removeListener('ask:showTextInput', callback),
onResponseChunk: (callback) => ipcRenderer.on('ask-response-chunk', callback),
removeOnResponseChunk: (callback) => ipcRenderer.removeListener('ask-response-chunk', callback),
onResponseStreamEnd: (callback) => ipcRenderer.on('ask-response-stream-end', callback),
removeOnResponseStreamEnd: (callback) => ipcRenderer.removeListener('ask-response-stream-end', callback),
onScrollResponseUp: (callback) => ipcRenderer.on('scroll-response-up', callback),
removeOnScrollResponseUp: (callback) => ipcRenderer.removeListener('scroll-response-up', callback),
onScrollResponseDown: (callback) => ipcRenderer.on('scroll-response-down', callback),
removeOnScrollResponseDown: (callback) => ipcRenderer.removeListener('scroll-response-down', callback)
onScrollResponseUp: (callback) => ipcRenderer.on('aks:scrollResponseUp', callback),
removeOnScrollResponseUp: (callback) => ipcRenderer.removeListener('aks:scrollResponseUp', callback),
onScrollResponseDown: (callback) => ipcRenderer.on('aks:scrollResponseDown', callback),
removeOnScrollResponseDown: (callback) => ipcRenderer.removeListener('aks:scrollResponseDown', callback)
},
// src/ui/listen/ListenView.js

View File

@ -719,15 +719,13 @@ export class AskView extends LitElement {
this.headerText = 'AI Response';
this.headerAnimating = false;
this.isStreaming = false;
// this.accumulatedResponse = '';
this.marked = null;
this.hljs = null;
this.DOMPurify = null;
this.isLibrariesLoaded = false;
// this.handleStreamChunk = this.handleStreamChunk.bind(this);
// this.handleStreamEnd = this.handleStreamEnd.bind(this);
this.handleSendText = this.handleSendText.bind(this);
this.handleTextKeydown = this.handleTextKeydown.bind(this);
this.handleCopy = this.handleCopy.bind(this);
@ -770,12 +768,6 @@ export class AskView extends LitElement {
};
if (window.api) {
window.api.askView.onSendQuestionToRenderer(this.handleQuestionFromAssistant);
window.api.askView.onHideTextInput(() => {
console.log('📤 Hide text input signal received');
this.showTextInput = false;
this.requestUpdate();
});
window.api.askView.onShowTextInput(() => {
console.log('📤 Show text input signal received');
if (!this.showTextInput) {
@ -784,9 +776,6 @@ export class AskView extends LitElement {
}
});
// window.api.askView.onResponseChunk(this.handleStreamChunk);
// window.api.askView.onResponseStreamEnd(this.handleStreamEnd);
window.api.askView.onScrollResponseUp(() => this.handleScroll('up'));
window.api.askView.onScrollResponseDown(() => this.handleScroll('down'));
window.api.askView.onAskStateUpdate((event, newState) => {
@ -824,11 +813,7 @@ export class AskView extends LitElement {
if (window.api) {
window.api.askView.removeOnAskStateUpdate(this.handleAskStateUpdate);
window.api.askView.removeOnSendQuestionToRenderer(this.handleQuestionFromAssistant);
window.api.askView.removeOnHideTextInput(this.handleHideTextInput);
window.api.askView.removeOnShowTextInput(this.handleShowTextInput);
window.api.askView.removeOnResponseChunk(this.handleStreamChunk);
window.api.askView.removeOnResponseStreamEnd(this.handleStreamEnd);
window.api.askView.removeOnScrollResponseUp(this.handleScroll);
window.api.askView.removeOnScrollResponseDown(this.handleScroll);
console.log('✅ AskView: IPC 이벤트 리스너 제거 필요');
@ -917,9 +902,6 @@ export class AskView extends LitElement {
this.isStreaming = false;
this.headerText = 'AI Response';
this.showTextInput = true;
// this.accumulatedResponse = '';
// this.requestUpdate();
// this.renderContent();
}
handleInputFocus() {
@ -986,33 +968,7 @@ export class AskView extends LitElement {
}
}
// --- 스트리밍 처리 핸들러 ---
// handleStreamChunk(event, { token }) {
// if (!this.isStreaming) {
// this.isStreaming = true;
// this.isLoading = false;
// this.accumulatedResponse = '';
// const container = this.shadowRoot.getElementById('responseContainer');
// if (container) container.innerHTML = '';
// this.headerText = 'AI Response';
// this.headerAnimating = false;
// this.requestUpdate();
// }
// this.accumulatedResponse += token;
// this.renderContent();
// }
// handleStreamEnd() {
// this.isStreaming = false;
// this.currentResponse = this.accumulatedResponse;
// if (this.headerText !== 'AI Response') {
// this.headerText = 'AI Response';
// this.requestUpdate();
// }
// this.renderContent();
// }
// ✨ 렌더링 로직 통합
renderContent() {
const responseContainer = this.shadowRoot.getElementById('responseContainer');
if (!responseContainer) return;
@ -1029,7 +985,6 @@ export class AskView extends LitElement {
return;
}
// ✨ isStreaming이나 accumulatedResponse 대신 currentResponse를 직접 사용
let textToRender = this.fixIncompleteMarkdown(this.currentResponse);
textToRender = this.fixIncompleteCodeBlocks(textToRender);
@ -1269,32 +1224,11 @@ export class AskView extends LitElement {
textInput.value = '';
// this.currentQuestion = text;
// this.lineCopyState = {};
// this.showTextInput = false;
// this.isLoading = true;
// this.isStreaming = false;
// this.currentResponse = '';
// this.accumulatedResponse = '';
// this.startHeaderAnimation();
// this.requestUpdate();
// this.renderContent();
if (window.api) {
window.api.askView.sendMessage(text).catch(error => {
console.error('Error sending text:', error);
});
}
// if (window.api) {
// window.api.askView.sendMessage(text).catch(error => {
// console.error('Error sending text:', error);
// this.isLoading = false;
// this.isStreaming = false;
// this.currentResponse = `Error: ${error.message}`;
// this.renderContent();
// });
// }
}
handleTextKeydown(e) {
@ -1312,21 +1246,6 @@ export class AskView extends LitElement {
}
}
// updated(changedProperties) {
// super.updated(changedProperties);
// if (changedProperties.has('isLoading')) {
// this.renderContent();
// }
// if (changedProperties.has('showTextInput') || changedProperties.has('isLoading')) {
// this.adjustWindowHeightThrottled();
// }
// if (changedProperties.has('showTextInput') && this.showTextInput) {
// this.focusTextInput();
// }
// }
updated(changedProperties) {
super.updated(changedProperties);

View File

@ -897,9 +897,9 @@ function setupIpcHandlers(movementManager) {
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
ipcMain.handle('toggle-feature', async (event, featureName) => {
return toggleFeature(featureName);
});
// ipcMain.handle('toggle-feature', async (event, featureName) => {
// return toggleFeature(featureName);
// });
ipcMain.on('animation-finished', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
@ -916,27 +916,56 @@ function setupIpcHandlers(movementManager) {
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 };
// });
}
/**
*
* @param {'listen'|'ask'|'settings'} featureName
* @param {{
* listen?: { targetVisibility?: 'show'|'hide' },
* ask?: { targetVisibility?: 'show'|'hide', questionText?: string },
* settings?: { targetVisibility?: 'show'|'hide' }
* }} [options={}]
*/
// /**
// *
// * @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'));
// }
// if (featureName === 'ask') {
// let askWindow = windowPool.get('ask');
// if (!askWindow || askWindow.isDestroyed()) {
// console.log('[WindowManager] Ask window not found, creating new one');
// return;
// }
// const questionText = options?.ask?.questionText ?? null;
// const targetVisibility = options?.ask?.targetVisibility ?? null;
// if (askWindow.isVisible()) {
// 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');
// }
// }
// }
async function toggleFeature(featureName, options = {}) {
if (!windowPool.get(featureName) && currentHeaderState === 'main') {
createFeatureWindows(windowPool.get('header'));
@ -949,61 +978,15 @@ async function toggleFeature(featureName, options = {}) {
console.log('[WindowManager] Ask window not found, creating new one');
return;
}
const questionText = options?.ask?.questionText ?? null;
const targetVisibility = options?.ask?.targetVisibility ?? null;
if (askWindow.isVisible()) {
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');
}
}
if (featureName === 'settings') {
const settingsWindow = windowPool.get(featureName);
if (settingsWindow) {
if (settingsWindow.isDestroyed()) {
console.error(`Window ${featureName} is destroyed, cannot toggle`);
return;
}
if (settingsWindow.isVisible()) {
if (featureName === 'settings') {
settingsWindow.webContents.send('settings-window-hide-animation');
} else {
settingsWindow.webContents.send('window-hide-animation');
}
} else {
try {
settingsWindow.show();
updateLayout();
settingsWindow.webContents.send('window-show-animation');
} catch (e) {
console.error('Error showing window:', e);
}
}
} else {
console.error(`Window not found for feature: ${featureName}`);
console.error('Available windows:', Array.from(windowPool.keys()));
}
}
}