centralized ask logic
This commit is contained in:
parent
8402e7d296
commit
c948d4ed08
@ -23,11 +23,6 @@ async function sendMessage(userPrompt) {
|
|||||||
return { success: false, error: '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;
|
let sessionId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -193,8 +193,6 @@ class ListenService {
|
|||||||
this.currentSessionId = null;
|
this.currentSessionId = null;
|
||||||
this.summaryService.resetConversationHistory();
|
this.summaryService.resetConversationHistory();
|
||||||
|
|
||||||
this.sendToRenderer('session-did-close');
|
|
||||||
|
|
||||||
console.log('Listen service session closed.');
|
console.log('Listen service session closed.');
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -729,17 +729,13 @@ export class AskView extends LitElement {
|
|||||||
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
this.handleStreamChunk = this.handleStreamChunk.bind(this);
|
||||||
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
this.handleStreamEnd = this.handleStreamEnd.bind(this);
|
||||||
this.handleSendText = this.handleSendText.bind(this);
|
this.handleSendText = this.handleSendText.bind(this);
|
||||||
this.handleGlobalSendRequest = this.handleGlobalSendRequest.bind(this);
|
|
||||||
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
||||||
this.closeResponsePanel = this.closeResponsePanel.bind(this);
|
|
||||||
this.handleCopy = this.handleCopy.bind(this);
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
this.clearResponseContent = this.clearResponseContent.bind(this);
|
this.clearResponseContent = this.clearResponseContent.bind(this);
|
||||||
this.processAssistantQuestion = this.processAssistantQuestion.bind(this);
|
|
||||||
this.handleEscKey = this.handleEscKey.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.handleScroll = this.handleScroll.bind(this);
|
||||||
|
this.handleCloseAskWindow = this.handleCloseAskWindow.bind(this);
|
||||||
|
this.handleCloseIfNoContent = this.handleCloseIfNoContent.bind(this);
|
||||||
|
|
||||||
this.loadLibraries();
|
this.loadLibraries();
|
||||||
|
|
||||||
@ -747,6 +743,94 @@ export class AskView extends LitElement {
|
|||||||
this.isThrottled = false;
|
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() {
|
async loadLibraries() {
|
||||||
try {
|
try {
|
||||||
if (!window.marked) {
|
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) {
|
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
||||||
const askContainer = this.shadowRoot?.querySelector('.ask-container');
|
this.handleCloseAskWindow();
|
||||||
if (askContainer && !e.composedPath().includes(askContainer)) {
|
|
||||||
this.closeIfNoContent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEscKey(e) {
|
handleEscKey(e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.closeResponsePanel();
|
this.handleCloseIfNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowBlur() {
|
clearResponseContent() {
|
||||||
if (!this.currentResponse && !this.isLoading && !this.isStreaming) {
|
this.currentResponse = '';
|
||||||
// If there's no active content, ask the main process to close this window.
|
this.currentQuestion = '';
|
||||||
if (window.require) {
|
this.isLoading = false;
|
||||||
const { ipcRenderer } = window.require('electron');
|
this.isStreaming = false;
|
||||||
ipcRenderer.invoke('close-ask-window-if-empty');
|
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) {
|
loadScript(src) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -874,114 +969,6 @@ export class AskView extends LitElement {
|
|||||||
return text;
|
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) {
|
handleScroll(direction) {
|
||||||
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
|
const scrollableElement = this.shadowRoot.querySelector('#responseContainer');
|
||||||
if (scrollableElement) {
|
if (scrollableElement) {
|
||||||
@ -1124,18 +1111,6 @@ export class AskView extends LitElement {
|
|||||||
this.adjustWindowHeightThrottled();
|
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) {
|
requestWindowResize(targetHeight) {
|
||||||
if (window.require) {
|
if (window.require) {
|
||||||
@ -1180,13 +1155,6 @@ export class AskView extends LitElement {
|
|||||||
.replace(/`(.*?)`/g, '<code>$1</code>');
|
.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||||
}
|
}
|
||||||
|
|
||||||
closeResponsePanel() {
|
|
||||||
if (window.require) {
|
|
||||||
const { ipcRenderer } = window.require('electron');
|
|
||||||
ipcRenderer.invoke('force-close-window', 'ask');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fixIncompleteMarkdown(text) {
|
fixIncompleteMarkdown(text) {
|
||||||
if (!text) return text;
|
if (!text) return text;
|
||||||
|
|
||||||
@ -1224,29 +1192,6 @@ export class AskView extends LitElement {
|
|||||||
return text;
|
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() {
|
async handleCopy() {
|
||||||
if (this.copyState === 'copied') return;
|
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');
|
const textInput = this.shadowRoot?.getElementById('textInput');
|
||||||
if (!textInput) return;
|
const text = (overridingText || textInput?.value || '').trim();
|
||||||
const text = textInput.value.trim();
|
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
textInput.value = '';
|
textInput.value = '';
|
||||||
@ -1377,37 +1321,10 @@ export class AskView extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focusTextInput() {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const textInput = this.shadowRoot?.getElementById('textInput');
|
|
||||||
if (textInput) {
|
|
||||||
textInput.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
setTimeout(() => this.adjustWindowHeight(), 200);
|
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) {
|
getTruncatedQuestion(question, maxLength = 30) {
|
||||||
if (!question) return '';
|
if (!question) return '';
|
||||||
@ -1415,24 +1332,7 @@ export class AskView extends LitElement {
|
|||||||
return question.substring(0, maxLength) + '...';
|
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() {
|
render() {
|
||||||
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
||||||
@ -1470,7 +1370,7 @@ export class AskView extends LitElement {
|
|||||||
<path d="M20 6L9 17l-5-5" />
|
<path d="M20 6L9 17l-5-5" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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">
|
<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="18" y1="6" x2="6" y2="18" />
|
||||||
<line x1="6" y1="6" x2="18" 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"
|
placeholder="Ask about your screen or audio"
|
||||||
@keydown=${this.handleTextKeydown}
|
@keydown=${this.handleTextKeydown}
|
||||||
@focus=${this.handleInputFocus}
|
@focus=${this.handleInputFocus}
|
||||||
@blur=${this.handleInputBlur}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="submit-btn"
|
class="submit-btn"
|
||||||
|
@ -412,14 +412,7 @@ export class SummaryView extends LitElement {
|
|||||||
const { ipcRenderer } = window.require('electron');
|
const { ipcRenderer } = window.require('electron');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isAskViewVisible = await ipcRenderer.invoke('is-ask-window-visible', 'ask');
|
const result = await ipcRenderer.invoke('ask:sendQuestionToMain', requestText);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('✅ Question sent to AskView successfully');
|
console.log('✅ Question sent to AskView successfully');
|
||||||
|
@ -76,8 +76,16 @@ function updateLayout() {
|
|||||||
let movementManager = null;
|
let movementManager = null;
|
||||||
const windowBridge = require('../bridge/windowBridge');
|
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') {
|
if (!windowPool.get(featureName) && currentHeaderState === 'main') {
|
||||||
createFeatureWindows(windowPool.get('header'));
|
createFeatureWindows(windowPool.get('header'));
|
||||||
}
|
}
|
||||||
@ -117,14 +125,27 @@ async function toggleFeature(featureName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const questionText = options?.ask?.questionText ?? null;
|
||||||
|
const targetVisibility = options?.ask?.targetVisibility ?? null;
|
||||||
if (askWindow.isVisible()) {
|
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 {
|
} else {
|
||||||
console.log('[WindowManager] Showing hidden Ask window');
|
console.log('[WindowManager] Showing hidden Ask window');
|
||||||
askWindow.show();
|
askWindow.show();
|
||||||
updateLayout();
|
updateLayout();
|
||||||
|
if (questionText) {
|
||||||
|
askWindow.webContents.send('ask:sendQuestionToRenderer', questionText);
|
||||||
|
}
|
||||||
askWindow.webContents.send('window-show-animation');
|
askWindow.webContents.send('window-show-animation');
|
||||||
askWindow.webContents.send('window-did-show');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,8 +260,6 @@ function createFeatureWindows(header, namesToCreate) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ask.on('blur',()=>ask.webContents.send('window-blur'));
|
|
||||||
|
|
||||||
// Open DevTools in development
|
// Open DevTools in development
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
ask.webContents.openDevTools({ mode: 'detach' });
|
ask.webContents.openDevTools({ mode: 'detach' });
|
||||||
@ -532,61 +551,6 @@ function createWindows() {
|
|||||||
updateLayout();
|
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;
|
return windowPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -617,6 +581,12 @@ function loadAndRegisterShortcuts(movementManager) {
|
|||||||
|
|
||||||
|
|
||||||
function setupIpcHandlers(movementManager) {
|
function setupIpcHandlers(movementManager) {
|
||||||
|
setupApiKeyIPC();
|
||||||
|
|
||||||
|
ipcMain.handle('quit-application', () => {
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
screen.on('display-added', (event, newDisplay) => {
|
screen.on('display-added', (event, newDisplay) => {
|
||||||
console.log('[Display] New display added:', newDisplay.id);
|
console.log('[Display] New display added:', newDisplay.id);
|
||||||
});
|
});
|
||||||
@ -631,107 +601,10 @@ function setupIpcHandlers(movementManager) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
|
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();
|
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', () => {
|
ipcMain.handle('toggle-content-protection', () => {
|
||||||
isContentProtectionOn = !isContentProtectionOn;
|
isContentProtectionOn = !isContentProtectionOn;
|
||||||
console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
|
console.log(`[Protection] Content protection toggled to: ${isContentProtectionOn}`);
|
||||||
@ -827,8 +700,6 @@ function setupIpcHandlers(movementManager) {
|
|||||||
console.log('Opening personalization page:', personalizeUrl);
|
console.log('Opening personalization page:', personalizeUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
setupApiKeyIPC();
|
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle('resize-header-window', (event, { width, height }) => {
|
ipcMain.handle('resize-header-window', (event, { width, height }) => {
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
@ -952,12 +823,32 @@ function setupIpcHandlers(movementManager) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('force-close-window', (event, windowName) => {
|
ipcMain.handle('adjust-window-height', (event, targetHeight) => {
|
||||||
const window = windowPool.get(windowName);
|
const senderWindow = BrowserWindow.fromWebContents(event.sender);
|
||||||
if (window && !window.isDestroyed()) {
|
if (senderWindow) {
|
||||||
console.log(`[WindowManager] Force closing window: ${windowName}`);
|
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1141,12 +1032,94 @@ function setupIpcHandlers(movementManager) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('close-ask-window-if-empty', async () => {
|
ipcMain.handle('toggle-all-windows-visibility', () => toggleAllWindowsVisibility());
|
||||||
const askWindow = windowPool.get('ask');
|
|
||||||
if (askWindow && !askWindow.isFocused()) {
|
ipcMain.handle('toggle-feature', async (event, featureName) => {
|
||||||
askWindow.hide();
|
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();
|
callback = () => toggleAllWindowsVisibility();
|
||||||
break;
|
break;
|
||||||
case 'nextStep':
|
case 'nextStep':
|
||||||
callback = () => toggleFeature('ask');
|
callback = () => toggleFeature('ask', {ask: { targetVisibility: 'show' }});
|
||||||
break;
|
break;
|
||||||
case 'scrollUp':
|
case 'scrollUp':
|
||||||
callback = () => {
|
callback = () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user