2024-10-26 13:05:56 -03:00
|
|
|
"use strict";
|
2024-10-26 16:26:11 -03:00
|
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
|
|
|
if (k2 === undefined) k2 = k;
|
|
|
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
|
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
|
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
|
|
|
}
|
|
|
|
|
Object.defineProperty(o, k2, desc);
|
|
|
|
|
}) : (function(o, m, k, k2) {
|
|
|
|
|
if (k2 === undefined) k2 = k;
|
|
|
|
|
o[k2] = m[k];
|
|
|
|
|
}));
|
|
|
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
|
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
|
|
|
}) : function(o, v) {
|
|
|
|
|
o["default"] = v;
|
|
|
|
|
});
|
|
|
|
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
|
|
if (mod && mod.__esModule) return mod;
|
|
|
|
|
var result = {};
|
|
|
|
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
|
|
|
__setModuleDefault(result, mod);
|
|
|
|
|
return result;
|
2024-10-26 13:05:56 -03:00
|
|
|
};
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
exports.RecorderService = void 0;
|
|
|
|
|
const electron_1 = require("electron");
|
2024-10-26 16:26:11 -03:00
|
|
|
const openai_service_1 = require("../services/openai.service");
|
|
|
|
|
const path = __importStar(require("path"));
|
|
|
|
|
const fs = __importStar(require("fs"));
|
2024-10-26 13:05:56 -03:00
|
|
|
class RecorderService {
|
|
|
|
|
constructor() {
|
2024-10-27 13:07:05 -03:00
|
|
|
this.eventGroups = [];
|
|
|
|
|
this.currentEvents = [];
|
2024-10-26 13:05:56 -03:00
|
|
|
this.recording = false;
|
|
|
|
|
this.currentScreenshot = '';
|
2024-10-27 13:07:05 -03:00
|
|
|
this.audioBuffer = [];
|
|
|
|
|
this.isListeningToMicrophone = false;
|
2024-10-26 16:26:11 -03:00
|
|
|
this.silenceTimer = null;
|
|
|
|
|
this.isProcessingAudio = false;
|
2024-10-27 13:07:05 -03:00
|
|
|
this.SILENCE_THRESHOLD = 0.01;
|
|
|
|
|
this.SILENCE_DURATION = 1500; // 1.5 seconds of silence to trigger processing
|
|
|
|
|
this.MIN_AUDIO_DURATION = 500; // Minimum audio duration to process
|
|
|
|
|
this.handleAudioLevel = (_, level) => {
|
|
|
|
|
if (!this.recording || !this.isListeningToMicrophone)
|
2024-10-26 16:26:11 -03:00
|
|
|
return;
|
2024-10-27 13:07:05 -03:00
|
|
|
if (level < this.SILENCE_THRESHOLD) {
|
|
|
|
|
if (!this.silenceTimer && !this.isProcessingAudio && this.audioBuffer.length > 0) {
|
2024-10-26 16:26:11 -03:00
|
|
|
this.silenceTimer = setTimeout(async () => {
|
|
|
|
|
if (this.recording) {
|
2024-10-27 13:07:05 -03:00
|
|
|
await this.processCapturedAudio();
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
2024-10-27 13:07:05 -03:00
|
|
|
}, this.SILENCE_DURATION);
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (this.silenceTimer) {
|
|
|
|
|
clearTimeout(this.silenceTimer);
|
|
|
|
|
this.silenceTimer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-26 21:21:51 -03:00
|
|
|
};
|
2024-10-27 13:07:05 -03:00
|
|
|
this.handleAudioChunk = (_, chunk) => {
|
|
|
|
|
if (!this.recording || !this.isListeningToMicrophone)
|
|
|
|
|
return;
|
|
|
|
|
this.audioBuffer.push(chunk);
|
|
|
|
|
};
|
|
|
|
|
this.handleKeyboardEvent = async (_, event) => {
|
2024-10-26 16:26:11 -03:00
|
|
|
if (!this.recording)
|
|
|
|
|
return;
|
2024-10-27 13:07:05 -03:00
|
|
|
this.currentEvents.push({
|
|
|
|
|
type: 'type',
|
|
|
|
|
identifier: event.key,
|
|
|
|
|
value: event.key,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
narration: ''
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
this.handleMouseEvent = async (_, event) => {
|
|
|
|
|
if (!this.recording)
|
|
|
|
|
return;
|
|
|
|
|
const analysis = await this.openAIService.analyzeScreen(this.currentScreenshot);
|
|
|
|
|
const element = this.findElementAtPosition(analysis, event.clientX, event.clientY);
|
|
|
|
|
if (element) {
|
|
|
|
|
this.currentEvents.push({
|
|
|
|
|
type: 'click',
|
|
|
|
|
identifier: element.identifier,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
narration: ''
|
|
|
|
|
});
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
};
|
2024-10-26 21:21:51 -03:00
|
|
|
console.log('RecorderService.constructor()');
|
2024-10-26 13:05:56 -03:00
|
|
|
this.openAIService = new openai_service_1.OpenAIService();
|
2024-10-26 16:26:11 -03:00
|
|
|
this.tempDir = path.join(process.cwd(), 'temp_recordings');
|
2024-10-27 13:07:05 -03:00
|
|
|
this.ensureTempDirectory();
|
|
|
|
|
}
|
|
|
|
|
ensureTempDirectory() {
|
2024-10-26 16:26:11 -03:00
|
|
|
if (!fs.existsSync(this.tempDir)) {
|
|
|
|
|
fs.mkdirSync(this.tempDir, { recursive: true });
|
|
|
|
|
}
|
2024-10-26 13:05:56 -03:00
|
|
|
}
|
|
|
|
|
async startRecording() {
|
2024-10-26 21:21:51 -03:00
|
|
|
console.log('RecorderService.startRecording()');
|
2024-10-26 16:26:11 -03:00
|
|
|
try {
|
|
|
|
|
this.recording = true;
|
2024-10-27 13:07:05 -03:00
|
|
|
this.eventGroups = [];
|
|
|
|
|
this.currentEvents = [];
|
|
|
|
|
await this.startMicrophoneCapture();
|
|
|
|
|
await this.captureInitialScreenshot();
|
|
|
|
|
this.setupEventListeners();
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
catch (error) {
|
2024-10-26 21:21:51 -03:00
|
|
|
console.error('RecorderService.startRecording() error:', error);
|
2024-10-26 16:26:11 -03:00
|
|
|
this.recording = false;
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-27 13:07:05 -03:00
|
|
|
async startMicrophoneCapture() {
|
|
|
|
|
console.log('RecorderService.startMicrophoneCapture()');
|
2024-10-26 16:26:11 -03:00
|
|
|
try {
|
2024-10-27 13:07:05 -03:00
|
|
|
this.isListeningToMicrophone = true;
|
2024-10-26 16:26:11 -03:00
|
|
|
electron_1.ipcRenderer.on('audio-level', this.handleAudioLevel);
|
|
|
|
|
electron_1.ipcRenderer.on('audio-chunk', this.handleAudioChunk);
|
2024-10-27 13:07:05 -03:00
|
|
|
await electron_1.ipcRenderer.invoke('start-microphone-capture');
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
catch (error) {
|
2024-10-27 13:07:05 -03:00
|
|
|
console.error('Failed to start microphone capture:', error);
|
|
|
|
|
throw new Error(`Microphone initialization failed: ${error.message}`);
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-27 13:07:05 -03:00
|
|
|
async processCapturedAudio() {
|
|
|
|
|
if (this.isProcessingAudio || this.audioBuffer.length === 0)
|
2024-10-26 16:26:11 -03:00
|
|
|
return;
|
|
|
|
|
this.isProcessingAudio = true;
|
2024-10-27 13:07:05 -03:00
|
|
|
const combinedBuffer = Buffer.concat(this.audioBuffer);
|
|
|
|
|
this.audioBuffer = []; // Clear the buffer
|
2024-10-26 16:26:11 -03:00
|
|
|
try {
|
2024-10-27 13:07:05 -03:00
|
|
|
const audioFilePath = path.join(this.tempDir, `audio-${Date.now()}.wav`);
|
|
|
|
|
fs.writeFileSync(audioFilePath, combinedBuffer);
|
|
|
|
|
const transcription = await this.openAIService.transcribeAudio(new Blob([combinedBuffer], { type: 'audio/wav' }));
|
|
|
|
|
if (transcription.text.trim()) {
|
|
|
|
|
await this.processNarrationWithEvents(transcription.text);
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
2024-10-27 13:07:05 -03:00
|
|
|
fs.unlinkSync(audioFilePath);
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
catch (error) {
|
2024-10-27 13:07:05 -03:00
|
|
|
console.error('Audio processing error:', error);
|
2024-10-26 16:26:11 -03:00
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
this.isProcessingAudio = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-27 13:07:05 -03:00
|
|
|
async processNarrationWithEvents(narration) {
|
|
|
|
|
if (this.currentEvents.length === 0)
|
|
|
|
|
return;
|
|
|
|
|
const eventGroup = {
|
|
|
|
|
narration,
|
|
|
|
|
events: [...this.currentEvents],
|
2024-10-26 16:26:11 -03:00
|
|
|
screenshot: this.currentScreenshot,
|
2024-10-27 13:07:05 -03:00
|
|
|
timestamp: Date.now()
|
|
|
|
|
};
|
|
|
|
|
this.eventGroups.push(eventGroup);
|
|
|
|
|
this.currentEvents = []; // Clear current events for next group
|
|
|
|
|
await this.captureInitialScreenshot(); // Get fresh screenshot for next group
|
|
|
|
|
}
|
|
|
|
|
setupEventListeners() {
|
|
|
|
|
electron_1.ipcRenderer.on('keyboard-event', this.handleKeyboardEvent);
|
|
|
|
|
electron_1.ipcRenderer.on('mouse-event', this.handleMouseEvent);
|
|
|
|
|
}
|
|
|
|
|
async captureInitialScreenshot() {
|
|
|
|
|
const sources = await electron_1.ipcRenderer.invoke('get-screenshot');
|
|
|
|
|
this.currentScreenshot = sources[0].thumbnail;
|
|
|
|
|
}
|
|
|
|
|
findElementAtPosition(analysis, x, y) {
|
|
|
|
|
return analysis.elements.find(element => {
|
|
|
|
|
const bounds = element.bounds;
|
|
|
|
|
return x >= bounds.x &&
|
|
|
|
|
x <= bounds.x + bounds.width &&
|
|
|
|
|
y >= bounds.y &&
|
|
|
|
|
y <= bounds.y + bounds.height;
|
2024-10-26 16:26:11 -03:00
|
|
|
});
|
2024-10-26 13:05:56 -03:00
|
|
|
}
|
2024-10-26 16:26:11 -03:00
|
|
|
async stopRecording() {
|
2024-10-26 21:21:51 -03:00
|
|
|
console.log('RecorderService.stopRecording()');
|
2024-10-27 13:07:05 -03:00
|
|
|
// Process any remaining audio
|
|
|
|
|
if (this.audioBuffer.length > 0) {
|
|
|
|
|
await this.processCapturedAudio();
|
|
|
|
|
}
|
|
|
|
|
this.cleanup();
|
|
|
|
|
return this.generateBasicCode();
|
|
|
|
|
}
|
|
|
|
|
cleanup() {
|
2024-10-26 13:05:56 -03:00
|
|
|
this.recording = false;
|
2024-10-27 13:07:05 -03:00
|
|
|
this.isListeningToMicrophone = false;
|
2024-10-26 16:26:11 -03:00
|
|
|
if (this.silenceTimer) {
|
|
|
|
|
clearTimeout(this.silenceTimer);
|
|
|
|
|
this.silenceTimer = null;
|
|
|
|
|
}
|
|
|
|
|
electron_1.ipcRenderer.removeListener('audio-level', this.handleAudioLevel);
|
|
|
|
|
electron_1.ipcRenderer.removeListener('audio-chunk', this.handleAudioChunk);
|
2024-10-27 13:07:05 -03:00
|
|
|
electron_1.ipcRenderer.removeListener('keyboard-event', this.handleKeyboardEvent);
|
|
|
|
|
electron_1.ipcRenderer.removeListener('mouse-event', this.handleMouseEvent);
|
|
|
|
|
// Cleanup temp directory
|
|
|
|
|
fs.readdirSync(this.tempDir).forEach(file => {
|
|
|
|
|
fs.unlinkSync(path.join(this.tempDir, file));
|
2024-10-26 13:05:56 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
generateBasicCode() {
|
|
|
|
|
let basicCode = '10 REM BotDesktop Automation Script\n';
|
|
|
|
|
let lineNumber = 20;
|
2024-10-27 13:07:05 -03:00
|
|
|
this.eventGroups.forEach(group => {
|
|
|
|
|
basicCode += `${lineNumber} REM ${group.narration}\n`;
|
2024-10-26 13:05:56 -03:00
|
|
|
lineNumber += 10;
|
2024-10-27 13:07:05 -03:00
|
|
|
group.events.forEach(event => {
|
|
|
|
|
switch (event.type) {
|
|
|
|
|
case 'click':
|
|
|
|
|
basicCode += `${lineNumber} CLICK "${event.identifier}"\n`;
|
|
|
|
|
break;
|
|
|
|
|
case 'type':
|
|
|
|
|
basicCode += `${lineNumber} TYPE "${event.identifier}" "${event.value}"\n`;
|
|
|
|
|
break;
|
|
|
|
|
case 'move':
|
|
|
|
|
basicCode += `${lineNumber} MOVE "${event.identifier}"\n`;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
lineNumber += 10;
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-10-26 13:05:56 -03:00
|
|
|
basicCode += `${lineNumber} END\n`;
|
|
|
|
|
return basicCode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
exports.RecorderService = RecorderService;
|