screenshot moved from windowManager
This commit is contained in:
parent
9ec8df0548
commit
5c2f9c1eb7
@ -67,6 +67,7 @@ module.exports = {
|
|||||||
ipcMain.handle('ask:sendQuestionFromAsk', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
ipcMain.handle('ask:sendQuestionFromAsk', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
||||||
ipcMain.handle('ask:sendQuestionFromSummary', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
ipcMain.handle('ask:sendQuestionFromSummary', async (event, userPrompt) => await askService.sendMessage(userPrompt));
|
||||||
ipcMain.handle('ask:toggleAskButton', async () => await askService.toggleAskButton());
|
ipcMain.handle('ask:toggleAskButton', async () => await askService.toggleAskButton());
|
||||||
|
ipcMain.handle('stop-screen-capture', async () => askService.handleStopScreenCapture());
|
||||||
|
|
||||||
// Listen
|
// Listen
|
||||||
ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => await listenService.handleSendAudioContent(data, mimeType));
|
ipcMain.handle('send-audio-content', async (event, { data, mimeType }) => await listenService.handleSendAudioContent(data, mimeType));
|
||||||
|
@ -1,10 +1,111 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
const { BrowserWindow } = require('electron');
|
||||||
const { createStreamingLLM } = require('../common/ai/factory');
|
const { createStreamingLLM } = require('../common/ai/factory');
|
||||||
const { getCurrentModelInfo, windowPool, captureScreenshot } = require('../../window/windowManager');
|
const { getCurrentModelInfo, windowPool, updateLayout } = require('../../window/windowManager');
|
||||||
const sessionRepository = require('../common/repositories/session');
|
const sessionRepository = require('../common/repositories/session');
|
||||||
const askRepository = require('./repositories');
|
const askRepository = require('./repositories');
|
||||||
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
const { getSystemPrompt } = require('../common/prompts/promptBuilder');
|
||||||
const listenService = require('../listen/listenService');
|
const path = require('node:path');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const os = require('os');
|
||||||
|
const util = require('util');
|
||||||
|
const execFile = util.promisify(require('child_process').execFile);
|
||||||
|
const { desktopCapturer } = require('electron');
|
||||||
|
|
||||||
|
// Try to load sharp, but don't fail if it's not available
|
||||||
|
let sharp;
|
||||||
|
try {
|
||||||
|
sharp = require('sharp');
|
||||||
|
console.log('[AskService] Sharp module loaded successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[AskService] Sharp module not available:', error.message);
|
||||||
|
console.warn('[AskService] Screenshot functionality will work with reduced image processing capabilities');
|
||||||
|
sharp = null;
|
||||||
|
}
|
||||||
|
let lastScreenshot = null;
|
||||||
|
|
||||||
|
async function captureScreenshot(options = {}) {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
try {
|
||||||
|
const tempPath = path.join(os.tmpdir(), `screenshot-${Date.now()}.jpg`);
|
||||||
|
|
||||||
|
await execFile('screencapture', ['-x', '-t', 'jpg', tempPath]);
|
||||||
|
|
||||||
|
const imageBuffer = await fs.promises.readFile(tempPath);
|
||||||
|
await fs.promises.unlink(tempPath);
|
||||||
|
|
||||||
|
if (sharp) {
|
||||||
|
try {
|
||||||
|
// Try using sharp for optimal image processing
|
||||||
|
const resizedBuffer = await sharp(imageBuffer)
|
||||||
|
.resize({ height: 384 })
|
||||||
|
.jpeg({ quality: 80 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const base64 = resizedBuffer.toString('base64');
|
||||||
|
const metadata = await sharp(resizedBuffer).metadata();
|
||||||
|
|
||||||
|
lastScreenshot = {
|
||||||
|
base64,
|
||||||
|
width: metadata.width,
|
||||||
|
height: metadata.height,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { success: true, base64, width: metadata.width, height: metadata.height };
|
||||||
|
} catch (sharpError) {
|
||||||
|
console.warn('Sharp module failed, falling back to basic image processing:', sharpError.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Return the original image without resizing
|
||||||
|
console.log('[AskService] Using fallback image processing (no resize/compression)');
|
||||||
|
const base64 = imageBuffer.toString('base64');
|
||||||
|
|
||||||
|
lastScreenshot = {
|
||||||
|
base64,
|
||||||
|
width: null, // We don't have metadata without sharp
|
||||||
|
height: null,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { success: true, base64, width: null, height: null };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to capture screenshot:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sources = await desktopCapturer.getSources({
|
||||||
|
types: ['screen'],
|
||||||
|
thumbnailSize: {
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sources.length === 0) {
|
||||||
|
throw new Error('No screen sources available');
|
||||||
|
}
|
||||||
|
const source = sources[0];
|
||||||
|
const buffer = source.thumbnail.toJPEG(70);
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
const size = source.thumbnail.getSize();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
base64,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to capture screenshot using desktopCapturer:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
@ -32,15 +133,17 @@ class AskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async toggleAskButton() {
|
async toggleAskButton() {
|
||||||
const { windowPool, updateLayout } = require('../../window/windowManager');
|
|
||||||
const askWindow = windowPool.get('ask');
|
const askWindow = windowPool.get('ask');
|
||||||
|
|
||||||
|
// 답변이 있거나 스트리밍 중일 때
|
||||||
const hasContent = this.state.isStreaming || (this.state.currentResponse && this.state.currentResponse.length > 0);
|
const hasContent = this.state.isStreaming || (this.state.currentResponse && this.state.currentResponse.length > 0);
|
||||||
|
|
||||||
if (askWindow.isVisible() && hasContent) {
|
if (askWindow.isVisible() && hasContent) {
|
||||||
|
// 창을 닫는 대신, 텍스트 입력창만 토글합니다.
|
||||||
this.state.showTextInput = !this.state.showTextInput;
|
this.state.showTextInput = !this.state.showTextInput;
|
||||||
this._broadcastState();
|
this._broadcastState(); // 변경된 상태 전파
|
||||||
} else {
|
} else {
|
||||||
|
// 기존의 창 보이기/숨기기 로직
|
||||||
if (askWindow.isVisible()) {
|
if (askWindow.isVisible()) {
|
||||||
askWindow.webContents.send('window-hide-animation');
|
askWindow.webContents.send('window-hide-animation');
|
||||||
this.state.isVisible = false;
|
this.state.isVisible = false;
|
||||||
@ -51,6 +154,7 @@ class AskService {
|
|||||||
updateLayout();
|
updateLayout();
|
||||||
askWindow.webContents.send('window-show-animation');
|
askWindow.webContents.send('window-show-animation');
|
||||||
}
|
}
|
||||||
|
// 창이 다시 열릴 때를 대비해 상태를 초기화하고 전파합니다.
|
||||||
if (this.state.isVisible) {
|
if (this.state.isVisible) {
|
||||||
this.state.showTextInput = true;
|
this.state.showTextInput = true;
|
||||||
this._broadcastState();
|
this._broadcastState();
|
||||||
@ -77,7 +181,7 @@ class AskService {
|
|||||||
* @param {string} userPrompt
|
* @param {string} userPrompt
|
||||||
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
|
* @returns {Promise<{success: boolean, response?: string, error?: string}>}
|
||||||
*/
|
*/
|
||||||
async sendMessage(userPrompt) {
|
async sendMessage(userPrompt, conversationHistoryRaw=[]) {
|
||||||
if (this.abortController) {
|
if (this.abortController) {
|
||||||
this.abortController.abort('New request received.');
|
this.abortController.abort('New request received.');
|
||||||
}
|
}
|
||||||
@ -117,20 +221,7 @@ class AskService {
|
|||||||
const screenshotResult = await captureScreenshot({ quality: 'medium' });
|
const screenshotResult = await captureScreenshot({ quality: 'medium' });
|
||||||
const screenshotBase64 = screenshotResult.success ? screenshotResult.base64 : null;
|
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);
|
const conversationHistory = this._formatConversationForPrompt(conversationHistoryRaw);
|
||||||
console.log(`[AskService] Using conversation history (${conversationHistory}`);
|
|
||||||
|
|
||||||
const systemPrompt = getSystemPrompt('pickle_glass_analysis', conversationHistory, false);
|
const systemPrompt = getSystemPrompt('pickle_glass_analysis', conversationHistory, false);
|
||||||
|
|
||||||
@ -241,6 +332,9 @@ class AskService {
|
|||||||
console.log(`[AskService] Stream reading was intentionally cancelled. Reason: ${signal.reason}`);
|
console.log(`[AskService] Stream reading was intentionally cancelled. Reason: ${signal.reason}`);
|
||||||
} else {
|
} else {
|
||||||
console.error('[AskService] Error while processing stream:', streamError);
|
console.error('[AskService] Error while processing stream:', streamError);
|
||||||
|
if (askWin && !askWin.isDestroyed()) {
|
||||||
|
askWin.webContents.send('ask-response-stream-error', { error: streamError.message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.state.isStreaming = false;
|
this.state.isStreaming = false;
|
||||||
@ -256,6 +350,12 @@ class AskService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleStopScreenCapture() {
|
||||||
|
lastScreenshot = null;
|
||||||
|
console.log('[AskService] Stopped screen capture and cleared cache.');
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const askService = new AskService();
|
const askService = new AskService();
|
||||||
|
@ -2,23 +2,9 @@ const { BrowserWindow, globalShortcut, ipcMain, screen, app, shell, desktopCaptu
|
|||||||
const WindowLayoutManager = require('./windowLayoutManager');
|
const WindowLayoutManager = require('./windowLayoutManager');
|
||||||
const SmoothMovementManager = require('./smoothMovementManager');
|
const SmoothMovementManager = require('./smoothMovementManager');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('node:fs');
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const util = require('util');
|
|
||||||
const execFile = util.promisify(require('child_process').execFile);
|
|
||||||
const shortcutsService = require('../features/shortcuts/shortcutsService');
|
const shortcutsService = require('../features/shortcuts/shortcutsService');
|
||||||
const internalBridge = require('../bridge/internalBridge');
|
const internalBridge = require('../bridge/internalBridge');
|
||||||
|
|
||||||
// Try to load sharp, but don't fail if it's not available
|
|
||||||
let sharp;
|
|
||||||
try {
|
|
||||||
sharp = require('sharp');
|
|
||||||
console.log('[WindowManager] Sharp module loaded successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[WindowManager] Sharp module not available:', error.message);
|
|
||||||
console.warn('[WindowManager] Screenshot functionality will work with reduced image processing capabilities');
|
|
||||||
sharp = null;
|
|
||||||
}
|
|
||||||
const permissionRepository = require('../features/common/repositories/permission');
|
const permissionRepository = require('../features/common/repositories/permission');
|
||||||
|
|
||||||
/* ────────────────[ GLASS BYPASS ]─────────────── */
|
/* ────────────────[ GLASS BYPASS ]─────────────── */
|
||||||
@ -53,7 +39,6 @@ const DEFAULT_WINDOW_WIDTH = 353;
|
|||||||
let currentHeaderState = 'apikey';
|
let currentHeaderState = 'apikey';
|
||||||
const windowPool = new Map();
|
const windowPool = new Map();
|
||||||
let fixedYPosition = 0;
|
let fixedYPosition = 0;
|
||||||
let lastScreenshot = null;
|
|
||||||
|
|
||||||
let settingsHideTimer = null;
|
let settingsHideTimer = null;
|
||||||
|
|
||||||
@ -719,59 +704,6 @@ function setupIpcHandlers(movementManager) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('start-screen-capture', async () => {
|
|
||||||
try {
|
|
||||||
isCapturing = true;
|
|
||||||
console.log('Starting screen capture in main process');
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to start screen capture:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('stop-screen-capture', async () => {
|
|
||||||
try {
|
|
||||||
isCapturing = false;
|
|
||||||
lastScreenshot = null;
|
|
||||||
console.log('Stopped screen capture in main process');
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to stop screen capture:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('capture-screenshot', async (event, options = {}) => {
|
|
||||||
return captureScreenshot(options);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('get-current-screenshot', async event => {
|
|
||||||
try {
|
|
||||||
if (lastScreenshot && Date.now() - lastScreenshot.timestamp < 1000) {
|
|
||||||
console.log('Returning cached screenshot');
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
base64: lastScreenshot.base64,
|
|
||||||
width: lastScreenshot.width,
|
|
||||||
height: lastScreenshot.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'No screenshot available',
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to get current screenshot:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// firebase-logout handler moved to windowBridge.js to avoid duplication
|
|
||||||
|
|
||||||
ipcMain.handle('check-system-permissions', async () => {
|
ipcMain.handle('check-system-permissions', async () => {
|
||||||
const { systemPreferences } = require('electron');
|
const { systemPreferences } = require('electron');
|
||||||
const permissions = {
|
const permissions = {
|
||||||
@ -1041,91 +973,6 @@ function setupApiKeyIPC() {
|
|||||||
//////// after_modelStateService ////////
|
//////// after_modelStateService ////////
|
||||||
|
|
||||||
|
|
||||||
async function captureScreenshot(options = {}) {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
try {
|
|
||||||
const tempPath = path.join(os.tmpdir(), `screenshot-${Date.now()}.jpg`);
|
|
||||||
|
|
||||||
await execFile('screencapture', ['-x', '-t', 'jpg', tempPath]);
|
|
||||||
|
|
||||||
const imageBuffer = await fs.promises.readFile(tempPath);
|
|
||||||
await fs.promises.unlink(tempPath);
|
|
||||||
|
|
||||||
if (sharp) {
|
|
||||||
try {
|
|
||||||
// Try using sharp for optimal image processing
|
|
||||||
const resizedBuffer = await sharp(imageBuffer)
|
|
||||||
// .resize({ height: 1080 })
|
|
||||||
.resize({ height: 384 })
|
|
||||||
.jpeg({ quality: 80 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
const base64 = resizedBuffer.toString('base64');
|
|
||||||
const metadata = await sharp(resizedBuffer).metadata();
|
|
||||||
|
|
||||||
lastScreenshot = {
|
|
||||||
base64,
|
|
||||||
width: metadata.width,
|
|
||||||
height: metadata.height,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { success: true, base64, width: metadata.width, height: metadata.height };
|
|
||||||
} catch (sharpError) {
|
|
||||||
console.warn('Sharp module failed, falling back to basic image processing:', sharpError.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: Return the original image without resizing
|
|
||||||
console.log('[WindowManager] Using fallback image processing (no resize/compression)');
|
|
||||||
const base64 = imageBuffer.toString('base64');
|
|
||||||
|
|
||||||
lastScreenshot = {
|
|
||||||
base64,
|
|
||||||
width: null, // We don't have metadata without sharp
|
|
||||||
height: null,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { success: true, base64, width: null, height: null };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to capture screenshot:', error);
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const sources = await desktopCapturer.getSources({
|
|
||||||
types: ['screen'],
|
|
||||||
thumbnailSize: {
|
|
||||||
width: 1920,
|
|
||||||
height: 1080,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sources.length === 0) {
|
|
||||||
throw new Error('No screen sources available');
|
|
||||||
}
|
|
||||||
const source = sources[0];
|
|
||||||
const buffer = source.thumbnail.toJPEG(70);
|
|
||||||
const base64 = buffer.toString('base64');
|
|
||||||
const size = source.thumbnail.getSize();
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
base64,
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to capture screenshot using desktopCapturer:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeWindow = (windowName) => {
|
const closeWindow = (windowName) => {
|
||||||
const win = windowPool.get(windowName);
|
const win = windowPool.get(windowName);
|
||||||
if (win && !win.isDestroyed()) {
|
if (win && !win.isDestroyed()) {
|
||||||
@ -1141,7 +988,6 @@ module.exports = {
|
|||||||
getStoredApiKey,
|
getStoredApiKey,
|
||||||
getStoredProvider,
|
getStoredProvider,
|
||||||
getCurrentModelInfo,
|
getCurrentModelInfo,
|
||||||
captureScreenshot,
|
|
||||||
toggleFeature, // Export toggleFeature so shortcutsService can use it
|
toggleFeature, // Export toggleFeature so shortcutsService can use it
|
||||||
toggleContentProtection,
|
toggleContentProtection,
|
||||||
resizeHeaderWindow,
|
resizeHeaderWindow,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user