Set up the basic structure and core functionality for the application
Initialize project with UI components, core libraries, and basic app structure. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 556aa286-edd2-4cea-8583-f4fc3cfd119b
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
import { storage } from '../storage';
|
||||
|
||||
export interface AIResponse {
|
||||
content: string;
|
||||
processingTime: number;
|
||||
modelUsed: string;
|
||||
}
|
||||
|
||||
export interface JokeResponse extends AIResponse {
|
||||
category: string;
|
||||
}
|
||||
|
||||
class AIService {
|
||||
private modelPath: string;
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
constructor() {
|
||||
this.modelPath = process.env.AI_MODEL_PATH || './models/ai/base-llm';
|
||||
}
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
try {
|
||||
console.log('Initializing AI models...');
|
||||
|
||||
// In production, this would load the actual local LLM
|
||||
await this.loadModel();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('AI models initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize AI models:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadModel(): Promise<void> {
|
||||
// Simulate model loading time
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
console.log(`AI model loaded from ${this.modelPath}`);
|
||||
}
|
||||
|
||||
async generateDailyJoke(): Promise<JokeResponse> {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// In production, this would use the actual AI model
|
||||
const joke = await this.simulateJokeGeneration();
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
const response: JokeResponse = {
|
||||
content: joke,
|
||||
processingTime,
|
||||
modelUsed: 'local-llm-base',
|
||||
category: 'programming'
|
||||
};
|
||||
|
||||
// Store the interaction
|
||||
await storage.createAIInteraction({
|
||||
userId: null, // Daily joke is global
|
||||
type: 'joke',
|
||||
prompt: 'Generate daily programming joke',
|
||||
response: joke,
|
||||
modelUsed: 'local-llm-base',
|
||||
processingTime,
|
||||
wasSpoken: false
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('AI joke generation error:', error);
|
||||
throw new Error('Failed to generate joke');
|
||||
}
|
||||
}
|
||||
|
||||
async generatePersonalizedJoke(userId: number, preferences?: any): Promise<JokeResponse> {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Get user context for personalization
|
||||
const userTasks = await storage.getTasks(userId);
|
||||
const recentCommands = await storage.getVoiceCommands(userId, 5);
|
||||
|
||||
const joke = await this.simulatePersonalizedJoke(userTasks.length, recentCommands.length);
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
const response: JokeResponse = {
|
||||
content: joke,
|
||||
processingTime,
|
||||
modelUsed: 'local-llm-base',
|
||||
category: 'personalized'
|
||||
};
|
||||
|
||||
// Store the interaction
|
||||
await storage.createAIInteraction({
|
||||
userId,
|
||||
type: 'joke',
|
||||
prompt: 'Generate personalized joke',
|
||||
response: joke,
|
||||
modelUsed: 'local-llm-base',
|
||||
processingTime,
|
||||
wasSpoken: false
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Personalized joke generation error:', error);
|
||||
throw new Error('Failed to generate personalized joke');
|
||||
}
|
||||
}
|
||||
|
||||
async chat(userId: number, message: string): Promise<AIResponse> {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// Get user context
|
||||
const user = await storage.getUser(userId);
|
||||
const recentTasks = await storage.getTasks(userId);
|
||||
const financialSummary = await storage.getFinancialSummary(userId);
|
||||
|
||||
const response = await this.simulateChat(message, {
|
||||
user,
|
||||
taskCount: recentTasks.length,
|
||||
financialSummary
|
||||
});
|
||||
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
const aiResponse: AIResponse = {
|
||||
content: response,
|
||||
processingTime,
|
||||
modelUsed: 'local-llm-base'
|
||||
};
|
||||
|
||||
// Store the interaction
|
||||
await storage.createAIInteraction({
|
||||
userId,
|
||||
type: 'response',
|
||||
prompt: message,
|
||||
response,
|
||||
modelUsed: 'local-llm-base',
|
||||
processingTime,
|
||||
wasSpoken: false
|
||||
});
|
||||
|
||||
return aiResponse;
|
||||
} catch (error) {
|
||||
console.error('AI chat error:', error);
|
||||
throw new Error('Failed to process chat message');
|
||||
}
|
||||
}
|
||||
|
||||
async generateInsight(userId: number, type: 'financial' | 'productivity' | 'general'): Promise<AIResponse> {
|
||||
if (!this.isInitialized) {
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
let insight = '';
|
||||
|
||||
switch (type) {
|
||||
case 'financial':
|
||||
const summary = await storage.getFinancialSummary(userId);
|
||||
insight = await this.simulateFinancialInsight(summary);
|
||||
break;
|
||||
case 'productivity':
|
||||
const tasks = await storage.getTasks(userId);
|
||||
insight = await this.simulateProductivityInsight(tasks);
|
||||
break;
|
||||
default:
|
||||
insight = await this.simulateGeneralInsight();
|
||||
}
|
||||
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
const response: AIResponse = {
|
||||
content: insight,
|
||||
processingTime,
|
||||
modelUsed: 'local-llm-base'
|
||||
};
|
||||
|
||||
// Store the interaction
|
||||
await storage.createAIInteraction({
|
||||
userId,
|
||||
type: 'insight',
|
||||
prompt: `Generate ${type} insight`,
|
||||
response: insight,
|
||||
modelUsed: 'local-llm-base',
|
||||
processingTime,
|
||||
wasSpoken: false
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('AI insight generation error:', error);
|
||||
throw new Error('Failed to generate insight');
|
||||
}
|
||||
}
|
||||
|
||||
private async simulateJokeGeneration(): Promise<string> {
|
||||
const jokes = [
|
||||
"Why don't developers ever finish their tasks? Because they always get stuck in an infinite loop of 'just one more feature'!",
|
||||
"Why did the programmer quit his job? He didn't get arrays! (a raise)",
|
||||
"How many programmers does it take to screw in a light bulb? None, that's a hardware problem!",
|
||||
"Why do Java developers wear glasses? Because they can't C#!",
|
||||
"A SQL query walks into a bar, approaches two tables and asks: 'Can I join you?'",
|
||||
"Why don't tasks ever get lonely? Because they always have deadlines to keep them company!",
|
||||
"What's a programmer's favorite hangout place? The Foo Bar!",
|
||||
"Why did the developer go broke? Because he used up all his cache!"
|
||||
];
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
return jokes[Math.floor(Math.random() * jokes.length)];
|
||||
}
|
||||
|
||||
private async simulatePersonalizedJoke(taskCount: number, voiceCommandCount: number): Promise<string> {
|
||||
let joke = '';
|
||||
|
||||
if (taskCount > 10) {
|
||||
joke = "Looks like you're collecting tasks like Pokémon cards! Gotta catch 'em all... and then actually do them!";
|
||||
} else if (taskCount === 0) {
|
||||
joke = "Your task list is emptier than a JavaScript developer's knowledge of semicolons!";
|
||||
} else if (voiceCommandCount > 5) {
|
||||
joke = "You're talking to me more than most people talk to their houseplants. At least I talk back!";
|
||||
} else {
|
||||
joke = await this.simulateJokeGeneration();
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return joke;
|
||||
}
|
||||
|
||||
private async simulateChat(message: string, context: any): Promise<string> {
|
||||
const lowerMessage = message.toLowerCase();
|
||||
|
||||
if (lowerMessage.includes('task') || lowerMessage.includes('todo')) {
|
||||
if (context.taskCount === 0) {
|
||||
return "You have no pending tasks right now. Great job staying on top of things! Ready to add something new?";
|
||||
} else {
|
||||
return `You currently have ${context.taskCount} pending tasks. Would you like me to help you prioritize them or create a new one?`;
|
||||
}
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('money') || lowerMessage.includes('financial') || lowerMessage.includes('budget')) {
|
||||
const { income, expenses, net } = context.financialSummary;
|
||||
return `Your current financial status shows $${income} in income and $${expenses} in expenses, for a net of $${net}. ${net > 0 ? "You're in the positive!" : "Consider reviewing your expenses."}`;
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('help')) {
|
||||
return "I can help you with tasks, financial tracking, and answering questions about your data. Try saying 'create task', 'add expense', or ask about your financial summary!";
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('hello') || lowerMessage.includes('hi')) {
|
||||
return `Hello ${context.user?.firstName || 'there'}! I'm your AI assistant. How can I help you manage your tasks and finances today?`;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
return "I understand you're asking about that. While I'm still learning, I can help you with tasks, expenses, and general productivity questions. Is there something specific I can assist with?";
|
||||
}
|
||||
|
||||
private async simulateFinancialInsight(summary: { income: number; expenses: number; net: number }): Promise<string> {
|
||||
await new Promise(resolve => setTimeout(resolve, 1200));
|
||||
|
||||
const { income, expenses, net } = summary;
|
||||
const expenseRatio = income > 0 ? (expenses / income) * 100 : 0;
|
||||
|
||||
if (net > 0) {
|
||||
if (expenseRatio < 70) {
|
||||
return `Excellent financial management! You're spending only ${expenseRatio.toFixed(1)}% of your income. Consider investing the surplus for long-term growth.`;
|
||||
} else {
|
||||
return `Good job maintaining a positive balance! Your expense ratio is ${expenseRatio.toFixed(1)}%. Consider optimizing some categories to improve savings.`;
|
||||
}
|
||||
} else {
|
||||
return `Your expenses exceed income by $${Math.abs(net)}. I'd recommend reviewing your spending categories and identifying areas where you can reduce costs.`;
|
||||
}
|
||||
}
|
||||
|
||||
private async simulateProductivityInsight(tasks: any[]): Promise<string> {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const completedTasks = tasks.filter(t => t.status === 'completed');
|
||||
const pendingTasks = tasks.filter(t => t.status === 'pending');
|
||||
const overdueTasks = tasks.filter(t => t.dueDate && new Date(t.dueDate) < new Date() && t.status !== 'completed');
|
||||
|
||||
if (tasks.length === 0) {
|
||||
return "Your task list is clear! This is a great time to plan ahead or tackle some long-term goals.";
|
||||
}
|
||||
|
||||
if (overdueTasks.length > 0) {
|
||||
return `You have ${overdueTasks.length} overdue tasks. Consider prioritizing these first to get back on track.`;
|
||||
}
|
||||
|
||||
if (completedTasks.length > pendingTasks.length) {
|
||||
return `Great productivity! You've completed ${completedTasks.length} tasks. Keep up the momentum with your remaining ${pendingTasks.length} tasks.`;
|
||||
}
|
||||
|
||||
return `You have ${pendingTasks.length} pending tasks. Consider breaking larger tasks into smaller, manageable chunks for better progress.`;
|
||||
}
|
||||
|
||||
private async simulateGeneralInsight(): Promise<string> {
|
||||
const insights = [
|
||||
"Remember: Progress, not perfection. Small daily improvements lead to remarkable long-term results.",
|
||||
"Voice commands can speed up your workflow by 40%. Try using them for quick task creation and expense logging.",
|
||||
"The best time to plan tomorrow is today. Spend 5 minutes each evening reviewing and organizing.",
|
||||
"Your data stays completely private with local processing. No cloud, no worries!",
|
||||
"Consistency beats intensity. Better to track expenses daily than to do it all at month-end."
|
||||
];
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
return insights[Math.floor(Math.random() * insights.length)];
|
||||
}
|
||||
|
||||
async getModelStatus(): Promise<{ loaded: boolean; modelPath: string; initialized: boolean }> {
|
||||
return {
|
||||
loaded: this.isInitialized,
|
||||
modelPath: this.modelPath,
|
||||
initialized: this.isInitialized
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const aiService = new AIService();
|
||||
@@ -0,0 +1,330 @@
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { storage } from '../storage';
|
||||
|
||||
export interface VoiceProcessingResult {
|
||||
transcription: string;
|
||||
confidence: number;
|
||||
processingTime: number;
|
||||
intent?: string;
|
||||
entities?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TTSResult {
|
||||
audioBuffer: Buffer;
|
||||
processingTime: number;
|
||||
}
|
||||
|
||||
class VoiceService {
|
||||
private sttModelPath: string;
|
||||
private ttsModelPath: string;
|
||||
private isInitialized: boolean = false;
|
||||
private sttProcess: ChildProcess | null = null;
|
||||
private ttsProcess: ChildProcess | null = null;
|
||||
|
||||
constructor() {
|
||||
this.sttModelPath = process.env.STT_MODEL_PATH || './models/speech/base-stt';
|
||||
this.ttsModelPath = process.env.TTS_MODEL_PATH || './models/speech/base-tts';
|
||||
}
|
||||
|
||||
async initialize(): Promise<boolean> {
|
||||
try {
|
||||
console.log('Initializing voice models...');
|
||||
|
||||
// Check if model files exist
|
||||
const sttExists = await this.checkModelExists(this.sttModelPath);
|
||||
const ttsExists = await this.checkModelExists(this.ttsModelPath);
|
||||
|
||||
if (!sttExists || !ttsExists) {
|
||||
console.error('Voice models not found. Please run model setup script.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize STT model (using whisper.cpp or similar)
|
||||
await this.initializeSTT();
|
||||
|
||||
// Initialize TTS model (using espeak-ng or similar)
|
||||
await this.initializeTTS();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('Voice models initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize voice models:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async checkModelExists(modelPath: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(modelPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeSTT(): Promise<void> {
|
||||
// Initialize STT process - in production, this would start the actual model
|
||||
console.log(`Loading STT model from ${this.sttModelPath}`);
|
||||
// For now, we'll simulate model loading
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
private async initializeTTS(): Promise<void> {
|
||||
// Initialize TTS process - in production, this would start the actual model
|
||||
console.log(`Loading TTS model from ${this.ttsModelPath}`);
|
||||
// For now, we'll simulate model loading
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
async processAudioToText(audioBuffer: Buffer, userId?: number): Promise<VoiceProcessingResult> {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('Voice service not initialized');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// In production, this would use actual STT processing
|
||||
// For now, we'll simulate transcription
|
||||
const transcription = await this.simulateSTT(audioBuffer);
|
||||
const confidence = 0.95; // Simulated confidence
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
// Extract intent and entities from transcription
|
||||
const { intent, entities } = this.extractIntent(transcription);
|
||||
|
||||
const result: VoiceProcessingResult = {
|
||||
transcription,
|
||||
confidence,
|
||||
processingTime,
|
||||
intent,
|
||||
entities
|
||||
};
|
||||
|
||||
// Log voice command if user is provided
|
||||
if (userId) {
|
||||
await storage.createVoiceCommand({
|
||||
userId,
|
||||
command: transcription,
|
||||
transcription,
|
||||
intent,
|
||||
confidence,
|
||||
processingTime,
|
||||
successful: true
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
if (userId) {
|
||||
await storage.createVoiceCommand({
|
||||
userId,
|
||||
command: '',
|
||||
transcription: '',
|
||||
confidence: 0,
|
||||
processingTime,
|
||||
successful: false,
|
||||
errorMessage: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async textToSpeech(text: string, voice?: string): Promise<TTSResult> {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('Voice service not initialized');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// In production, this would use actual TTS processing
|
||||
const audioBuffer = await this.simulateTTS(text, voice);
|
||||
const processingTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
audioBuffer,
|
||||
processingTime
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('TTS error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async simulateSTT(audioBuffer: Buffer): Promise<string> {
|
||||
// Simulate STT processing delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Return a simulated transcription based on common commands
|
||||
const sampleCommands = [
|
||||
"Create task review budget reports",
|
||||
"Add expense coffee shop four dollars fifty cents",
|
||||
"Show me today's tasks",
|
||||
"What's my financial summary",
|
||||
"Schedule meeting with team for Friday",
|
||||
"Mark task as completed",
|
||||
"Add income freelance payment five hundred dollars"
|
||||
];
|
||||
|
||||
return sampleCommands[Math.floor(Math.random() * sampleCommands.length)];
|
||||
}
|
||||
|
||||
private async simulateTTS(text: string, voice?: string): Promise<Buffer> {
|
||||
// Simulate TTS processing delay
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
|
||||
// Return a dummy audio buffer (in production, this would be actual audio)
|
||||
const dummyAudioData = Buffer.alloc(1024, 0);
|
||||
return dummyAudioData;
|
||||
}
|
||||
|
||||
private extractIntent(transcription: string): { intent?: string; entities?: Record<string, any> } {
|
||||
const text = transcription.toLowerCase();
|
||||
|
||||
// Simple intent recognition based on keywords
|
||||
if (text.includes('create') && (text.includes('task') || text.includes('todo'))) {
|
||||
return {
|
||||
intent: 'task_create',
|
||||
entities: this.extractTaskEntities(text)
|
||||
};
|
||||
}
|
||||
|
||||
if (text.includes('add') && (text.includes('expense') || text.includes('cost'))) {
|
||||
return {
|
||||
intent: 'expense_add',
|
||||
entities: this.extractExpenseEntities(text)
|
||||
};
|
||||
}
|
||||
|
||||
if (text.includes('add') && text.includes('income')) {
|
||||
return {
|
||||
intent: 'income_add',
|
||||
entities: this.extractIncomeEntities(text)
|
||||
};
|
||||
}
|
||||
|
||||
if (text.includes('show') || text.includes('list') || text.includes('get')) {
|
||||
if (text.includes('task')) {
|
||||
return { intent: 'task_list' };
|
||||
}
|
||||
if (text.includes('financial') || text.includes('money') || text.includes('summary')) {
|
||||
return { intent: 'financial_summary' };
|
||||
}
|
||||
}
|
||||
|
||||
if (text.includes('complete') || text.includes('done') || text.includes('finish')) {
|
||||
return { intent: 'task_complete' };
|
||||
}
|
||||
|
||||
return { intent: 'unknown' };
|
||||
}
|
||||
|
||||
private extractTaskEntities(text: string): Record<string, any> {
|
||||
const entities: Record<string, any> = {};
|
||||
|
||||
// Extract task title (everything after "create task" or similar)
|
||||
const taskMatch = text.match(/(?:create|add|new)\s+(?:task|todo)\s+(.+)/i);
|
||||
if (taskMatch) {
|
||||
entities.title = taskMatch[1].trim();
|
||||
}
|
||||
|
||||
// Extract priority
|
||||
if (text.includes('high priority') || text.includes('urgent')) {
|
||||
entities.priority = 'high';
|
||||
} else if (text.includes('low priority')) {
|
||||
entities.priority = 'low';
|
||||
} else {
|
||||
entities.priority = 'medium';
|
||||
}
|
||||
|
||||
// Extract due date (simplified)
|
||||
if (text.includes('today')) {
|
||||
entities.dueDate = new Date().toISOString();
|
||||
} else if (text.includes('tomorrow')) {
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
entities.dueDate = tomorrow.toISOString();
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private extractExpenseEntities(text: string): Record<string, any> {
|
||||
const entities: Record<string, any> = {};
|
||||
|
||||
// Extract amount
|
||||
const amountMatch = text.match(/(?:\$|dollar|dollars?)\s*(\d+(?:\.\d{2})?)|(\d+(?:\.\d{2})?)\s*(?:dollar|dollars?)/i);
|
||||
if (amountMatch) {
|
||||
entities.amount = parseFloat(amountMatch[1] || amountMatch[2]);
|
||||
}
|
||||
|
||||
// Extract category/description
|
||||
const categories = ['food', 'transport', 'shopping', 'utilities', 'entertainment', 'healthcare'];
|
||||
for (const category of categories) {
|
||||
if (text.includes(category)) {
|
||||
entities.category = category;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entities.category) {
|
||||
entities.category = 'other';
|
||||
}
|
||||
|
||||
// Extract description
|
||||
const expenseMatch = text.match(/(?:expense|spent|paid)\s+(?:for\s+)?(.+?)(?:\s+\$|\s+\d+|$)/i);
|
||||
if (expenseMatch) {
|
||||
entities.description = expenseMatch[1].trim();
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private extractIncomeEntities(text: string): Record<string, any> {
|
||||
const entities: Record<string, any> = {};
|
||||
|
||||
// Extract amount
|
||||
const amountMatch = text.match(/(?:\$|dollar|dollars?)\s*(\d+(?:\.\d{2})?)|(\d+(?:\.\d{2})?)\s*(?:dollar|dollars?)/i);
|
||||
if (amountMatch) {
|
||||
entities.amount = parseFloat(amountMatch[1] || amountMatch[2]);
|
||||
}
|
||||
|
||||
// Extract source/description
|
||||
const incomeMatch = text.match(/(?:income|received|earned)\s+(?:from\s+)?(.+?)(?:\s+\$|\s+\d+|$)/i);
|
||||
if (incomeMatch) {
|
||||
entities.description = incomeMatch[1].trim();
|
||||
}
|
||||
|
||||
entities.category = 'income';
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
async getModelStatus(): Promise<{ stt: boolean; tts: boolean; initialized: boolean }> {
|
||||
return {
|
||||
stt: await this.checkModelExists(this.sttModelPath),
|
||||
tts: await this.checkModelExists(this.ttsModelPath),
|
||||
initialized: this.isInitialized
|
||||
};
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
if (this.sttProcess) {
|
||||
this.sttProcess.kill();
|
||||
}
|
||||
if (this.ttsProcess) {
|
||||
this.ttsProcess.kill();
|
||||
}
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const voiceService = new VoiceService();
|
||||
Reference in New Issue
Block a user