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 {
|
||||
constructor() {
|
||||
this.abortController = null;
|
||||
this.state = {
|
||||
isVisible: false,
|
||||
isLoading: false,
|
||||
isStreaming: false,
|
||||
currentQuestion: '',
|
||||
currentResponse: '',
|
||||
showTextInput: true,
|
||||
};
|
||||
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() {
|
||||
const { windowPool, updateLayout } = require('../../window/windowManager');
|
||||
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()) {
|
||||
askWindow.webContents.send('window-hide-animation');
|
||||
this.state.isVisible = false;
|
||||
} else {
|
||||
console.log('[AskService] Showing hidden Ask window');
|
||||
this.state.isVisible = true;
|
||||
askWindow.show();
|
||||
updateLayout();
|
||||
askWindow.webContents.send('window-show-animation');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AskService] error in toggleAskButton:', error);
|
||||
throw error;
|
||||
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
|
||||
if (this.state.isVisible) {
|
||||
this.state.showTextInput = true;
|
||||
this._broadcastState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +97,16 @@ class AskService {
|
||||
try {
|
||||
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');
|
||||
await askRepository.addAiMessage({ sessionId, role: 'user', content: userPrompt.trim() });
|
||||
console.log(`[AskService] DB: Saved user prompt to session ${sessionId}`);
|
||||
@ -139,10 +176,9 @@ class AskService {
|
||||
}
|
||||
|
||||
console.error('[AskService] Error processing message:', error);
|
||||
const askWin = windowPool.get('ask');
|
||||
if (askWin && !askWin.isDestroyed()) {
|
||||
askWin.webContents.send('ask-response-stream-error', { error: error.message });
|
||||
}
|
||||
this.state.isLoading = false;
|
||||
this.state.error = error.message;
|
||||
this._broadcastState();
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
@ -161,6 +197,9 @@ class AskService {
|
||||
let fullResponse = '';
|
||||
|
||||
try {
|
||||
this.state.isLoading = false;
|
||||
this.state.isStreaming = true;
|
||||
this._broadcastState();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
@ -172,9 +211,6 @@ class AskService {
|
||||
if (line.startsWith('data: ')) {
|
||||
const data = line.substring(6);
|
||||
if (data === '[DONE]') {
|
||||
if (askWin && !askWin.isDestroyed()) {
|
||||
askWin.webContents.send('ask-response-stream-end');
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@ -182,9 +218,8 @@ class AskService {
|
||||
const token = json.choices[0]?.delta?.content || '';
|
||||
if (token) {
|
||||
fullResponse += token;
|
||||
if (askWin && !askWin.isDestroyed()) {
|
||||
askWin.webContents.send('ask-response-chunk', { token });
|
||||
}
|
||||
this.state.currentResponse = fullResponse;
|
||||
this._broadcastState();
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
@ -201,6 +236,9 @@ class AskService {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.state.isStreaming = false;
|
||||
this.state.currentResponse = fullResponse;
|
||||
this._broadcastState();
|
||||
if (fullResponse) {
|
||||
try {
|
||||
await askRepository.addAiMessage({ sessionId, role: 'assistant', content: fullResponse });
|
||||
|
@ -133,6 +133,10 @@ contextBridge.exposeInMainWorld('api', {
|
||||
// Message Handling
|
||||
sendMessage: (text) => ipcRenderer.invoke('ask:sendQuestionFromAsk', text),
|
||||
|
||||
// Listeners
|
||||
onAskStateUpdate: (callback) => ipcRenderer.on('ask:stateUpdate', callback),
|
||||
removeOnAskStateUpdate: (callback) => ipcRenderer.removeListener('ask:stateUpdate', callback),
|
||||
|
||||
// Listeners
|
||||
onSendQuestionToRenderer: (callback) => ipcRenderer.on('ask:sendQuestionToRenderer', callback),
|
||||
removeOnSendQuestionToRenderer: (callback) => ipcRenderer.removeListener('ask:sendQuestionToRenderer', callback),
|
||||
|
@ -719,15 +719,15 @@ export class AskView extends LitElement {
|
||||
this.headerText = 'AI Response';
|
||||
this.headerAnimating = false;
|
||||
this.isStreaming = false;
|
||||
this.accumulatedResponse = '';
|
||||
// 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.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);
|
||||
@ -784,11 +784,18 @@ export class AskView extends LitElement {
|
||||
}
|
||||
});
|
||||
|
||||
window.api.askView.onResponseChunk(this.handleStreamChunk);
|
||||
window.api.askView.onResponseStreamEnd(this.handleStreamEnd);
|
||||
// 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) => {
|
||||
this.currentResponse = newState.currentResponse;
|
||||
this.currentQuestion = newState.currentQuestion;
|
||||
this.isLoading = newState.isLoading;
|
||||
this.isStreaming = newState.isStreaming;
|
||||
this.showTextInput = newState.showTextInput;
|
||||
});
|
||||
console.log('✅ AskView: IPC 이벤트 리스너 등록 완료');
|
||||
}
|
||||
}
|
||||
@ -816,9 +823,14 @@ export class AskView extends LitElement {
|
||||
Object.values(this.lineCopyTimeouts).forEach(timeout => clearTimeout(timeout));
|
||||
|
||||
if (window.api) {
|
||||
// Note: We need to keep references to the actual callbacks used in connectedCallback
|
||||
// For now, we'll just log that removal is needed
|
||||
// TODO: Store callback references for proper removal
|
||||
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 이벤트 리스너 제거 필요');
|
||||
}
|
||||
}
|
||||
@ -905,9 +917,9 @@ export class AskView extends LitElement {
|
||||
this.isStreaming = false;
|
||||
this.headerText = 'AI Response';
|
||||
this.showTextInput = true;
|
||||
this.accumulatedResponse = '';
|
||||
this.requestUpdate();
|
||||
this.renderContent();
|
||||
// this.accumulatedResponse = '';
|
||||
// this.requestUpdate();
|
||||
// this.renderContent();
|
||||
}
|
||||
|
||||
handleInputFocus() {
|
||||
@ -975,56 +987,53 @@ 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();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
// handleStreamEnd() {
|
||||
// this.isStreaming = false;
|
||||
// this.currentResponse = this.accumulatedResponse;
|
||||
// if (this.headerText !== 'AI Response') {
|
||||
// this.headerText = 'AI Response';
|
||||
// this.requestUpdate();
|
||||
// }
|
||||
// this.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');
|
||||
if (!responseContainer) return;
|
||||
|
||||
// ✨ 로딩 상태를 먼저 확인
|
||||
if (this.isLoading) {
|
||||
responseContainer.innerHTML = `
|
||||
<div class="loading-dots">
|
||||
<div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div>
|
||||
</div>`;
|
||||
responseContainer.innerHTML = `<div class="loading-dots">...</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let textToRender = this.isStreaming ? this.accumulatedResponse : this.currentResponse;
|
||||
// ✨ 응답이 없을 때의 처리
|
||||
if (!this.currentResponse) {
|
||||
responseContainer.innerHTML = `<div class="empty-state">...</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// 불완전한 마크다운 수정
|
||||
textToRender = this.fixIncompleteMarkdown(textToRender);
|
||||
// ✨ isStreaming이나 accumulatedResponse 대신 currentResponse를 직접 사용
|
||||
let textToRender = this.fixIncompleteMarkdown(this.currentResponse);
|
||||
textToRender = this.fixIncompleteCodeBlocks(textToRender);
|
||||
|
||||
|
||||
if (this.isLibrariesLoaded && this.marked && this.DOMPurify) {
|
||||
try {
|
||||
// 마크다운 파싱
|
||||
@ -1260,26 +1269,32 @@ 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();
|
||||
// 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);
|
||||
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) {
|
||||
@ -1297,13 +1312,30 @@ 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);
|
||||
if (changedProperties.has('isLoading')) {
|
||||
|
||||
// ✨ isLoading 또는 currentResponse가 변경될 때마다 뷰를 다시 그립니다.
|
||||
if (changedProperties.has('isLoading') || changedProperties.has('currentResponse')) {
|
||||
this.renderContent();
|
||||
}
|
||||
|
||||
if (changedProperties.has('showTextInput') || changedProperties.has('isLoading')) {
|
||||
if (changedProperties.has('showTextInput') || changedProperties.has('isLoading') || changedProperties.has('currentResponse')) {
|
||||
this.adjustWindowHeightThrottled();
|
||||
}
|
||||
|
||||
@ -1327,6 +1359,7 @@ export class AskView extends LitElement {
|
||||
|
||||
render() {
|
||||
const hasResponse = this.isLoading || this.currentResponse || this.isStreaming;
|
||||
const headerText = this.isLoading ? 'Thinking...' : 'AI Response';
|
||||
|
||||
return html`
|
||||
<div class="ask-container">
|
||||
@ -1339,7 +1372,7 @@ export class AskView extends LitElement {
|
||||
<path d="M8 12l2 2 4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="response-label ${this.headerAnimating ? 'animating' : ''}">${this.headerText}</span>
|
||||
<span class="response-label">${headerText}</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="question-text">${this.getTruncatedQuestion(this.currentQuestion)}</span>
|
||||
|
Loading…
x
Reference in New Issue
Block a user