transfer roles from askview to askservice
This commit is contained in:
parent
c0edcfb0f9
commit
093f233f5a
2
aec
2
aec
@ -1 +1 @@
|
|||||||
Subproject commit f00bb1fb948053c752b916adfee19f90644a0b2f
|
Subproject commit 9e11f4f95707714464194bdfc9db0222ec5c6163
|
@ -12,25 +12,52 @@ const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
|||||||
class AskService {
|
class AskService {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.abortController = null;
|
this.abortController = null;
|
||||||
|
this.state = {
|
||||||
|
isVisible: false,
|
||||||
|
isLoading: false,
|
||||||
|
isStreaming: false,
|
||||||
|
currentQuestion: '',
|
||||||
|
currentResponse: '',
|
||||||
|
showTextInput: true,
|
||||||
|
};
|
||||||
console.log('[AskService] Service instance created.');
|
console.log('[AskService] Service instance created.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_broadcastState() {
|
||||||
|
const askWindow = windowPool.get('ask');
|
||||||
|
if (askWindow && !askWindow.isDestroyed()) {
|
||||||
|
askWindow.webContents.send('ask:stateUpdate', this.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async toggleAskButton() {
|
async toggleAskButton() {
|
||||||
const { windowPool, updateLayout } = require('../../window/windowManager');
|
const { windowPool, updateLayout } = require('../../window/windowManager');
|
||||||
const askWindow = windowPool.get('ask');
|
const askWindow = windowPool.get('ask');
|
||||||
const header = windowPool.get('header');
|
|
||||||
try {
|
// 답변이 있거나 스트리밍 중일 때
|
||||||
|
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(); // 변경된 상태 전파
|
||||||
|
} else {
|
||||||
|
// 기존의 창 보이기/숨기기 로직
|
||||||
if (askWindow.isVisible()) {
|
if (askWindow.isVisible()) {
|
||||||
askWindow.webContents.send('window-hide-animation');
|
askWindow.webContents.send('window-hide-animation');
|
||||||
|
this.state.isVisible = false;
|
||||||
} else {
|
} else {
|
||||||
console.log('[AskService] Showing hidden Ask window');
|
console.log('[AskService] Showing hidden Ask window');
|
||||||
|
this.state.isVisible = true;
|
||||||
askWindow.show();
|
askWindow.show();
|
||||||
updateLayout();
|
updateLayout();
|
||||||
askWindow.webContents.send('window-show-animation');
|
askWindow.webContents.send('window-show-animation');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
|
||||||
console.error('[AskService] error in toggleAskButton:', error);
|
if (this.state.isVisible) {
|
||||||
throw error;
|
this.state.showTextInput = true;
|
||||||
|
this._broadcastState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +96,16 @@ class AskService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
|
console.log(`[AskService] 🤖 Processing message: ${userPrompt.substring(0, 50)}...`);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
isLoading: true,
|
||||||
|
isStreaming: false,
|
||||||
|
currentQuestion: userPrompt,
|
||||||
|
currentResponse: '',
|
||||||
|
showTextInput: false,
|
||||||
|
};
|
||||||
|
this._broadcastState();
|
||||||
|
|
||||||
sessionId = await sessionRepository.getOrCreateActive('ask');
|
sessionId = await sessionRepository.getOrCreateActive('ask');
|
||||||
await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
|
await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
|
||||||
@ -139,10 +176,9 @@ class AskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.error('[AskService] Error processing message:', error);
|
console.error('[AskService] Error processing message:', error);
|
||||||
const askWin = windowPool.get('ask');
|
this.state.isLoading = false;
|
||||||
if (askWin && !askWin.isDestroyed()) {
|
this.state.error = error.message;
|
||||||
askWin.webContents.send('ask-response-stream-error', { error: error.message });
|
this._broadcastState();
|
||||||
}
|
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +197,9 @@ class AskService {
|
|||||||
let fullResponse = '';
|
let fullResponse = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
this.state.isLoading = false;
|
||||||
|
this.state.isStreaming = true;
|
||||||
|
this._broadcastState();
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
@ -172,9 +211,6 @@ class AskService {
|
|||||||
if (line.startsWith('data: ')) {
|
if (line.startsWith('data: ')) {
|
||||||
const data = line.substring(6);
|
const data = line.substring(6);
|
||||||
if (data === '[DONE]') {
|
if (data === '[DONE]') {
|
||||||
if (askWin && !askWin.isDestroyed()) {
|
|
||||||
askWin.webContents.send('ask-response-stream-end');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -182,9 +218,8 @@ class AskService {
|
|||||||
const token = json.choices[0]?.delta?.content || '';
|
const token = json.choices[0]?.delta?.content || '';
|
||||||
if (token) {
|
if (token) {
|
||||||
fullResponse += token;
|
fullResponse += token;
|
||||||
if (askWin && !askWin.isDestroyed()) {
|
this.state.currentResponse = fullResponse;
|
||||||
askWin.webContents.send('ask-response-chunk', { token });
|
this._broadcastState();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
}
|
}
|
||||||
@ -201,6 +236,9 @@ class AskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
this.state.isStreaming = false;
|
||||||
|
this.state.currentResponse = fullResponse;
|
||||||
|
this._broadcastState();
|
||||||
if (fullResponse) {
|
if (fullResponse) {
|
||||||
try {
|
try {
|
||||||
await askRepository.addAiMessage({ sessionId, role: 'assistant', content: fullResponse });
|
await askRepository.addAiMessage({ sessionId, role: 'assistant', content: fullResponse });
|
||||||
|
@ -132,7 +132,11 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
|
|
||||||
// Message Handling
|
// Message Handling
|
||||||
sendMessage: (text) => ipcRenderer.invoke('ask:sendQuestionFromAsk', text),
|
sendMessage: (text) => ipcRenderer.invoke('ask:sendQuestionFromAsk', text),
|
||||||
|
|
||||||
|
// Listeners
|
||||||
|
onAskStateUpdate: (callback) => ipcRenderer.on('ask:stateUpdate', callback),
|
||||||
|
removeOnAskStateUpdate: (callback) => ipcRenderer.removeListener('ask:stateUpdate', callback),
|
||||||
|
|
||||||
// Listeners
|
// Listeners
|
||||||
onSendQuestionToRenderer: (callback) => ipcRenderer.on('ask:sendQuestionToRenderer', callback),
|
onSendQuestionToRenderer: (callback) => ipcRenderer.on('ask:sendQuestionToRenderer', callback),
|
||||||
removeOnSendQuestionToRenderer: (callback) => ipcRenderer.removeListener('ask:sendQuestionToRenderer', callback),
|
removeOnSendQuestionToRenderer: (callback) => ipcRenderer.removeListener('ask:sendQuestionToRenderer', callback),
|
||||||
|
@ -719,15 +719,15 @@ export class AskView extends LitElement {
|
|||||||
this.headerText = 'AI Response';
|
this.headerText = 'AI Response';
|
||||||
this.headerAnimating = false;
|
this.headerAnimating = false;
|
||||||
this.isStreaming = false;
|
this.isStreaming = false;
|
||||||
this.accumulatedResponse = '';
|
// this.accumulatedResponse = '';
|
||||||
|
|
||||||
this.marked = null;
|
this.marked = null;
|
||||||
this.hljs = null;
|
this.hljs = null;
|
||||||
this.DOMPurify = null;
|
this.DOMPurify = null;
|
||||||
this.isLibrariesLoaded = false;
|
this.isLibrariesLoaded = false;
|
||||||
|
|
||||||
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.handleTextKeydown = this.handleTextKeydown.bind(this);
|
this.handleTextKeydown = this.handleTextKeydown.bind(this);
|
||||||
this.handleCopy = this.handleCopy.bind(this);
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
@ -784,11 +784,18 @@ export class AskView extends LitElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.api.askView.onResponseChunk(this.handleStreamChunk);
|
// window.api.askView.onResponseChunk(this.handleStreamChunk);
|
||||||
window.api.askView.onResponseStreamEnd(this.handleStreamEnd);
|
// window.api.askView.onResponseStreamEnd(this.handleStreamEnd);
|
||||||
|
|
||||||
window.api.askView.onScrollResponseUp(() => this.handleScroll('up'));
|
window.api.askView.onScrollResponseUp(() => this.handleScroll('up'));
|
||||||
window.api.askView.onScrollResponseDown(() => this.handleScroll('down'));
|
window.api.askView.onScrollResponseDown(() => this.handleScroll('down'));
|
||||||
|
window.api.askView.onAskStateUpdate((event, newState) => {
|
||||||
|
this.currentResponse = newState.currentResponse;
|
||||||
|
this.currentQuestion = newState.currentQuestion;
|
||||||
|
this.isLoading = newState.isLoading;
|
||||||
|
this.isStreaming = newState.isStreaming;
|
||||||
|
this.showTextInput = newState.showTextInput;
|
||||||
|
});
|
||||||
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
|
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -816,9 +823,14 @@ export class AskView extends LitElement {
|
|||||||
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
|
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
|
||||||
|
|
||||||
if (window.api) {
|
if (window.api) {
|
||||||
// Note: We need to keep references to the actual callbacks used in connectedCallback
|
window.api.askView.removeOnAskStateUpdate(this.handleAskStateUpdate);
|
||||||
// For now, we'll just log that removal is needed
|
window.api.askView.removeOnSendQuestionToRenderer(this.handleQuestionFromAssistant);
|
||||||
// TODO: Store callback references for proper removal
|
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 이벤트 리스너 제거 필요');
|
console.log('✅ AskView: IPC 이벤트 리스너 제거 필요');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -905,9 +917,9 @@ export class AskView extends LitElement {
|
|||||||
this.isStreaming = false;
|
this.isStreaming = false;
|
||||||
this.headerText = 'AI Response';
|
this.headerText = 'AI Response';
|
||||||
this.showTextInput = true;
|
this.showTextInput = true;
|
||||||
this.accumulatedResponse = '';
|
// this.accumulatedResponse = '';
|
||||||
this.requestUpdate();
|
// this.requestUpdate();
|
||||||
this.renderContent();
|
// this.renderContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputFocus() {
|
handleInputFocus() {
|
||||||
@ -975,55 +987,52 @@ export class AskView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 스트리밍 처리 핸들러 ---
|
// --- 스트리밍 처리 핸들러 ---
|
||||||
handleStreamChunk(event, { token }) {
|
// handleStreamChunk(event, { token }) {
|
||||||
if (!this.isStreaming) {
|
// if (!this.isStreaming) {
|
||||||
this.isStreaming = true;
|
// this.isStreaming = true;
|
||||||
this.isLoading = false;
|
// this.isLoading = false;
|
||||||
this.accumulatedResponse = '';
|
// this.accumulatedResponse = '';
|
||||||
const container = this.shadowRoot.getElementById('responseContainer');
|
// const container = this.shadowRoot.getElementById('responseContainer');
|
||||||
if (container) container.innerHTML = '';
|
// if (container) container.innerHTML = '';
|
||||||
this.headerText = 'AI Response';
|
// this.headerText = 'AI Response';
|
||||||
this.headerAnimating = false;
|
// this.headerAnimating = false;
|
||||||
this.requestUpdate();
|
// this.requestUpdate();
|
||||||
}
|
// }
|
||||||
this.accumulatedResponse += token;
|
// this.accumulatedResponse += token;
|
||||||
this.renderContent();
|
// this.renderContent();
|
||||||
}
|
// }
|
||||||
|
|
||||||
handleStreamEnd() {
|
// handleStreamEnd() {
|
||||||
this.isStreaming = false;
|
// this.isStreaming = false;
|
||||||
this.currentResponse = this.accumulatedResponse;
|
// this.currentResponse = this.accumulatedResponse;
|
||||||
if (this.headerText !== 'AI Response') {
|
// if (this.headerText !== 'AI Response') {
|
||||||
this.headerText = 'AI Response';
|
// this.headerText = 'AI Response';
|
||||||
this.requestUpdate();
|
// this.requestUpdate();
|
||||||
}
|
// }
|
||||||
this.renderContent();
|
// this.renderContent();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ✨ 렌더링 로직 통합
|
// ✨ 렌더링 로직 통합
|
||||||
renderContent() {
|
renderContent() {
|
||||||
if (!this.isLoading && !this.isStreaming && !this.currentResponse) {
|
|
||||||
const responseContainer = this.shadowRoot.getElementById('responseContainer');
|
|
||||||
if (responseContainer) responseContainer.innerHTML = '<div class="empty-state">Ask a question to see the response here</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseContainer = this.shadowRoot.getElementById('responseContainer');
|
const responseContainer = this.shadowRoot.getElementById('responseContainer');
|
||||||
if (!responseContainer) return;
|
if (!responseContainer) return;
|
||||||
|
|
||||||
|
// ✨ 로딩 상태를 먼저 확인
|
||||||
if (this.isLoading) {
|
if (this.isLoading) {
|
||||||
responseContainer.innerHTML = `
|
responseContainer.innerHTML = `<div class="loading-dots">...</div>`;
|
||||||
<div class="loading-dots">
|
|
||||||
<div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div>
|
|
||||||
</div>`;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let textToRender = this.isStreaming ? this.accumulatedResponse : this.currentResponse;
|
// ✨ 응답이 없을 때의 처리
|
||||||
|
if (!this.currentResponse) {
|
||||||
// 불완전한 마크다운 수정
|
responseContainer.innerHTML = `<div class="empty-state">...</div>`;
|
||||||
textToRender = this.fixIncompleteMarkdown(textToRender);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✨ isStreaming이나 accumulatedResponse 대신 currentResponse를 직접 사용
|
||||||
|
let textToRender = this.fixIncompleteMarkdown(this.currentResponse);
|
||||||
textToRender = this.fixIncompleteCodeBlocks(textToRender);
|
textToRender = this.fixIncompleteCodeBlocks(textToRender);
|
||||||
|
|
||||||
|
|
||||||
if (this.isLibrariesLoaded && this.marked && this.DOMPurify) {
|
if (this.isLibrariesLoaded && this.marked && this.DOMPurify) {
|
||||||
try {
|
try {
|
||||||
@ -1260,26 +1269,32 @@ export class AskView extends LitElement {
|
|||||||
|
|
||||||
textInput.value = '';
|
textInput.value = '';
|
||||||
|
|
||||||
this.currentQuestion = text;
|
// this.currentQuestion = text;
|
||||||
this.lineCopyState = {};
|
// this.lineCopyState = {};
|
||||||
this.showTextInput = false;
|
// this.showTextInput = false;
|
||||||
this.isLoading = true;
|
// this.isLoading = true;
|
||||||
this.isStreaming = false;
|
// this.isStreaming = false;
|
||||||
this.currentResponse = '';
|
// this.currentResponse = '';
|
||||||
this.accumulatedResponse = '';
|
// this.accumulatedResponse = '';
|
||||||
this.startHeaderAnimation();
|
// this.startHeaderAnimation();
|
||||||
this.requestUpdate();
|
// this.requestUpdate();
|
||||||
this.renderContent();
|
// this.renderContent();
|
||||||
|
|
||||||
if (window.api) {
|
if (window.api) {
|
||||||
window.api.askView.sendMessage(text).catch(error => {
|
window.api.askView.sendMessage(text).catch(error => {
|
||||||
console.error('Error sending text:', error);
|
console.error('Error sending text:', error);
|
||||||
this.isLoading = false;
|
|
||||||
this.isStreaming = false;
|
|
||||||
this.currentResponse = `Error: ${error.message}`;
|
|
||||||
this.renderContent();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
handleTextKeydown(e) {
|
||||||
@ -1297,16 +1312,33 @@ 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) {
|
updated(changedProperties) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('isLoading')) {
|
|
||||||
|
// ✨ isLoading 또는 currentResponse가 변경될 때마다 뷰를 다시 그립니다.
|
||||||
|
if (changedProperties.has('isLoading') || changedProperties.has('currentResponse')) {
|
||||||
this.renderContent();
|
this.renderContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('showTextInput') || changedProperties.has('isLoading')) {
|
if (changedProperties.has('showTextInput') || changedProperties.has('isLoading') || changedProperties.has('currentResponse')) {
|
||||||
this.adjustWindowHeightThrottled();
|
this.adjustWindowHeightThrottled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('showTextInput') && this.showTextInput) {
|
if (changedProperties.has('showTextInput') && this.showTextInput) {
|
||||||
this.focusTextInput();
|
this.focusTextInput();
|
||||||
}
|
}
|
||||||
@ -1327,6 +1359,7 @@ export class AskView extends LitElement {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
||||||
|
const headerText = this.isLoading ? 'Thinking...' : 'AI Response';
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="ask-container">
|
<div class="ask-container">
|
||||||
@ -1339,7 +1372,7 @@ export class AskView extends LitElement {
|
|||||||
<path d="M8 12l2 2 4-4" />
|
<path d="M8 12l2 2 4-4" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="response-label ${this.headerAnimating ? 'animating' : ''}">${this.headerText}</span>
|
<span class="response-label">${headerText}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<span class="question-text">${this.getTruncatedQuestion(this.currentQuestion)}</span>
|
<span class="question-text">${this.getTruncatedQuestion(this.currentQuestion)}</span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user